Апплет Didi для путешествий на основе mpvue

внешний интерфейс Апплет WeChat Vuex mpvue

первоначальное намерение

В последнее время я изучал небольшие программы и случайно обнаружил фреймворк mpvue, мне очень понравился Vue, поэтому я решил войти в яму. Однажды я увидел старомодный апплет Didi в Nuggets, и он показался мне довольно хорошим, но он был основан на нативном апплете, поэтому я решил потратить некоторое время и переписать его с помощью mpvue. Теперь начинается ужин.


визуализация

Для получения дополнительных эффектов, пожалуйста, проверьтеGitHub.com/Q в Z равен 001/...



Структура каталогов

┣━ api # 存放网络请求相关
┣━ common ●                
          ┣━ constant               //常量
          ┣━ css                    //weui.css
          ┣━ less                   //通用less样式与变量
          ┗━ lib                     //第三方库 qqmap-wx-jssdk.js
┣━ components  ● 抽取出来的组件
               ┣━ addressList.vue            
               ┣━ common-footer.vue                
               ┣━ driver-header.vue           
               ┣━ loading-sprinner.vue        
               ┣━ search-bar.vue              
               ┗━ star.vue               
┣━ pages ● 页面
         ┣━ cars                    //选择车    
         ┣━ cityChoose              //选择城市
         ┣━ destination             //选择目的地
         ┣━ evaluation              //评价    
         ┣━ index                   //主页面
         ┣━ login                   //登录
         ┣━ orderCancel             //订单取消
         ┣━ orderClose              //订单关闭
         ┣━ orderService            //订单服务
         ┣━ orderWhy                //询问原因
         ┣━ starting                //选择出发地点
         ┗━ wait                     //等待
┣━ store ● 存放vuex相关
         ┣━ index.js            
         ┣━ mutation-types.js               
         ┣━ mutations.js           
         ┗━ state.js        
┣━ utils 工具类
┣━ App.vue
┣━ main.js
┗━ static # 静态资源,存放图片


данные vuex

const state = {
  curNavIndex: 0,   //当前头部导航索引
  phone: '',         //登录号码
  curCity: '',      //当前所在的城市
  startPlace: '出发地', //出发地
  startFormattedPlace: '', //更具人性化的描述的出发地
  startPosition: [],        //包含startLatitude和startLongitude
  destination: '你要去哪儿', //目的地
  endPosition: [],      //包含endLatitude和endLongitude
  driver: {},       //司机信息 包含Cartnumber,cart,id,name,stars
  cost: 0       //花费
}


Детали функции


Автоматическое скольжение головы навигации


Чтобы обеспечить автоматическое выдвижение главной навигации при нажатии, проведите пальцем по экрану, пока скользит главная навигация, и прокрутите назад до страницы индекса, когда автомобиль выбран на странице автомобилей.Я сохраняю значение индекса curNavIndex в vuex. Установите разные значения прокрутки влево для прокрутки в соответствии с разным curNavIndex.

Итак, как установить точное значение прокрутки влево?

Апплет WeChat не может выполнять операции Dom, поэтому ширина элемента не может быть получена динамически. Поэтому я поддерживаю массив navOffsetArr на основе ширины каждого элемента в навигации заголовка.

  //两个字宽度+2*margin 也就是 32+10*2 = 52
  const NAV_SMALL_WIDTH = 52;
  //三个字宽度+2*margin 也就是 48+10*2 = 68
  const NAV_BIG_WIDTH = 68;


  this.navOffsetArr = [
        0,
        0,
        NAV_SMALL_WIDTH,
        NAV_SMALL_WIDTH * 2,
        NAV_SMALL_WIDTH * 2 + NAV_BIG_WIDTH,
        NAV_SMALL_WIDTH * 2 + NAV_BIG_WIDTH * 2,
        NAV_SMALL_WIDTH * 3 + NAV_BIG_WIDTH * 2,
        NAV_SMALL_WIDTH * 4 + NAV_BIG_WIDTH * 2
      ]

получить значение индекса

 computed: {
      ...mapState([
        'curNavIndex'
      ])
    }

