马蜂窝旅游小程序

栏目: IOS · Android · 发布时间: 5年前

内容简介:前段时间学习了关于微信小程序的开发,光说不练假把式,所以就打算自己手撸一个微信小程序,而网上电商类小程序太多了,所以就选择了旅游攻略类小程序来练手。这是我第一次写小程序和第一次写文章,不足之处请多包涵,谢谢。下面我会分享我在写小程序的时候遇到的问题和获得的经验,希望能给你带来帮助,也欢迎大佬指正。最后,我要感谢我在写小程序的时候给我帮助的老师和同学,还有百度上所有给过我帮助的有名的无名的作者。我的废话说完了,先上项目效果图。(链接中详细介绍了关于自定义组件的使用方法)必须把app.json中window属性

前段时间学习了关于微信小程序的开发,光说不练假把式,所以就打算自己手撸一个微信小程序,而网上电商类小程序太多了,所以就选择了旅游攻略类小程序来练手。这是我第一次写小程序和第一次写文章,不足之处请多包涵,谢谢。下面我会分享我在写小程序的时候遇到的问题和获得的经验,希望能给你带来帮助,也欢迎大佬指正。最后,我要感谢我在写小程序的时候给我帮助的老师和同学,还有百度上所有给过我帮助的有名的无名的作者。我的废话说完了,先上项目效果图。

马蜂窝旅游小程序

开发前的准备

项目的所有页面

自定义顶部导航栏组件

马蜂窝旅游小程序
马蜂窝旅游小程序
马蜂窝旅游小程序
微信小程序自带的顶部导航栏 满足不了实际的需求所以就自己写了一个组件,顶部导航栏要想使用 自定义组件

(链接中详细介绍了关于自定义组件的使用方法)必须把app.json中window属性设置为:

"window": {
    "navigationBarTextStyle": "black",//导航栏标题颜色,仅支持 black / white
    "navigationStyle": "custom" //导航栏样式,仅支持以下值:default 默认样式custom 自定义导航栏,只保留右上角胶囊按钮
  }
复制代码

wxml

<view class='nav-wrap' style='height: {{height*2 + 20}}px; background-color:{{navbarData.backgroundColor}};opacity:{{navbarData.opacity}}'>
  <view style="width:100%;height:100%;">
    <!--城市名-->
    <navigator url="/pages/destination/destination" hover-class="none">
      <view class="nav-city" style='margin-top:{{height*2 + 20-36}}px;' wx:if='{{navbarData.showMain}}'>
        <text>{{navbarData.cityName}}</text>
        <view class="downtips"></view>
      </view>
    </navigator>
    <navigator url="/pages/search/search" hover-class="none">
    <!--搜索框-->
    <view class="section" style='top:{{height*2 + 20-34}}px;' wx:if='{{navbarData.showMain}}'>
      // 这里的搜索框不是一个input组件,只是一个view可供点击然后跳到搜索页
      <view class='search_icon'>
        <icon type='search' size='14px'></icon>
      </view>
      <view class='placehold'>搜索目的地/景点/攻略</view>
    </view>
    </navigator>
  </view>
  <!-- 标题 -->
  <view wx:if="{{navbarData.title!=''}}" class='nav-title' style='line-height: {{height*2 + 44}}px;'>
    {{navbarData.title}}
  </view>
  <!-- 返回上一级按钮 和 返回主页按钮-->
  <block wx:if="{{navbarData.showCapsule===1}}">
    <view class='nav'>
      <view class='nav_back' bindtap="_navback">
        <image src='/images/back.png'></image>
      </view>
      <view class="line"></view>
        <view class='nav_home' bindtap="_backhome">
          <image src='/images/home.png'></image>
        </view>
    </view>
  </block>
</view>
复制代码

组件中的元素都可以通过当前页面传入组件的数据控制显示与否

js就写了两个路由跳转 函数,微信小程序官方文档有很详细的介绍,这里就不多赘述了。

登录界面

马蜂窝旅游小程序
初进小程序,会跳到登录授权页面,因为微信小程序不再支持 wx.getUserInfo

