uni-app黑马优购项目学习记录(九)(中篇)

162 阅读7分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第2天,点击查看活动详情

9.2 收货地址区域

9.2.1 创建收货地址组件

  1. components 目录上鼠标右键,选择 新建组件,并填写组件相关的信息。

  2. cart.vue引用

    <template>
      <view>
        <!-- 收获地址 -->
        <my-address></my-address>
        <!--..-->
      <view>
    <template>
    
  3. 渲染收货地址组件的基本结构:

    <view>
    
      <!-- 选择收货地址的盒子 -->
      <view class="address-choose-box">
        <button type="primary" size="mini" class="btnChooseAddress">请选择收货地址+</button>
      </view>
    
      <!-- 渲染收货信息的盒子 -->
      <view class="address-info-box">
        <view class="row1">
          <view class="row1-left">
            <view class="username">收货人:<text>escook</text></view>
          </view>
          <view class="row1-right">
            <view class="phone">电话:<text>138XXXX5555</text></view>
            <uni-icons type="arrowright" size="16"></uni-icons>
          </view>
        </view>
        <view class="row2">
          <view class="row2-left">收货地址:</view>
          <view class="row2-right">河北省邯郸市肥乡区xxx 河北省邯郸市肥乡区xxx 河北省邯郸市肥乡区xxx 河北省邯郸市肥乡区xxx </view>
        </view>
      </view>
    
      <!-- 底部的边框线 -->
      <image src="/static/cart_border@2x.png" class="address-border"></image>
    </view>
    
  4. 美化收货地址组件的样式:

    // 底部边框线的样式
    .address-border {
      display: block;
      width: 100%;
      height: 5px;
    }
    
    // 选择收货地址的盒子
    .address-choose-box {
      height: 90px;
      display: flex;
      align-items: center;
      justify-content: center;
    }
    
    // 渲染收货信息的盒子
    .address-info-box {
      font-size: 12px;
      height: 90px;
      display: flex;
      flex-direction: column;
      justify-content: center;
      padding: 0 5px;
    
      // 第一行
      .row1 {
        display: flex;
        justify-content: space-between;
    
        .row1-right {
          display: flex;
          align-items: center;
    
          .phone {
            margin-right: 5px;
          }
        }
      }
    
      // 第二行
      .row2 {
        display: flex;
        align-items: center;
        margin-top: 10px;
    
        .row2-left {
          white-space: nowrap;
        }
      }
    }
    

9.2.2 实现收货地址区域的按需展示

  1. data 中定义收货地址的信息对象:

    export default {
      data() {
        return {
          // 收货地址
          address: {},
        }
      },
    }
    
  2. 使用 v-ifv-else 实现按需展示:

    <!-- 选择收货地址的盒子 -->
    <view class="address-choose-box" v-if="JSON.stringify(address) === '{}'">
      <button type="primary" size="mini" class="btnChooseAddress">请选择收货地址+</button>
    </view>
    
    <!-- 渲染收货信息的盒子 -->
    <view class="address-info-box" v-else>
      <!-- 省略其它代码 -->
    </view>
    

9.2.3 实现选择收货地址的功能

  1. 请选择收货地址+button 按钮绑定点击事件处理函数:

    <!-- 选择收货地址的盒子 -->
    <view class="address-choose-box" v-if="JSON.stringify(address) === '{}'">
      <button type="primary" size="mini" class="btnChooseAddress" @click="chooseAddress">请选择收货地址+</button>
    </view>
    
  2. 定义 chooseAddress 事件处理函数,调用小程序提供的 chooseAddress() API 实现选择收货地址的功能:

    methods: {
      // 选择收货地址
      async chooseAddress() {
        // 1. 调用小程序提供的 chooseAddress() 方法,即可使用选择收货地址的功能
        //    返回值是一个数组:第 1 项为错误对象;第 2 项为成功之后的收货地址对象
        const [err, succ] = await uni.chooseAddress().catch(err => err)
    
        // 2. 用户成功的选择了收货地址
        if (err === null && succ.errMsg === 'chooseAddress:ok') {
          // 为 data 里面的收货地址对象赋值
          this.address = succ
        }
      }
    }
    
  3. 定义收货详细地址的计算属性:

    computed: {
      // 收货详细地址的计算属性
      addstr() {
        if (!this.address.provinceName) return ''
    
        // 拼接 省,市,区,详细地址 的字符串并返回给用户
        return this.address.provinceName + this.address.cityName + this.address.countyName + this.address.detailInfo
      }
    }
    
  4. 渲染收货地址区域的数据:

    <!-- 渲染收货信息的盒子 -->
    <view class="address-info-box" v-else>
      <view class="row1">
        <view class="row1-left">
          <view class="username">收货人:<text>{{address.userName}}</text></view>
        </view>
        <view class="row1-right">
          <view class="phone">电话:<text>{{address.telNumber}}</text></view>
          <uni-icons type="arrowright" size="16"></uni-icons>
        </view>
      </view>
      <view class="row2">
        <view class="row2-left">收货地址:</view>
        <view class="row2-right">{{addstr}}</view>
      </view>
    </view>
    