Посмотрите значение индекса в часах, когда curNavIndex изменится, получите другое значение navScrollLeft

 watch: {
      curNavIndex(newIndex){
        this.navScrollLeft = this.navOffsetArr[newIndex]
      }
    }

Наконец, привяжите scroll-left к navScrollLeft, чтобы добиться автоматического скольжения.

     <scroll-view
        class="nav"
        scroll-x="true"
        scroll-with-animation="true"
        :scroll-left="navScrollLeft">
       
       ......
       ......
       
      </scroll-view>


Домашняя страница автоматически сохраняет информацию о местоположении

При входе на домашнюю страницу индекса текущий город, текущая широта и долгота, а также текущий адрес будут автоматически сохранены в качестве информации о начальной точке. Здесь удобнее получить доступ к API карты Tencent.

  wx.getLocation({
          type: 'gcj02',
          success: (res) => {
            reverseGeocoder(qqmapsdk, res).then(res => {
              this.saveStartPlace(res.result.address)
              this.saveFormattedStartPlace(res.result.formatted_addresses.recommend)
              this.saveCurCity(res.result.address_component.city)
            })
            this.saveStartPosition([res.latitude, res.longitude])
          }
        })

mapMutations

methods: {
  ...mapMutations({
        saveCurNavIndex: 'SET_CUR_NAV_INDEX',
        saveStartPlace: 'SET_START_PLACE',
        saveFormattedStartPlace: 'SET_FORMATTED_START_PLACE',
        saveCurCity: 'SET_CUR_CITY',
        saveStartPosition: 'SET_START_POSITION',
        saveCost: 'SET_COST'
      })
} 

При этом reverseGeocoder() — это функция преобразования адреса местоположения, qqmapsdk.reverseGeocoder() провела пакет

function reverseGeocoder(qqmapsdk, {latitude, longitude}) {
  return new Promise((resolve, reject) => {
    qqmapsdk.reverseGeocoder({
      location: {
        latitude: latitude,
        longitude: longitude,
      },
      success: (res) => resolve(res),
      fail: (res) => reject(res)
    })
  })
}

Так что, когда мы входим на домашнюю страницу индекса, вы можете увидеть данные в консоли для успешного сохранения в Vuex


Выберите отправную точку



Будут некоторые ямки при использовании компонента карты в mpvue, тут небольшая задержка, а ямки будут обсуждаться позже.


карта карта

 <map class="map-didi"
         id="map-didi"
         :latitude="latitude"
         :longitude="longitude"
         :markers="markers"
         @regionchange="regionChange"
         @begin="begin"
         @end="end"
         show-location
    >
    ...
</map>

При инициализации карты переместите центр карты в startPosition.Если startPosition не существует, переместите центр карты в координаты текущего местоположения, полученные с помощью wx.getLocation().

 initLocation(){
        if (this.startPosition.length) {
          this.latitude = this.startPosition[0]
          this.longitude = this.startPosition[1]
        } else {
          wx.getLocation({
            type: "gcj02",
            success: (res) => {
              this.longitude = res.longitude
              this.latitude = res.latitude
            }
          })
        }
      }

Используйте случайные данные для имитации близлежащих автомобилей, а затем добавляйте их в this.markers.Иконка автомобиля динамически устанавливается в соответствии с curNavIndex, поэтому при выборе различных услуг могут отображаться разные значки автомобилей.

   this.markers = []
        const carNum = getRandomNum(3, 8)
        for (let i = 1; i <= carNum; i++) {
          // 定义一个车对象
          let car = {
            id: 0,
            iconPath: "/static/img/car/cart1.png",
            latitude: 0,
            longitude: 0,
            width: 35,
            height: 15
          }

          //随机值
          const lon_dis = (Math.ceil(Math.random() * 99)) * 0.00012;
          const lat_dis = (Math.ceil(Math.random() * 99)) * 0.00012;

          car.id = 2 + i
          car.latitude = this.latitude + lat_dis
          car.longitude = this.longitude + lon_dis
          car.iconPath = `/static/img/car/cart${this.curNavIndex + 1}.png`
          this.markers.push(car)
        }