接口直接弹出授权框的开发方式,所以这里直接使用 button 组件,并将 open-type 指定为 getUserInfo 类型,获取用户基本信息。

<button style='background:green; color:#fff' open-type="getUserInfo" bindgetuserinfo="bindGetUserInfo">同意授权</button
复制代码

小程序在授权允许访问用户信息后,又会弹出位置授权框用来获取用户当前所在地,来渲染主页面的数据。调用小程序给的接口wx.getLocation(需要用户授权) 来获取经纬度,再把获取到的经纬度利用百度地图开放平台 提供给小程序使用的API来获取当前城市的名字,并将城市名字放入缓存,好让主页面获取到。

##注意: 使用wx.getLocation()需要在app.json中配置

"permission": {
    "scope.userLocation": {
      "desc": "小程序将获取你的位置信息"
    }
  }
复制代码

登录界面js

// miniprogram/pages/login/login.js
const app = getApp()
Page({

  /**
   * 页面的初始数据
   */
  data: {
    show: false,
    // 顶部导航栏数据
    navbarData: {
      showCapsule: 0, //是否显示左上角图标   1表示显示    0表示不显示
      title: '马蜂窝旅游', //导航栏 中间的标题
      backgroundColor: '#354a98', //'#354a98'
      opacity: 1,
      showMain: 0,
    },
    // 此页面 页面内容距最顶部的距离
    height: app.globalData.height * 2 + 20,
  },
  bindGetUserInfo(res) {
    let that =this
    let info = res;
    if (info.detail.userInfo) {
      wx.login({
        success: function (res) {
          that.getPlaceData()
        }
      })
    }
  },
  /**
   * 生命周期函数--监听页面加载
   */
  onLoad: function (options) {
    let that = this;
    //页面加载时判断用户是否授权过,如果授权过直接跳到主页面,没有就显示授权按钮
    wx.getUserInfo({
      success: function (res) {
        wx.switchTab({
          url: '/pages/main/index'
        })
      },
      fail(err) {
        that.setData({
          show: true
        })
      }
    })
  },
  // 获取城市名字
  getCityName(location) {
    return new Promise((resolve, reject) => {
      let that = this;
      var e = {
        coord_type: "gcj02",
        output: "json",
        pois: 0,
        ak: '',//放上自己的ak密钥 密钥申请见上文百度地图开方平台链接
        sn: "",
        timestamp: ""
      };
      e.location = location;
      wx.request({
        url: "https://api.map.baidu.com/geocoder/v2/",
        data: e,
        header: {
          "content-type": "application/json"
        },
        method: "GET",
        success: function (t) {
          let currentCity = t.data.result.addressComponent.city;
          if (currentCity.slice(currentCity.length - 1) == "市") {
            currentCity = currentCity.slice(0, currentCity.length - 1)
          }
          wx.setStorageSync('currentCity', currentCity)
          resolve(currentCity) //通过城市名字 请求城市数据
        }
      })
    })
  },
  // 获取经纬度
  getLocation() {
    return new Promise((resolve, reject) => {
      wx.getLocation({
        type: 'wgs84',
        success(res) {
          const latitude = res.latitude
          const longitude = res.longitude
          let location = latitude + ',' + longitude
          console.log(location)
          resolve(location) //获取城市名字
        }
      })
    })
  },
  getPlaceData() { // 获取地理信息
    let that = this
    this.getLocation().then((val) => {
      return that.getCityName(val)
    }).then(()=>{
      wx.switchTab({
        url: '/pages/main/index'
      })
    })
  }
})
复制代码

主页面

写小程序的时候我不知道主页面有两种样式,等我知道的时候已经写了不少东西了,所以就没有写成组件了,代码看起来就很冗长,这是我的失误(MangFu),希望你在想好写什么小程序的时候,一定要把小程序的页面结构想好来否则就会和我一样,要改的话就要改很多地方。

  • 普通城市页面
马蜂窝旅游小程序
  • 热门城市页面
马蜂窝旅游小程序

进入主页是,页面会先获取到缓存中的城市名字,再通过城市名字去请求数据,再根据请求到的数据中的ishot属性,如果ishot属性为真,就显示热门城市的页面 ,反之就显示普通城市的页面