9.2.4 将 address 信息存储到 vuex 中

  1. store 目录中,创建用户相关的 vuex 模块,命名为 user.js

    export default {
      // 开启命名空间
      namespaced: true,
    
      // state 数据
      state: () => ({
        // 收货地址
        address: {},
      }),
    
      // 方法
      mutations: {
        // 更新收货地址
        updateAddress(state, address) {
          state.address = address
        },
      },
    
      // 数据包装器
      getters: {},
    }
    
  2. store/store.js 模块中,导入并挂载 user.js 模块:

    // 1. 导入 Vue 和 Vuex
    import Vue from 'vue'
    import Vuex from 'vuex'
    // 导入购物车的 vuex 模块
    import moduleCart from './cart.js'
    // 导入用户的 vuex 模块
    import moduleUser from './user.js'
    
    // 2. 将 Vuex 安装为 Vue 的插件
    Vue.use(Vuex)
    
    // 3. 创建 Store 的实例对象
    const store = new Vuex.Store({
      // TODO:挂载 store 模块
      modules: {
        // 挂载购物车的 vuex 模块,模块内成员的访问路径被调整为 m_cart,例如:
        // 购物车模块中 cart 数组的访问路径是 m_cart/cart
        m_cart: moduleCart,
        // 挂载用户的 vuex 模块,访问路径为 m_user
        m_user: moduleUser,
      },
    })
    
    // 4. 向外共享 Store 的实例对象
    export default store
    
  3. 改造 my-address.vue 组件中的代码,使用 vuex 提供的 address 计算属性 替代 data 中定义的本地 address 对象:

    // 1. 按需导入 mapState 和 mapMutations 这两个辅助函数
    import { mapState, mapMutations } from 'vuex'
    
    export default {
      data() {
        return {
          // 2.1 注释掉下面的 address 对象,使用 2.2 中的代码替代之
          // address: {}
        }
      },
      methods: {
        // 3.1 把 m_user 模块中的 updateAddress 函数映射到当前组件
        ...mapMutations('m_user', ['updateAddress']),
        // 选择收货地址
        async chooseAddress() {
          const [err, succ] = await uni.chooseAddress().catch((err) => err)
    
          // 用户成功的选择了收货地址
          if (err === null && succ.errMsg === 'chooseAddress:ok') {
            // 3.2 把下面这行代码注释掉,使用 3.3 中的代码替代之
            // this.address = succ
    
            // 3.3 调用 Store 中提供的 updateAddress 方法,将 address 保存到 Store 里面
            this.updateAddress(succ)
          }
        },
      },
      computed: {
        // 2.2 把 m_user 模块中的 address 对象映射当前组件中使用,代替 data 中 address 对象
        ...mapState('m_user', ['address']),
        // 收货详细地址的计算属性
        addstr() {
          if (!this.address.provinceName) return ''
    
          // 拼接 省,市,区,详细地址 的字符串并返回给用户
          return this.address.provinceName + this.address.cityName + this.address.countyName + this.address.detailInfo
        },
      },
    }
    

9.2.5 将 Store 中的 address 持久化存储到本地

  1. 修改 store/user.js 模块中的代码如下:

    export default {
      // 开启命名空间
      namespaced: true,
    
      // state 数据
      state: () => ({
        // 3. 读取本地的收货地址数据,初始化 address 对象
        address: JSON.parse(uni.getStorageSync('address') || '{}'),
      }),
    
      // 方法
      mutations: {
        // 更新收货地址
        updateAddress(state, address) {
          state.address = address
    
          // 2. 通过 this.commit() 方法,调用 m_user 模块下的 saveAddressToStorage 方法将 address 对象持久化存储到本地
          this.commit('m_user/saveAddressToStorage')
        },
        // 1. 定义将 address 持久化存储到本地 mutations 方法
        saveAddressToStorage(state) {
          uni.setStorageSync('address', JSON.stringify(state.address))
        },
      },
    
      // 数据包装器
      getters: {},
    }
    

9.2.6 将 addstr 抽离为 getters