Красный значок позиционирования в центре карты и текст времени посадки реализованы путем обтекания изображения обложки с видом обложки.

   <cover-view class="center-marker">
        <cover-view class="text-center">最快{{minutes}}分钟接驾</cover-view>
        <cover-image class="inverted-triangle" src="/static/img/triangle-down.png"></cover-image>
        <cover-image class="img-center" src="/static/img/marker2.png"></cover-image>
      </cover-view>

Среди них перевернутый треугольник — это изображение в виде перевернутого треугольника, потому что вид обложки не может реализовать сложные стили CSS, поэтому эффект перевернутого треугольника внизу может быть достигнут только с помощью изображений.


Карты не рекомендуется использовать здесь для управления, и чиновник также заявил, что управление скоро будет отменено, пожалуйста, используйте обложку


Выберите пункт назначения


Здесь сначала получите curCity в состоянии, используйте qqmapsdk.getSuggestion() и установите для его параметра region значение curCity, вы можете выполнить нечеткий поиск адресов. Когда адрес выбран, используйте qqmapsdk.geocoder() для выполнения разрешения адреса для получения соответствующих данных пункта назначения, а затем сохраните данные в состоянии с помощью mapMutations.

 computed: {
      ...mapState([
        'curCity'
      ])
    }


нечеткий поиск

 qqmapsdk.getSuggestion({
          keyword: value,
          region: this.curCity,
          success: (res) => {
            this.addresses = res.data
          }
        })

При нажатии на адрес адрес анализируется и данные сохраняются

choosePlace(item){
        //item.address详细地址
        //item.title简短语义化地址
        console.log(item)
        qqmapsdk.geocoder({
          address: item.address,
          success: (res) => {
            this.saveEndPosition([res.result.location.lat, res.result.location.lng])
            this.saveDestination(item.title)
            this.goBack()
          },
          fail: (err) => {
            console.log(err)
          }
        })
      }

mapMutations

methods: {
 ...mapMutations({
        saveDestination: 'SET_DESTINATION',
        saveEndPosition: 'SET_END_POSITION'
      })
}   


выберите город


Стиль здесь реализован по текущему апплету Didi, пока выбранный город сохраняется в curCity в состоянии, функция поиска еще не доработана. qqmapsdk.getCityList() в Tencent Maps API используется для получения данных списка городов. Это собственно и есть фильтрация и обработка данных.Сначала инициализируется пустой объект temp_citys, а затем устанавливается ключ по заглавной букве первой буквы пиньинь города, а соответствующее значение представляет собой массив.Массив содержит все города, которые начинаются с этой буквы пиньинь. temp_citys присваивается this.cityList

 qqmapsdk.getCityList({
        success: (res) => {
          const result = res.result[1]
          let temp_citys = {} //使用temp_citys 避免频繁改动data里面的数据
          for (let i = 0; i < result.length; i++) {
            let key = result[i].pinyin[0].charAt(0).toLocaleUpperCase()
            if (!temp_citys[key]) {
              temp_citys[key] = []
            }
            temp_citys[key].push(result[i].fullname)
          }
          this.cityList = temp_citys
        }
      })

Некоторые другие страницы упоминаться не будут. Заинтересованные друзья могут перейти к исходному коду.

Некоторые преимущества использования mpvue


можно использовать векс

Использование vuex для управления состоянием упрощает создание сложных приложений. Вот совет по отладке, используйте createLogger(), после использования вы можете четко увидеть изменения состояния в консоли

index.js в магазине

import Vue from 'vue'
import Vuex from 'vuex'
import state from './state'
import mutations from './mutations'
import createLogger from 'vuex/dist/logger'

Vue.use(Vuex)

const debug = process.env.NODE_ENV !== 'production'

export default new Vuex.Store({
  state,
  mutations,
  strict: debug,
  plugins: debug ? [createLogger()] : []
})

При использовании vuex не забудьте ввести store в main.js соответствующей страницы и назначить store Vue.prototype.$store

Например:

import Vue from 'vue'
import App from './wait.vue'
import store from '../../store/index'

Vue.prototype.$store = store

const app = new Vue(App)
app.$mount()


разработка компонента

Использование компонентной разработки mpvue более удобно, а также удобно переносить компоненты в другие проекты.Полный опыт разработки Vue улучшает повторное использование кода.