‘我的’页面

马蜂窝旅游小程序

‘我的’页面中主要是为了显示用户收藏的内容

景点详情页

马蜂窝旅游小程序

因为种种原因(lan)页面中的大半数据没有放到Easy Mock里,马蜂窝本来就以大数据出名,数据ttm多了。

洲/国家/城市列表页

马蜂窝旅游小程序

这个页面的布局分为三部分,头部搜索框用绝对定位定死、左部各大洲的列表用绝对定位定死,右部各大洲的国家是一个微信小程序自带的组件scroll-view

wxml

<!-- pages/destination/destination.wxml -->
<nav-bar navbar-data='{{navbarData}}'></nav-bar>
<view class="destination" style='top: {{height}}px'>
<!--头部-->
  <view class="des_head">
  <navigator url="/pages/search/search" hover-class="none">
    <view class="des_search">
      <view class="des_search_icon">
        <icon type='search' size='30rpx' color="#000000"></icon>
      </view>
      搜索目的地
    </view>
  </navigator>
  </view>
  <!--左部-->
  <view class="des_continents">
    <view class="des_continent {{curIndex===index?'add':''}}}" wx:for="{{continents}}" wx:for-item="continent" wx:key='{{index}}' data-index='{{index}}' bindtap="switch_des">
      <view class='des_continent_name {{curIndex===index?"on":""}}}'>{{continent.name}}</view>
    </view>
  </view>
  <!--右部-->
  <scroll-view class='des_cities' scroll-y>
    <block wx:if="{{curIndex==0}}">
      <view class="des_cities_content" wx:for="{{continents[curIndex].cities}}" wx:key="{{index}}" wx:for-item="des_city">
        <view class="des_cities_title">{{des_city.title}}</view>
        <view class="des_city" wx:for="{{des_city.city}}" wx:key="{{index}}" bindtap='goMain' data-city_name="{{item.city_name}}">
          {{item.city_name}}
        </view>
      </view>
    </block>
    <block wx:else>
      <view class="des_area" wx:for="{{continents[curIndex].cities}}" wx:key="{{index}}" wx:for-item="des_city" bindtap='goMain' data-city_name="{{des_city.city_name}}">
          <view class="des_img">
            <image src="{{des_city.img}}" />
          </view>
          <view class="des_city_name">{{des_city.city_name}}</view>
        </view>
    </block>
  </scroll-view>
</view>

复制代码

js

