基于 Laravel + Vue 构建 API 驱动的前后端分离应用系列(二十四) —— 使用 Vue Mixins 在高德地图上对咖啡店点标记进行筛选过滤

上一篇教程中我们在应用首页通过 Vue Mixins 实现了对咖啡店的筛选过滤,在这篇教程中,我们将复用上一篇教程中的过滤处理函数通过 Vue Mixins 实现在高德地图上对咖啡店点标记进行筛选过滤的功能,实现思路和上篇教程类似,只不过前者是对列表数据进行过滤,而本篇教程是在地图上对点标记进行过滤。

第一步:创建高德地图过滤组件

和上篇教程的 CafeFilter 组件类似,我们首先需要为地图上的咖啡店标记创建过滤组件,在
resources/assets/js/components/cafes 目录下创建 CafeMapFilter.vue 作为高德地图过滤组件,并初始化代码如下:

<style lang="scss">
    @import '~@/abstracts/_variables.scss';
</style>

<template>
    <div id="cafe-map-filter">

    </div>
</template>

<script>
    export default {

    }
</script>

然后我们修改 resources/assets/js/components/cafes/CafeMap.vue 的模板代码如下(这篇教程中的 CafeMap.vue 组件类似于上篇教程的 CafeCard.vue 组件):

<div id="cafe-map-container">
    <div id="cafe-map">

    </div>
</div>

同时在 style 中为 div#cafe-map-container 添加样式代码:

<style lang="scss">
    div#cafe-map-container {
        position: absolute;
        top: 50px;
        left: 0px;
        right: 0px;
        bottom: 50px;

        div#cafe-map {
            position: absolute;
            top: 0px;
            left: 0px;
            right: 0px;
            bottom: 0px;
        }
    }
</style>

接下来,我们在 CafeMap.vue 组件中引入 CafeMapFilter.vue 组件:

import CafeMapFilter from './CafeMapFilter.vue';

定义相应的 components 属性:

components: {
    CafeMapFilter
},

并且在 template 模板中紧随 cafe-map 之后引入地图过滤组件:

<div id="cafe-map">

</div>
<cafe-map-filter></cafe-map-filter>

CafeMapFilter.vue 组件中添加样式代码如下:

<style lang="scss">
    @import '~@/abstracts/_variables.scss';

    div#cafe-map-filter{
        background-color: white;
        border-radius: 10px;
        box-shadow: 0 2px 2px 0 rgba(0, 0, 0, 0.16), 0 0 0 1px rgba(0, 0, 0, 0.08);
        padding: 5px;
        z-index: 999999;
        position: absolute;
        right: 45px;
        top: 50px;
        width: 25%;
    }
</style>