Например, панель поиска здесь:

<template>
  <div class="search-bar">
    <div class="text-location" @click.stop="chooseCity">{{curCity}}</div>
    <input type="text"
           v-model="search"
           class="input-location"
           placeholder="你在哪儿上车"
           placeholder-style="color:#cccccc">
    <div class="cancel-location" @click.stop="cancel">取消</div>
  </div>
</template>

<script type="text/ecmascript-6">
  import {debounce} from '../utils/index'

  export default{
    props: {
      curCity: {
        type: String,
        default: '暂无'
      }
    },
    data(){
      return {
        search: ''
      }
    },
    methods: {
      cancel(){
        this.$emit('cancel')
      },
      clear(){
        this.search = ''
      },
      chooseCity(){
        this.$emit('chooseCity')
      }
    },
    watch: {
      search(newVal){
        debounce(() => {
          this.$emit('search', newVal)
        }, 500)()
      }
    }
  }
</script>

Здесь для дроссельной обработки введена функция debounce().


Можно использовать асинхронный/ожидающий

Нативный апплет поддерживает Promise, но для Async/Await он не поддерживается.Используя фреймворк MPVUE, мы можем упаковать некоторые асинхронные функции, чтобы не вернуться в ад.

Например: сетевой запрос

export function request(url, method = 'GET', data, header = {}) {
  return new Promise((resolve, reject) => {
    wx.showLoading({title: '玩命加载中...'})
    wx.request({
      url: baseUrl + url,
      method,
      data,
      header: {'Content-Type': 'json'},
      success: function (res) {
        if (res.statusCode === 200) {
          resolve(res.data)
        } else {
          showToast('发生未知错误!')
          reject(res.data)
        }
      },
      fail: function () {
        showToast('获取数据失败!')
      },
      complete:function () {
        wx.hideLoading()
      }
    })
  })
}
async getInitData(){
    const res = await request('/comments')
    ...
}


Некоторые ямы с использованием mpvue

Молодежь более импульсивна, ошеломлена, и у них слезы, когда они говорят об этом.Официальные документы нужно читать внимательно.Прежде всего, отметим некоторые рутинные ямы.


Отображение вложенного списка

Следует отметить, что для рендеринга вложенного списка необходимо указать другой индекс!


Пример:

<!-- 在这种嵌套循环的时候, index 和 itemIndex 这种索引是必须指定,且别名不能相同,正确的写法如下 -->
<template>
    <ul v-for="(card, index) in list">
        <li v-for="(item, itemIndex) in card">
            {{item.value}}
        </li>
    </ul>
</template>


regionchange

Событие bindregionchange напрямую изменяет привязку к @regionchange на домене. В то же время это событие также очень особенное. Оно имеет два типа событий, начало и конец, поэтому мы не можем различить, какие события находятся в handleProxy, поэтому вы прослушивают такие события. При одновременном прослушивании имени и типа события

<map 
    @regionchange="functionName"
    @end="functionName" 
    @begin="functionName">
<map>


Проблема триггера события

Вот простой пример: компонент слайдера имеет свойство bindchange, которое выполняется после того, как событие вызвало перетаскивание, поэтому, если мы хотим взять значение, соответствующее тому, как работать.

В апплете мы используем: event.detail

А вот в мпвуе писать так: событие.mp.detail


карта мигает

При динамическом обновлении маркеров карта будет мерцать, что делает невозможным перемещение карты.Это большая яма

Ошибка компонента карты bindregionchange:

https://github.com/Meituan-Dianping/mpvue/issues/401


Причина: MPVUE будет обновлять все данные при обновлении определенного атрибута, что неэффективно, когда объем данных относительно большая, и часто меняется данные в данных, также приведут к проблеме замораживания


Решение. Используйте оптимизацию грязной проверки каждый раз при обновлении данных.

GitHub.com/Mehtuan-DI Ах…


Однако лично мне такой способ прямого изменения исходного кода кажется довольно странным, поэтому я нашел другой способ.

    <map class="map-didi"
         id="map-didi"
         @regionchange="regionChange"
         @begin="begin"
         @end="end" >
    </map>