目的:为了提高代码的复用性,可以把收货的详细地址抽离为 getters,方便在多个页面和组件之间实现复用。

  1. 剪切 my-address.vue 组件中的 addstr 计算属性的代码,粘贴到 user.js 模块中,作为一个 getters 节点:

    // 数据包装器
    getters: {
      // 收货详细地址的计算属性
      addstr(state) {
        if (!state.address.provinceName) return ''
    
        // 拼接 省,市,区,详细地址 的字符串并返回给用户
        return state.address.provinceName + state.address.cityName + state.address.countyName + state.address.detailInfo
      }
    }
    
  2. 改造 my-address.vue 组件中的代码,通过 mapGetters 辅助函数,将 m_user 模块中的 addstr 映射到当前组件中使用:

    // 按需导入 mapGetters 辅助函数
    import { mapState, mapMutations, mapGetters } from 'vuex'
    
    export default {
      // 省略其它代码
      computed: {
        ...mapState('m_user', ['address']),
        // 将 m_user 模块中的 addstr 映射到当前组件中使用
        ...mapGetters('m_user', ['addstr']),
      },
    }
    

9.2.7 重新选择收货地址

  1. class 类名为 address-info-box 的盒子绑定 click 事件处理函数如下:

    <!-- 渲染收货信息的盒子 -->
    <view class="address-info-box" v-else @click="chooseAddress">
      <!-- 省略其它代码 -->
    </view>
    

9.2.8 解决收货地址授权失败的问题

在这里插入图片描述

如果在选择收货地址的时候,用户点击了取消授权,则需要进行特殊的处理,否则用户将无法再次选择收货地址!

==注意:新版本的微信小程序不会再弹出授权提示框,默认是已授权状态,所以做以下处理主要是适配低版本小程序==

  1. 改造 chooseAddress 方法如下:

    // 选择收货地址
    async chooseAddress() {
      // 1. 调用小程序提供的 chooseAddress() 方法,即可使用选择收货地址的功能
      //    返回值是一个数组:第1项为错误对象;第2项为成功之后的收货地址对象
      const [err, succ] = await uni.chooseAddress().catch(err => err)
    
      // 2. 用户成功的选择了收货地址
      if (succ && succ.errMsg === 'chooseAddress:ok') {
        // 更新 vuex 中的收货地址
        this.updateAddress(succ)
      }
    
      // 3. 用户没有授权
      if (err && err.errMsg === 'chooseAddress:fail auth deny') {
        this.reAuth() // 调用 this.reAuth() 方法,向用户重新发起授权申请
      }
    }
    
  2. methods 节点中声明 reAuth 方法如下:

    // 调用此方法,重新发起收货地址的授权
    async reAuth() {
      // 3.1 提示用户对地址进行授权
      const [err2, confirmResult] = await uni.showModal({
        content: '检测到您没打开地址权限,是否去设置打开?',
        confirmText: "确认",
        cancelText: "取消",
      })
    
      // 3.2 如果弹框异常,则直接退出
      if (err2) return
    
      // 3.3 如果用户点击了 “取消” 按钮,则提示用户 “您取消了地址授权!”
      if (confirmResult.cancel) return uni.$showMsg('您取消了地址授权!')
    
      // 3.4 如果用户点击了 “确认” 按钮,则调用 uni.openSetting() 方法进入授权页面,让用户重新进行授权
      if (confirmResult.confirm) return uni.openSetting({
        // 3.4.1 授权结束,需要对授权的结果做进一步判断
        success: (settingResult) => {
          // 3.4.2 地址授权的值等于 true,提示用户 “授权成功”
          if (settingResult.authSetting['scope.address']) return uni.$showMsg('授权成功!请选择地址')
          // 3.4.3 地址授权的值等于 false,提示用户 “您取消了地址授权”
          if (!settingResult.authSetting['scope.address']) return uni.$showMsg('您取消了地址授权!')
        }
      })
    }
    

9.2.9 解决 iPhone 真机上无法重新授权的问题

问题说明:在 iPhone 设备上,当用户取消授权之后,再次点击选择收货地址按钮的时候,无法弹出授权的提示框!

  1. 导致问题的原因 - 用户取消授权后,再次点击 “选择收货地址” 按钮的时候:

    在模拟器和安卓真机上,错误消息 err.errMsg 的值为 chooseAddress:fail auth deny
    在 iPhone 真机上,错误消息 err.errMsg 的值为 chooseAddress:fail authorize no response

  2. 解决问题的方案 - 修改 chooseAddress 方法中的代码,进一步完善用户没有授权时的 if 判断条件即可:

    async chooseAddress() {
      // 1. 调用小程序提供的 chooseAddress() 方法,即可使用选择收货地址的功能
      //    返回值是一个数组:第1项为错误对象;第2项为成功之后的收货地址对象
      const [err, succ] = await uni.chooseAddress().catch(err => err)
    
      // 2. 用户成功的选择了收货地址
      if (succ && succ.errMsg === 'chooseAddress:ok') {
        this.updateAddress(succ)
      }
    
      // 3. 用户没有授权
      if (err && (err.errMsg === 'chooseAddress:fail auth deny' || err.errMsg === 'chooseAddress:fail authorize no response')) {
        this.reAuth()
      }
    }