这样,运行 npm run dev 重新编译前端资源后,在咖啡店列表(http://roast.test/#/cafes)页面右上角就可以看到过滤组件了,只不过现在该组件里还没有任何内容:

第二步:添加文本过滤器到组件

接下来我们就来为地图过滤组件填充内容,和 CafeFilter.vue 组件类似,首先,我们将文本搜索框添加到 CafeMapFilter.vue 文件的模板代码中作为文本过滤器:

<div id="cafe-map-filter">
    <div class="grid-container">
        <div class="grid-x grid-padding-x">
            <div class="large-12 medium-12 small-12 cell">
                <label>搜索</label>
                <input type="text" v-model="textSearch" placeholder="搜索"/>
            </div>
        </div>
    </div>
</div>

然后在 data 返回数据模型中添加 textSearch,以便和视图进行双向绑定:

data() {
   return {
       textSearch: ''
   }
}

第三步:是否是烘焙店过滤器

在地图过滤组件中,为了简单起见,我们省略了标签过滤器,接下来,将是否是烘焙店勾选框作为过滤条件添加到 CafeMapFilter.vue 中,紧随上一步的文本搜索框之后:

<div class="is-roaster-container">
    <input type="checkbox" v-model="isRoaster"/> <label>烘焙店</label>
</div>

同样,在 data 返回数据模型中添加 isRoaster 属性,默认值为 false

data() {
   return {
       textSearch: '',
       isRoaster: false
   }
}

第四步:添加冲泡方法过滤器到组件

再然后是冲泡方法过滤器,我们现在 data 返回数据模型中添加相应的 brewMethods 属性,并将其初始化为空数组:

data() {
  return {
      textSearch: '',
      isRoaster: false,
      brewMethods: []
  }
},

然后通过计算属性 cafeBrewMethods 从 Vuex 中获取全局支持的所有冲泡方法,以便作为筛选条件:

computed: {
   cafeBrewMethods() {
       return this.$store.getters.getBrewMethods;
   },
},

在模板代码中紧随是否是烘焙店勾选框之后添加如下冲泡方法选择组件:

<div class="brew-methods-container">
  <div class="filter-brew-method" v-on:click="toggleBrewMethodFilter( method.method )" v-bind:class="{'active' : brewMethods.indexOf( method.method ) > -1 }" v-for="method in cafeBrewMethods">
    {{ method.method }}
  </div>
</div>

methods 方法中定义冲泡方法选中/取消选中处理函数 toggleBrewMethodFilter

methods: {
   toggleBrewMethodFilter(method) {
       if (this.brewMethods.indexOf(method) > -1) {
           this.brewMethods.splice(this.brewMethods.indexOf(method), 1);
       } else {
           this.brewMethods.push(method);
       }
   },
}

最后在 style 中添加冲泡方法选择元素样式:

div.filter-brew-method{
  display: inline-block;
  height: 30px;
  text-align: center;
  border: 1px solid #ededed;
  border-radius: 5px;
  padding-left: 10px;
  padding-right: 10px;
  padding-top: 5px;
  padding-bottom: 5px;
  margin-right: 10px;
  margin-top: 10px;
  cursor: pointer;
  color: #7F5F2A;
  font-family: 'Josefin Sans', sans-serif;
  font-size: 12px;

  &.active{
    border-bottom: 4px solid $primary-color;
  }
}

第五步:完成地图过滤功能

最后,我们还要在 CafeMapFilter.vue 中引入事件总线,以便监听并触发过滤条件变更事件:

import { EventBus } from '../../event-bus.js';

通过 watch 监听过滤条件字段的变动并进行处理:

watch: {
   textSearch() {
       this.updateFilterDisplay();
   },

   isRoaster() {
       this.updateFilterDisplay();
   },

   brewMethods() {
       this.updateFilterDisplay();
   }
},

最后在 methods 中定义过滤条件变更处理函数 updateFilterDisplay

updateFilterDisplay(){
  EventBus.$emit('filters-updated', {
    text: this.textSearch,
    tags: [],
    roaster: this.isRoaster,
    brew_methods: this.brewMethods
  });
}

过滤条件变更后触发全局事件 filters-updated,这样监听该事件的代码就会执行相应的回调函数进行处理。这里,需要注意的是,我们在设置事件属性时传入了 tags 属性,以免我们在上一篇教程中定义的监听代码在执行回调函数的时候会报错。

以上 CafeMapFilter.vue 组件的实现思路和 CafeFilter.vue 完全一致,不做过多赘述。接下来我们在 CafeMap.vue 中实现在高德地图上根据过滤组件中的过滤条件对咖啡店点标记进行筛选过滤的功能。

第六步:在 CafeMap 中监听过滤条件变更

CafeMap.vue 中引入事件总线,以便对过滤条件变更进行监听和处理:

import { EventBus } from '../../event-bus.js';

我们在 mounted 中定义监听代码:

mounted() {
   this.map = new AMap.Map('cafe-map', {
       center: [this.latitude, this.longitude],
       zoom: this.zoom
   });
   this.clearMarkers();
   this.buildMarkers();

   // 监听 filters-updated 事件过滤点标记
   EventBus.$on('filters-updated', function (filters) {
       this.processFilters(filters);
   }.bind(this));
},

当过滤条件变更时,会调用 processFilters 方法对地图上的咖啡店点标记进行过滤。

第八步:实现 processFilters 方法

最后,我们需要在 CafeMap.vuemethods 中实现 processFilters 方法。在此之前,需要先引入上篇教程创建的若干过滤处理函数:

import { CafeIsRoasterFilter } from '../../mixins/filters/CafeIsRoasterFilter.js';
import { CafeBrewMethodsFilter } from '../../mixins/filters/CafeBrewMethodsFilter.js';
import { CafeTextFilter } from '../../mixins/filters/CafeTextFilter.js';

并定义 mixins 数组:

mixins: [
   CafeIsRoasterFilter,
   CafeBrewMethodsFilter,
   CafeTextFilter
],

buildMarkers 方法中新建点标记 marker 对象时传入额外的咖啡店模型数据以便在点标记过滤时从点标记对象中获取相应的咖啡店数据,进而实现过滤功能:

// 遍历所有咖啡店创建点标记
for (var i = 0; i < this.cafes.length; i++) {

     // 为每个咖啡店创建点标记并设置经纬度
     var marker = new AMap.Marker({
         position: new AMap.LngLat(parseFloat(this.cafes[i].latitude), parseFloat(this.cafes[i].longitude)),
         title: this.cafes[i].location_name,
         icon: icon,
         extData: { 
             'cafe': this.cafes[i]
         }
     });

     ...

做好以上准备工作后,就可以编写 processFilters 方法了:

processFilters(filters) {
  for (var i = 0; i < this.markers.length; i++) {
      if (filters.text === ''
          && filters.roaster === false
          && filters.brew_methods.length === 0) {
          this.markers[i].setMap(this.map);
      } else {
          var textPassed = false;
          var brewMethodsPassed = false;
          var roasterPassed = false;

          if (filters.roaster && this.processCafeIsRoasterFilter(this.markers[i].getExtData().cafe)) {
              roasterPassed = true;
          } else if (!filters.roaster) {
              roasterPassed = true;
          }

          if (filters.text !== '' && this.processCafeTextFilter(this.markers[i].getExtData().cafe, filters.text)) {
              textPassed = true;
          } else if (filters.text === '') {
              textPassed = true;
          }

          if (filters.brew_methods.length !== 0 && this.processCafeBrewMethodsFilter(this.markers[i].getExtData().cafe, filters.brew_methods)) {
              brewMethodsPassed = true;
          } else if (filters.brew_methods.length === 0) {
              brewMethodsPassed = true;
          }

          if (roasterPassed && textPassed && brewMethodsPassed) {
              this.markers[i].setMap(this.map);
          } else {
              this.markers[i].setMap(null);
          }
      }
  }
},

至此,我们已经完成了所有编码工作。运行 npm run dev 重新编译前端资源,访问咖啡店列表页 http://roast.test/#/cafes,页面显示如下:

选择适当的过滤条件,即可在地图上对咖啡店点标记进行过滤筛选:

注:项目的完整代码位于 nonfu/roastapp

学院君 has written 1269 articles

Laravel学院院长,终身学习者

积分:177167 等级:P12 职业:手艺人 城市:杭州

0 条回复

登录后才能进行评论,立即登录?