let touchTimeStamp = 0

      regionChange(){ 
      
      },
      begin({timeStamp}){
        touchTimeStamp = timeStamp
      },
      end({timeStamp}){
//       加入时间判断
        if (timeStamp - touchTimeStamp > 50) {
          this.mapCtx.getCenterLocation({
            success: (res) => {
              reverseGeocoder(qqmapsdk, res).then(res => {
                this.saveStartPlace(res.result.address)
                this.saveFormattedStartPlace(res.result.formatted_addresses.recommend)
              })
              const lon_distance = res.longitude - this.longitude
              const lat_distance = res.latitude - this.latitude
              // 更新当前位置坐标
              this.longitude = res.longitude
              this.latitude = res.latitude
              //判断屏幕移动的距离,如果超过阀值
              if (Math.abs(lon_distance) >= 0.0022 || Math.abs(lat_distance) >= 0.0022) {
                //刷新附近的车
                this.updateCars()
                //刷新等待时间
                this.minutes = getRandomNum(3, 12)
              }
            }
          })
        }
      }

Чтобы карта не запускала постоянно начало, конечное событие приводит к частому обновлению данных, здесь делается двойное суждение.Когда время запуска конечного события минус время запуска стартового события превышает установленное время , когда центральная точка перемещается больше, чем значение клапана, мы только обновляем данные данных, что фактически эквивалентно дросселированию.


Некоторые ямки в апплете

Обычные ямы упоминать не будем, а здесь поговорим о странных.

крышка-вид на яму

Cover-view — это текстовое представление, накладываемое на нативные компоненты. Нативные компоненты, которые могут быть покрыты, включают карту, видео, холст, камеру, live-player, live-pusher,Поддерживаются только вложенные обложки и обложки.

Поддерживаются только основные стили позиционирования, макета и текста. Не поддерживает настройку односторонней границы, фонового изображения, тени, переполнения: видимо и т. д.


Так что же нам делать, если мы хотим реализовать одностороннюю границу в обложке?


Вы можете добавить обложку шириной 1 пиксель к обложке, чтобы имитировать одностороннюю границу.

<cover-view class="footer-bar">
    <cover-view class="text" @click.stop="cancel">取消订单
    </cover-view>
    <cover-view class="right-border"></cover-view>
    <cover-view class="text" @click.stop="endTrip">结束行程
    </cover-view>
    <cover-view class="right-border"></cover-view>
    <cover-view class="text">下载滴滴APP</cover-view>
</cover-view>
.footer-bar {
        padding: 0 12px;
        display: flex;
        align-items: center;
        height: 44px;
        color: rgba(0, 0, 0, .7);
        background: #fff;
        font-size: 0;
        .text {
          flex: 1 1 auto;
          display: inline-block;
          height: 22px;
          line-height: 22px;
          text-align: center;
          font-size: 18px;
        }
        .right-border {
          flex: 0 0 1px;
          height: 22px;
          width: 1px;
          background-color: #d9d9d9;
        }
      }


Компонент карты имеет высший уровень Как сделать эффект тени на компоненте карты?

Реализация на самом деле похожа. Используйте обложку, чтобы добавить изображение, которое можно наложить на компонент карты для имитации теней.


Пожалуйста, смотрите эту статью для конкретной реализации:nuggets.capable/post/684490…


адрес проекта

Добро пожаловать, друзья, чтобы обмениваться и учиться вместе, если вам нравится проект, пожалуйста, дайте маленькую звезду


Суммировать

Путь к обучению долог, и не нужно спешить за успехом. Технологии меняются с каждым днем, и главное — освоить неизменное ядро. Приятно постоянно полировать работу, и если будет возможность в будущем, я буду постепенно ее улучшать.


Кроме того, сейчас я учусь на третьем курсе и ищу стажировку после летних каникул.Есть ли в Гуанчжоу кто-нибудь, кто готов меня принять? . .


Ссылки

Didi Yixia, автомобиль с мини-программой уже здесь  https://juejin.cn/post/6844903616961052679

Яма сетевого запроса запросаwoo woo woo.cn blog on.com/change it/afraid…

mpvue + vuex разрабатывает апплет WeChat mapState, проблема недоступности mapGetters

blog.CSDN.net/Боюсь _boom/art…