// pages/destination/destination.js
const app = getApp()
Page({

  /**
   * 页面的初始数据
   */
  data: {
  <!--顶部导航栏数据-->
    navbarData: {
      showCapsule: 1, //是否显示左上角图标   1表示显示    0表示不显示
      title: '目的地切换', //导航栏 中间的标题
      backgroundColor: '#fff',//背景颜色
      showMain: 0 ///显示搜索框
    },
    height: app.globalData.height * 2 + 20,
    continents: [],
    curIndex: 0 //当前洲的索引值
  },
  <!--左部各大洲的点击事件,来改变右边显示的内容,并且改变自身样式-->
  switch_des(e) {
    let curIndex = e.currentTarget.dataset.index;
    this.setData({
      curIndex,
    })
  },
  <!--右部国家/城市的点击事件,获取点击的元素上绑定的国家/城市的名字,放入缓存,并跳转到主页-->
  goMain(e){
    const city_name = e.currentTarget.dataset.city_name;
    wx.setStorageSync('currentCity', city_name)
    wx.switchTab({
      url: '/pages/main/index'
    })
  },
  /**
   * 生命周期函数--监听页面加载
   */
  onLoad: function (options) {
    let that = this
    <!--请求数据-->
    wx.request({
      url: 'https://www.easy-mock.com/mock/5ca457f04767c3737055c868/example/mafengwo/continents',
      success:(res)=>{
        that.setData({
          continents: res.data.continents
        })
      }
    })
  }
}
复制代码

搜索页

马蜂窝旅游小程序

实现的功能

点击切换列表

以主页为例

马蜂窝旅游小程序

其实所有的切换列表功能都差不多,实现方法就是在被点击元素上设置一个自定义属性 ( data-* ) 为唯一索引值,用bind-tap 绑定一个点击事件,通过点击事件获取这个唯一索引值,再通过唯一索引值去数据源找到想要的内容,然后通过数据控制页面上显示的内容,在data数据源中设置一个数据如mcurIndex,表示当前选择的元素,用来区别于其他元素,显示不同的样式。

wxml

<view class='menu_list'>
    <!-- {{mcurIndex===index?"on":""}} 表示如果自身的索引值为当前选择的元素索引值时,添加一个类名‘on’-->
    <view class='list {{mcurIndex===index?"on":""}}' wx:for="{{placeData.allGuide}}" data-mindex="{{index}}" bindtap='selected_menu' wx:key="{{index}}">
        {{item.name}}
    </view>
</view>
复制代码

js

selected_menu(e) {
    this.setData({
      mcurIndex: e.target.dataset.mindex,
      size: 0,
      showend: false
    })
    <!--调用自己写的函数来获取要显示的内容的数据-->
    this.bitiyan()
  }
复制代码

滑动页面改变顶部导航栏的可见度和上拉加载

  • 滑动页面改变顶部导航栏的可见度

    以主页为例

马蜂窝旅游小程序
这里的实现方法是使用 scroll-view

组件,组件中有个 bindscroll 属性,会在页面滚动时触发bindscroll 绑定的事件还会给函数传递一个对象event,其中的scrollTop属性是我们需要的,根据scrollTop知道页面滚动了多少,然后动态设置要传给组件的数据里的 opacity 属性。

<scroll-view class="main_scro" scroll-y bindscroll="scroll" bindscrolltolower="bindDownLoad">
</scroll-view>
复制代码

js

scroll(e) {
    let opacity = 0;
    if (e.detail.scrollTop < 60) {
      opacity = (e.detail.scrollTop / 100).toFixed(1);
    } else {
      opacity = 1;
    }
    this.data.navbarData.opacity = opacity;
    if (e.detail.scrollTop<10){
      this.setData({
        shownav: false
      })
    }else{
      this.setData({
        shownav: true
      })
    }
    this.setData({
      navbarData: this.data.navbarData,
    })
  }
复制代码
  • 上拉加载

    以主页为例

马蜂窝旅游小程序

这里的实现方法在scroll-view 组件中加 bindscrolltolower 属性,会在页面触底时触发bindscrolltolower 绑定的事件。

<scroll-view class="main_scro" scroll-y bindscroll="scroll" bindscrolltolower="bindDownLoad">
</scroll-view>
复制代码
bindDownLoad() {
    let part = 0; //已经显示的数据长度
    let all = 0; //总的数据长度
    <!--判断当前城市是否为热门城市-->
    if (this.data.ishot) {
      // 待完善 因为效果相同就没写了
    } else {
      if (this.data.mcurIndex === 0) {
        part = this.data.cur_view.length * 2;
        all = this.data.placeData.allGuide[this.data.mcurIndex].content[this.data.hlcurIndex].content.length;
      } else {
        part = this.data.cur_view.length;
        all = this.data.placeData.allGuide[this.data.mcurIndex].content.length;
      }

      if (part < all) {
        wx.showLoading({
          title: '正在加载'
        })
        setTimeout(() => {
          this.bitiyan(this.data.placeData)
          wx.hideLoading()
        }, 1000)
      } else {
        <!--当所有数据都加载完了,就显示end 图标-->
        this.setData({
          showend: true
        })
      }
    }
  }
复制代码

关于scroll-view组件有几点需要注意的

  • 设置竖向滚动的时后一定要设高度,有时候会发现设置了高度100%后,当滑到底部的时候,会显示不完整,这时候要看下你是否设置了margin/padding,或者父元素设置了margin/padding,这时的scroll-view组件的高度就要减去相应的margin/padding
  • 当设置为横向滚动时需要注意,scroll-view 中需要滑动的元素不可以用 float 浮动;scroll-view 中的包裹需要滑动的元素的大盒子用 display:flex 是没有作用的;scroll-view 中的需要滑动的元素要用 dislay:inline-block 进行元素的横向编排;包裹 scroll-view 的大盒子有明确的宽和加上样式--> overflow:hidden;white-space:nowrap;

收藏功能

收藏功能我是写在一个组件里,本来是想和顶部组件一样,供多个页面使用,后来因为写的页面中就只有一个有用到这个组件,这里就不单独说明这个组件了,而且这个组件和顶部组件基本差不多。

马蜂窝旅游小程序

收藏功能的实现,当点击某一个景点时会触发点击事件,相信你看了列表切换功能,已经知道了bind-tap 的使用方法,这里就不重复了。这里就是获取元素上的自定义属性,通过路由传参的方法传给详情页,详情页根据传递过来的数据,去数据源里获取相应的数据,再将数据传递给组件,当点击详情页上的收藏按钮时,会触发绑定的事件,然后会更新缓存中的collectData收藏夹数据。‘我的’页面会显示收藏夹中的数据

详情页js

<!--生命周期函数,监听页面加载-->
onLoad: function(options) {
    <!--options中包含了传递过来的参数-->
    let name = options.name;
    this.getinfo(name)
},
<!--通过名字获取想要的数据-->
getinfo(name){
    <!--先获取缓存中已经存在的收藏夹数据,如果不存在就将collectData设为空数组-->
    let collectData = wx.getStorageSync('collectData') || [];
    if (collectData.filter(e => e.name === name).length > 0) {
      this.setData({
        placeData: collectData.filter(e => e.name === name)[0]
      })
    } else {
      let placeData = wx.getStorageSync('placeData')
      let view = placeData.allGuide[0].content.map(e => e.content)
      let newView = []
      for (let i = 0; i < view.length; i++) {
        newView.push(...view[i])
      }
      this.setData({
        placeData: newView.find(e => e.name === name)
      })
    }
    this.setBottom();
  },
  <!--设置要传递给bottom组件的数据-->
  setBottom(){
    this.data.bottomData.placeData = this.data.placeData;
    let bottomData = this.data.bottomData;
    this.setData({
      bottomData
    })
  }
复制代码

bottom组件的js

// components/bottom/bottom.js
const app = getApp()
Component({
  /**
   * 组件的属性列表
   */
  properties: {
    bottomData: {   //   由父页面传递的数据,变量名字自命名
      type: Object,
      value: {},
      observer: function (newVal, oldVal) {
       }
    }
  },

  /**
   * 组件的初始数据
   */
  data: {
    height: ''
  },
  attached: function () {
    // 获取是否是通过分享进入的小程序
    this.setData({
      share: app.globalData.share
    })
    // 定义导航栏的高度   方便对齐
    this.setData({
      height: app.globalData.height
    })
  },
  /**
   * 组件的方法列表
   */
  methods: {
    <!--点击收藏按钮触发的事件-->
    collected(){
      <!--将isCollect(是否收藏过),collectors(收藏人数)从数据中解构出来-->
      let {isCollect,collectors} = this.data.bottomData.placeData;
      isCollect = !isCollect;
      this.data.bottomData.placeData.isCollect = isCollect;
      let collectData = wx.getStorageSync('collectData') || [];
      if(isCollect){
        wx.showToast({
          title: '收藏成功',
          icon: 'success',
          duration: 2000
        })
        collectors++;
        collectData.push(this.data.bottomData.placeData);
      }else{
        wx.showToast({
          title: '已取消收藏',
          icon: 'success',
          duration: 2000
        })
        collectors--;
        collectData = collectData.filter(e => e.name != this.data.bottomData.placeData.name)
      }
      this.data.bottomData.placeData.collectors = collectors;
      <!--将收藏夹数据放入缓存-->
      wx.setStorageSync('collectData', collectData)
      let bottomData = this.data.bottomData;
      this.setData({
        bottomData
      })
    }
  }
})

复制代码

搜索功能

效果一

马蜂窝旅游小程序

效果二

马蜂窝旅游小程序

搜索功能的实现是通过原生组件input 上的bindinput属性,当键盘输入时触发bindinput属性绑定的方法,实时获取中输入的值,然后将获取到的值放入请求地址中请求数据,再将请求获得的数据放入页面的data数据源中,当请求到的数据不为空时,页面上会显示得到的所有相关数据,如效果一。当按下搜索按钮时会触发input框上bindconfirm属性绑定的事件,此时页面上会显示请求到的数据中的第一条,如效果二。

wxml

<input style='width:500rpx' bindconfirm='confirm' confirm-type='search' focus='true' placeholder="搜索目的地/景点/攻略" bindinput='search'></input>
复制代码

js

// pages/search/search.js
const app = getApp()
Page({

  /**
   * 页面的初始数据
   */
  data: {
    navbarData: {
      showCapsule: 1, //是否显示左上角图标   1表示显示    0表示不显示
      title: '马蜂窝旅游', //导航栏 中间的标题
      backgroundColor: '#ffffff', //'#354a98'
      city: '',
      opacity: 1,
      showMain: 0
    },
    height: app.globalData.height * 2 + 20,
    result: [],
    searchparams: '',
    show: true,
    searchHistory: [],
    showResult: false,
    showconfirm: false,
    placedata: []
  },
  <!--清空历史纪录-->
  clear() {
    this.setData({
      searchHistory: []
    })
    wx.removeStorageSync('searchHistory')
  },
  <!--当点击键盘上搜索按钮触发的事件-->
  confirm(e) {
    if (e.detail.value != '') {
      let searchHistory = wx.getStorageSync('searchHistory') || []
      if (searchHistory.filter(a => a === e.detail.value).length === 0) {
        searchHistory.push(e.detail.value)
        wx.setStorageSync('searchHistory', searchHistory)
      }
      if (this.data.result.length > 0) {
        let currentCity = this.data.result[0].name;
        this.getCityDataByName(currentCity);
      }
      this.setData({
        show: false,
        showResult: false,
        showconfirm: true
      })
    }
  },
  <!--跳到主页面-->
  gotomain(e) {
    wx.setStorageSync('currentCity', e.currentTarget.dataset.name)
    wx.switchTab({
      url: '/pages/main/index',
    })
  },
  <!--点击历史纪录触发的事件,效果和confirm方法基本相同,不同的是confirm是从页面data中获取数据,而dosearch是从接口中获取数据-->
  gosearch(e) {
    let that = this
    wx.request({
      url: `https://www.easy-mock.com/mock/5ca457f04767c3737055c868/example/mafengwo/search?name=${e.currentTarget.dataset.name}`,
      success: (res) => {
        if (res.data.data.length > 0) {
          that.getCityDataByName(res.data.data[0].name)
        } else {
          this.setData({
            show: false,
            showResult: false,
            showconfirm: true
          })
        }
      }
    })

  },
  // 通过城市名字 获取城市数据
  getCityDataByName(cityname) {
    let that = this
    wx.request({
      url: 'https://www.easy-mock.com/mock/5ca457f04767c3737055c868/example/mafengwo/china',
      success: (res) => {
        let placedata = [];
        placedata.push(...res.data.data.china.filter(e => e.chName === cityname))
        that.setData({
          placedata,
          show: false,
          showResult: false,
          showconfirm: true
        })
      }
    })
  },
  <!--当键盘输入时触发的事件-->
  search(e) {
    let that = this
    wx.request({
      url: `https://www.easy-mock.com/mock/5ca457f04767c3737055c868/example/mafengwo/search?name=${e.detail.value}`,
      success: (res) => {
        if (res.data.data.length > 0) {
          that.changecolor(res.data.data, e.detail.value)
        } else {
          that.setData({
            result: [],
            searchparams: '',
            showResult: false
          })
        }
      }
    })
  },
  <!--改变名字颜色-->
  changecolor(result, searchparams) {
    for (let j = 0; j < result.length; j++) {
      let i = result[j].name.search(searchparams);
      let left = result[j].name.slice(0, i),
        mid = result[j].name.slice(i, i + searchparams.length),
        right = result[j].name.slice(i + searchparams.length);
      result[j].left = left;
      result[j].mid = mid;
      result[j].right = right;
    }
    this.setData({
      result,
      searchparams,
      show: false,
      showResult: true,
      showconfirm: false
    })
  },
  _navback() {
    wx.navigateBack({
      delta: 1
    })
  },
  /**
   * 生命周期函数--监听页面加载
   */
  onLoad: function() {
    <!--获取缓存中的搜索历史并放入数据源-->
    let searchHistory = wx.getStorageSync('searchHistory') || []
    this.setData({
      searchHistory
    })
  }
复制代码

这个API接口是我用Easy Mock写的

Easy Mock地址链接

Easy Mock 代码

{
  "data": function({
    _req
  }) {
    let i = 0,
    <!--数据源_data由于篇幅原因就放了一小段数据-->
      _data = [
        {
            name: '亚洲',
            type: '目的地'
          },
          {
            name: '欧洲',
            type: '目的地'
          },
          {
            name: '大洋洲',
            type: '目的地'
          },
          {
            name: '非洲',
            type: '目的地'
          },
          {
            name: '北美洲',
            type: '目的地'
          },
          {
            name: '南美洲',
            type: '目的地'
          },
          {
            name: '南极洲',
            type: '目的地'
          }
      ],
      <!--_req是easymock封装的对象,_req.query(将查询参数字符串进行解析并以对象的形式返回,如果没有查询参数字字符串则返回一个空对象);-->
      name = _req.query.name;
    if (name != '') {
    <!--当输入的值不为空时-->
      let result = [];
      let data = []
      for (let j = 0; j < result.length; j++) {
      <!--eval() 函数可计算某个字符串,并执行其中的的 JavaScript 代码。这里主要是为了给正则表达式动态传参-->
        if (eval('/' + name + '/').test(result[j].name)) {
          data.push(result[j])
        }
        <!--当查询到8个匹配项时跳出循环-->
        if (data.length > 8) break;
      }
      return data
    } else {
    <!--当输入的值为空时直接返回空数组-->
      return []
    }
  }
}
复制代码

热门城市动画

马蜂窝旅游小程序

因为动画只有6个元素,所以就没有必要写成数组遍历创建了,直接写6个盒子,给他们的样式初始化,让他们到自己的初始位置去。微信小程序提供了创建动画实例的API wx.createAnimation

wxml

<view class='video a' animation="{{animation1}}" data-index='0' bindtap="_play">
  <view class='context'>
    <text>{{placeData.vlog[0].title}}</text>
  </view>
  <view class='vdoIcon'>
    <image src='/images/play.png'></image>
  </view>
</view>
<view class='video b' animation="{{animation2}}" data-index='1' bindtap="_play">
  <view class='context'>
    <text>{{placeData.vlog[1].title}}</text>
  </view>
  <view class='vdoIcon'>
    <image src='/images/play.png'></image>
  </view>
</view>
<view class='video c' animation="{{animation3}}" data-index='2' bindtap="_play">
  <view class='context'>
    <text>{{placeData.vlog[2].title}}</text>
  </view>
  <view class='vdoIcon'>
    <image src='/images/play.png'></image>
  </view>
</view>
<view class='video d' animation="{{animation4}}" data-index='3' bindtap="_play">
  <view class='context'>
    <text>{{placeData.vlog[3].title}}</text>
  </view>
  <view class='vdoIcon'>
    <image src='/images/play.png'></image>
  </view>
</view>
<view class='video e' animation="{{animation5}}" data-index='4' bindtap="_play">
  <view class='context'>
    <text>{{placeData.vlog[4].title}}</text>
  </view>
  <view class='vdoIcon'>
    <image src='/images/play.png'></image>
  </view>
</view>
<view class='video f' animation="{{animation6}}" data-index='5' bindtap="_play">
  <view class='context'>
    <text>{{placeData.vlog[5].title}}</text>
  </view>
  <view class='vdoIcon'>
    <image src='/images/play.png'></image>
  </view>
</view>
复制代码

wxss

.a{
  opacity: 0.9;
}
.b{
  transform: translate(170rpx,-110rpx) scale(0.8);
  opacity: 0.8;
}
.c{
  transform: translate(210rpx,-250rpx) scale(0.7);
  opacity: 0.7;
}
.d{
  transform: translate(10rpx,-350rpx) scale(0.6);
  opacity: 0.6;
}
.e{
  transform: translate(-250rpx,-290rpx) scale(0.8);
  opacity: 0.5;
}
.f{
  transform: translate(-300rpx,-130rpx) scale(0.9);
  opacity: 0.8;
}
复制代码

js

// 动画的运行路线
  translate: function(i) {
    // 获取屏幕宽度来实现自适应
    let windowwidth = this.data.windowWidth;
    //动画的运行状态status[x轴偏移量,y轴偏移量,scale缩放倍数,opacity透明度],也是动画的运行路线
    let status = [
      [170, -110, 0.8, 0.7],
      [210, -250, 0.7, 0.6],
      [10, -350, 0.6, 0.5],
      [-250, -300, 0.8, 0.7],
      [-300, -130, 0.9, 0.8],
      [0, 0, 1, 0.9]
    ];
    let x = 0,
      y = 0,
      scale = 0,
      opacity = 0;
    for (let j = 0; j < 6; j++) {
      let animationName = 'animation' + (j + 1);
      x = status[(i + j) % 6][0] / 750 * windowwidth;
      y = status[(i + j) % 6][1] / 750 * windowwidth;
      scale = status[(i + j) % 6][2];
      opacity = status[(i + j) % 6][3];
      this.animation.translate(x, y).scale(scale).opacity(opacity).step()
      this.setData({
        [animationName]: this.animation.export()//导出动画数据传递给组件的 animation 属性
      })
    }
  },
  hotCityAnimation() {
    let i = 0;
    <!--创建动画实例-->
    this.animation = wx.createAnimation({
      duration: 2000,
      timingFunction: 'ease',
    })
    let that = this
    let anicontrol = this.data.anicontrol
    anicontrol = setInterval(function() {
      that.translate(i)
      if (i == 5) {
        i = -1;
      }
      i++;
    }, 3000)
    this.setData({
      anicontrol
    })
  }
复制代码

这里要注意的是,因为这是写在tabbar页面的动画,而且用了setinterval定时器,会按照指定的周期(以毫秒计)来执行注册的回调函数,意思就是即使你跳转到别的页面,动画依然在运行,当你回到主页时,动画就会运行出错,出现鬼畜,所以要在主页的onHide周期函数,监听页面隐藏时就把定时器给清除了,并且把动画实例也清除。

onHide: function() {
    let anicontrol = this.data.anicontrol;
    clearInterval(anicontrol)
    this.setData({
      animation1: '',
      animation2: '',
      animation3: '',
      animation4: '',
      animation5: '',
      animation6: ''
    })
  }
复制代码

关于css

写这个小程序我没有用到任何UI框架,这有坏处,也有好处,坏处就是代码进度贼慢,好处就是自己增加了很多对css的理解。有想用UI框架的可以使用 WeUI 。链接里有详细的使用方法。

结语

因为时间和精力的缘故,小程序只写了几个页面和小部分功能,在写项目的过程中也发现了自己的很多不足,因此吃到了不少苦头,但是也学到了不少,可以说痛并快乐着。希望这篇文章能够对打算写小程序的你有一点帮助。 GitHub源码 在这里,需要自取。


以上所述就是小编给大家介绍的《马蜂窝旅游小程序》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Persuasive Technology

Persuasive Technology

B.J. Fogg / Morgan Kaufmann / 2002-12 / USD 39.95

Can computers change what you think and do? Can they motivate you to stop smoking, persuade you to buy insurance, or convince you to join the Army? "Yes, they can," says Dr. B.J. Fogg, directo......一起来看看 《Persuasive Technology》 这本书的介绍吧!

图片转BASE64编码
图片转BASE64编码

在线图片转Base64编码工具

HEX HSV 转换工具
HEX HSV 转换工具

HEX HSV 互换工具