慕尚花坊 --- 我的

1. 登录

1. Token

**1. 什么是 Token **

Token 是服务器生成的一串字符串, 用作客户端发起请求的一个身份令牌。当第一次登录成功后, 服务器生成一个 Token 便将次 Token 返回给客户端, 客户端在接收到 Token 后, 会使用某种方式将 Token 保存到本地。以后客户端发起请求, 只需要在请求头上携带这个 Token, 服务器通过验证 Token来确认用户的身份, 而无需再次带上用户名和密码。

2. Token 的交互流程

  1. 客户端向服务器发起登录请求, 服务端验证用户名与密码
  2. 验证成功后, 服务端会签发一个 Token, 并将 Token 发送给客户端
  3. 客户端收到 Token 以后, 将其存储起来, 比如放在 localStoragesessionStorage
  4. 客户端每次向服务器请求资源的时候, 需要携带服务器签发的 Token, 服务端收到请求, 然后去验证客户端请求中携带的 Token, 如果验证成功, 就向客户端返回请求的数据

2. 实现流程

传统的登录功能, 需要用户先注册, 注册完成后, 使用注册的账号、密码进行登录, 小程序的登录流程比较简单, 小程序可以通过微信提供的登录能力, 便捷的获取微信提供的用户身份标识进行登录, 免去了注册和输入账号密码的步骤, 从而提高了用户体验

官方文档

https://developers.weixin.qq.com/miniprogram/dev/framework/open-ability/login.html

**小程序登录图示: **

3. 集成 Vant Weapp

1. 安装

npm i @vant/weapp

2. 工具 => 构建 Npm

3. 修改 app.json

{
  "style": "v2"   // 删除此配置项
}

4. 引入组件

pages/login/login.json

{
  "usingComponents": {
    "van-empty": "@vant/weapp/empty/index",
    "van-button": "@vant/weapp/button/index"
  }
}

4. 页面构建

pages/login/login.wxml

<van-empty class="empty-image" image="/assets/default.png" description="点击下方的按钮, 授权登录您的账户">
  <van-button round type="danger" class="bottom-button">点击授权登录</van-button>
</van-empty>

5. 数据交互

1. 绑定点击事件

pages/login/login.wxml

<van-empty class="empty-image" image="/assets/default.png" description="点击下方的按钮, 授权登录您的账户">
  <van-button round type="danger" class="bottom-button" bindtap="login">点击授权登录</van-button>
</van-empty>

2. 获取临时登录凭证,并发送给后端服务器, 得到 token 后将其保存在本地

api/user.js

import http from '../utils/http'

export const reqLoginData = (code) => {
  return http.get(`wexin/wxLogin/${code}`)
}

pages/login/login.js

import {
  toast
} from '@/utils/extendAPI'
import {
  reqLoginData
} from '@/api/user'
import {
  setStorage
} from '@/utils/storage'
Page({

  // 获取临时登录凭证 Code
  login() {
    wx.login({
      success: async (res) => {
        const { code } = res
        if (code) {
          // code 获取成功后, 传给后端服务器,后端会返回 token 字符串
          const res = await reqLoginData(code)
          // 将 token 保存在本地
          setStorage('token', res.token)
        } else {
          toast({
            title: "授权失败,请重新授权"
          })
        }
      },
    })
  },
})

3. 每次发送请求时, 携带 token 在请求头中, 发送给服务器

utils/http.js

import WxRequest from './request'
import {
  getStorage,
  clearStorage
} from './storage'
import {modal, toast} from './extendAPI'

// 实例化 WxRequest,并配置基准地址
const wxr = new WxRequest({
  baseURL: 'https://gmall-prod.atguigu.cn/mall-api',
  timeout: 15000
})

// 配置请求拦截器
wxr.interceptors.request = (config) => {
  // 在发送请求前, 需要先判断本地是否存在访问令牌 token
  const token = getStorage('token')
  //  如果存在,则需要在请求头中添加该令牌
  if (token) {
    config.header.token = token
  }
  return config
}

// 配置响应拦截器
wxr.interceptors.response = async (response) => {
  const {
    isSuccess,
    data
  } = response
  if (!isSuccess) {
    wx.showToast({
      title: '网络异常请重试',
      icon: 'error'
    })
    return response
  }

  // 判断状态码
  switch (data.code) {
    case 200:
      return data
    case 208:   
      const res = await modal({
        content: '鉴权失败, 请重新登录',
        showCancel: false    
      })
      if (res) { 
        clearStorage()

        wx.navigateTo({
          url: '/pages/login/login',
        })
      }
      return Promise.reject(response)
    default:
      toast({
        title: "程序出现异常, 请联系客服或稍后重试"
      })
      return Promise.reject(response)
  }
}

export default wxr

6. token 存储到 store

Token 直接存储到本地不方便对数据进行操作: 要先从本地存储取出、修改、保存, 并且 存储到本地的数据不是响应式的, 当本地存储里面的内容发生改变, 页面不会发生改变。这时候就需要将 Token 存储到 Store中,

1. 安装 Mobx, 并构建 工具 => 构建Npm

npm i mobx-miniprogram mobx-miniprogram-bindings

2. 创建 Store 对象

stores/userStore.js

import {
  observable,
  action
} from 'mobx-miniprogram'
import {
  getStorage
} from '@/utils/storage'

export const userStore = observable({
  // 定义响应式数据
  token: getStorage('token') || '',

  // 修改 Token
  setToken: action(function (token) {
    this.token = token
  })
})

pages/login/login.js

import {
  toast
} from '@/utils/extendAPI'
import {
  reqLoginData
} from '@/api/user'
import {
  setStorage
} from '@/utils/storage'
import {
  ComponentWithStore
} from 'mobx-miniprogram-bindings'
import {
  userStore
} from '@/stores/userStore'
ComponentWithStore({   // 使用 ComponentWithStore() 替换 Page()
  data: {

  },
  storeBindings: {
    store: userStore,
    fields: ['token'],
    actions: ['setToken']
  },
  methods: {    // 将 login 回调函数挪到 methods 对象中
    login() {
      wx.login({
        success: async (res) => {
          const {
            code
          } = res
          if (code) {
            const res = await reqLoginData(code)
            setStorage('token', res.token)
            this.setToken(res.token)  // 将 token 保存到 store
            wx.navigateTo({
              url: '/pages/my/my',
            })
          } else {
            toast({
              title: "授权失败,请重新授权"
            })
          }
        },
      })
    },
  },
})

2. 用户信息

用户信息可能会在多个页面或组件中使用, 为了方便对用户信息的获取和使用, 需要将用户信息存储到 store

1. 获取用户信息

1. 封装接口 API 函数

api/user.js

import http from '@/utils/http'

/**
 * @description 登录
 * @param {String} code 临时登录凭证
 * @returns Promise
 */
export const reqLoginData = (code) => {
  return http.get(`/wexin/wxLogin/${code}`)
}

/**
 * @description 获取用户信息
 * @returns Promise
 */
export const reqUserInfo = () => {
  return http.get('/weixin/getuserInfo')
}

2. 保存用户信息到 本地 和 store

1. 发送请求获取用户信息,并保存在 本地store

pages/login/login.js

import {
  toast
} from '@/utils/extendAPI'
import {
  reqLoginData,
  reqUserInfo
} from '@/api/user'
import {
  setStorage
} from '@/utils/storage'
import {
  ComponentWithStore
} from 'mobx-miniprogram-bindings'
import {
  userStore
} from '@/stores/userStore'
ComponentWithStore({
  data: {

  },
  storeBindings: {
    store: userStore,
    fields: ['token', 'userInfo'],     // 绑定 store 中的用户信息
    actions: ['setToken', 'setUserInfo']
  },
  methods: {
    login() {
      wx.login({
        success: async (res) => {
          const {
            code
          } = res
          if (code) {
            const res = await reqLoginData(code)

            setStorage('token', res.token)

            this.setToken(res.token)

            // 获取用户信息,并保存到 本地 和 store
            this.getUserInfo()

            wx.navigateTo({
              url: '/pages/my/my',
            })
          } else {
            toast({
              title: "授权失败,请重新授权"
            })
          }
        },
      })
    },

    // 获取用户信息
    async getUserInfo() {
      const {data} = await reqUserInfo()

      // 将用户信息存储在本地
      setStorage('userInfo', data)

      // 将用户信息存储在 store
	  this.setUserInfo(data)
    }
  }
})

stores/userStore.js

import {
  observable,
  action
} from 'mobx-miniprogram'
import {
  getStorage
} from '@/utils/storage'

export const userStore = observable({
  // 定义响应式数据
  token: getStorage('token') || '',
  userInfo: getStorage('userInfo') || '',

  setToken: action(function (token) {
    this.token = token
  }),

  // 修改 用户信息
  setUserInfo: action(function (userInfo) {
    this.userInfo = userInfo
  })
})

3. 页面构建

在获取到用户信息后, 已经将用户信息存储到了 本地Store, 想在页面渲染则需要从 Store 中取出用户数据, 并渲染到页面中。

如果用户没有登录, 展示默认头像, 提示用户登录的文案信息, 不展示设置按钮

如果用户已经登录, 展示用户的头像和昵称, 并且展示设置按钮, 方便用户对收货地址、头像、昵称进行更改

**1. 实现思路: **

  1. 在个人中心页面导入 ComponentWithStore 方法构建页面
  2. 配置 storeBindings 让组件和 Store 建立关联
  3. 渲染页面

0. 我的

0. 效果图

1. 构建页面

pages/my/my.wxml

<view class="container">
  <!-- 顶部展示图 -->
  <view class="top-show">
    <image class="img" src="../../assets/swipers/swiper-1.jpg" mode="" />
  </view>

  <view class="bottom-show">
    <!-- 未登录面板 -->
    <view class="user-container section">
      <image class="empty-image" src="../../assets/default.png" mode="" />
      <view class="login-status">
        <text>未登录</text>
        <text>点击授权登录</text>
      </view>
    </view>

    <!-- 登录面板 -->
    <!-- <view class="user-container section">
      <image class="empty-image" src="../../assets/default.png" mode="" />
      <view class="login-status">
        <text>未登录</text>
        <text>点击授权登录</text>
      </view>
    </view> -->

    <!-- 订单面板 -->
    <view class="order section">
      <view class="order-top">
        <view>
          <text>我的订单</text>
        </view>
        <navigator url="">
          <text>查看更多></text>
        </navigator>
      </view>
      <view class="order-content">
        <navigator url="" class="content-item">
          <text class="iconfont icon-dingdan"></text>
          <text class="title">商品订单</text>
        </navigator>

        <navigator url="" class="content-item">
          <text class="iconfont icon-lipinka"></text>
          <text class="title">礼品卡订单</text>
        </navigator>

        <navigator url="" class="content-item">
          <text class="iconfont icon-shouhou"></text>
          <text class="title">退款/售后</text>
        </navigator>
      </view>
    </view>

    <!-- 关于售前售后服务面板 -->
    <view class="after-scale section">
      <view class="scale-top">
        <text>关于售前售后服务</text>
      </view>
      <view class="scale-content">
        <navigator url="" class="content-item">
          <text class="iconfont icon-kefu"></text>
          <text>可与小程序客服实时聊天或电话咨询</text>
        </navigator>
        <navigator url="" class="content-item">
          <text class="iconfont icon-shijian"></text>
          <text>小程序客服工作时间为 08:30 - 20:30</text>
        </navigator>
        <navigator url="" class="content-item">
          <text class="iconfont icon-gantanhao"></text>
          <text>鲜花制作完毕情况下暂不支持退款</text>
        </navigator>
        <navigator url="" class="content-item">
          <text class="iconfont icon-bijijilu"></text>
          <text>鲜花可以提前7-15天预定重大节假日不支持定时配送</text>
        </navigator>
      </view>

    </view>
  </view>
</view>

paes/my/my.scss

.container {
  position: relative;

  // overflow: hidden;
  .top-show {
    .img {
      width: 100%;
      height: 400rpx;
      position: absolute;
      z-index: -100;
    }
  }

  .bottom-show {
    width: 100%;
    position: absolute;
    top: 290rpx;

    .user-container {
      height: 130rpx;
      display: flex;
      background-color: #efefef;
      margin: 0 15rpx;
      padding: 20rpx;
      border-radius: 25rpx;

      .empty-image {
        width: 120rpx;
        height: 120rpx;
      }

      .login-status {
        display: flex;
        flex-direction: column;
        justify-content: center;
        font-size: 27rpx;
        color: #979292;
        margin-left: 20rpx;
      }
    }

    .order {
      height: 230rpx;
      border-radius: 25rpx;
      background-color: #efefef;
      padding: 20rpx;
      margin: 15rpx;

      .order-top {
        display: flex;
        justify-content: space-between;
        font-size: 35rpx;

        navigator {
          text {
            color: #bbb8b8;
            font-size: 28rpx;
          }
        }
      }

      .order-content {
        margin-top: 20rpx;
        display: flex;
        justify-content: space-between;

        .content-item {
          display: flex;
          flex-direction: column;
          padding: 20rpx;

          .iconfont {
            font-size: 80rpx;
            margin: 0 auto;
          }

          .title {
            margin-top: 10rpx;
            font-size: 28rpx;
          }
        }
      }
    }

    .after-scale {
      height: 437rpx;
      background-color: #efefef;
      padding: 20rpx;
      margin: 15rpx;

      .scale-top {
        font-size: 34rpx;
        font-weight: 600;
      }

      .scale-content {
        margin-top: 30rpx;

        .content-item {
          font-size: 26rpx;
          color: #979292;
          margin-bottom: 40rpx;
        }

        .iconfont {
          color: rgb(221, 221, 121);
          margin-right: 15rpx;
        }
      }
    }
  }
}

1. 设置

pages/setup/setup.wxml

<view class="container">
  <view class="item-container">
    <navigator class="item" url="../../pages/setup/personal-data/personal-data">
      <text>修改个人资料</text>
      <text class="right">></text>
    </navigator>

    <navigator class="item" url="../../pages/setup/receive-address/receive-address">
      <text>我的收货地址</text>
      <text class="right">></text>
    </navigator>

    <navigator class="item" url="../../pages/setup/problem-feedback/problem-feedback">
      <text>问题反馈</text>
      <text class="right">></text>
    </navigator>

    <navigator class="item" url="../../pages/setup/contact-us/contact-us">
      <text>联系我们</text>
      <text class="right">></text>
    </navigator>

    <navigator class="item" url="../../pages/setup/auth-info/auth-info">
      <text>授权信息</text>
      <text class="right">></text>
    </navigator>
  </view>

</view>

pages/setup/setup.scss

.container {
  width: 100%;
  height: 100%;
  background-color: #efefef;

  .item {
    width: 100%;
    height: 40rpx;
    background-color: white;
    margin: 16rpx 20rpx;
    padding: 20rpx;
    font-size: 28rpx;
    font-weight: 500;
    display: flex;
    justify-content: space-between;

    .right {
      font-size: 28rpx;
      font-weight: 300;
      margin-right: 70rpx;
    }
  }
}

4. 分包加载

1. 分包处理

随着项目功能的增加, 项目体积也随着增大, 从而影响小程序的加载速度, 影响用户的体验, 因此需要将 更新个人资料收货地址 功能配置成一个分包, 当用户在访问设置页面时, 还要预先加载 更新个人资料收货地址 所在的分包

在分包后, 通过查看代码依赖检查是否分包完成: 详情 => 基本信息 => 代码依赖分析

1. 新建文件夹来存放分包后的页面: miniprogram/modules/settingModule

app.json

{
  "subPackages": [{
    "root": "modules/settingModule",
    "name": "settingModule",
    "pages": [
      "pages/setup/personal-data/personal-data",
      "pages/setup/receive-address/add/add",
      "pages/setup/receive-address/list/list"
    ]
  }]
}

2. 将 个人资料收货地址 目录移动到分包 settingModule

3. 替换 个人资料收货地址 的访问路径

pages/setup/setup.wxml

<view class="container">
  <view class="item-container">
    <!-- 修改为 /modules/settingModule/ 起始的路径地址-->
    <navigator class="item" url="/modules/settingModule/pages/setup/personal-data/personal-data">
      <text>修改个人资料</text>
      <text class="right">></text>
    </navigator>

    <navigator class="item" url="/modules/settingModule/pages/setup/receive-address/list/list">
      <text>我的收货地址</text>
      <text class="right">></text>
    </navigator>
</view>

2. 分包预下载

app.json

{
  "preloadRule": {
    "pages/setup/setup": {
      "network": "all",
      "packages": ["settingModule"]
    }
  }
}

当点击进入设置页面时, 预下载了 settingModule 这个分包

3. 分页面构建

1. 个人资料

modules/settingModule/pages/setup/personal-data/personal-data.json

{
  "usingComponents": {
    "van-button": "@vant/weapp/button/index"
  }
}

modules/settingModule/pages/setup/personal-data/personal-data.wxml

<view class="container">
  <view class="avatar">
    <text class="avatar-text">头像</text>
    <view class="right">
      <image class="right-img" src="/assets/default.png" mode="" />
      <text class="right-text">></text>
    </view>
  </view>

  <view class="nickname">
    <text class="nickname-text">头像</text>
    <view class="right">
      <text class="right-nickname">尚硅谷</text>
      <text class="right-text">></text>
    </view>
  </view>

  <view class="save-button">
    <van-button round type="danger" size="large">保存</van-button>
  </view>
</view>

modules/settingModule/pages/setup/personal-data/personal-data.scss

.container {
  width: 100%;
  height: 100%;
  background-color: #efefef;

  .avatar {
    width: 100%;
    height: 80rpx;
    padding: 16rpx 20rpx;
    background-color: white;
    display: flex;
    align-items: center;
    justify-content: space-between;

    .avatar-text {
      font-size: 30rpx;
    }

    .right {
      display: flex;
      align-items: center;

      .right-img {
        width: 80rpx;
        height: 80rpx;
        margin-right: 30rpx;
      }

      .right-text {
        margin-right: 50rpx;
        font-size: 35rpx;
        font-weight: 300;
        color: #b8b1b1;
      }
    }

  }


  .nickname {
    width: 100%;
    height: 80rpx;
    padding: 16rpx 20rpx;
    background-color: white;
    display: flex;
    align-items: center;
    margin-top: 15rpx;
    justify-content: space-between;

    .nickname-text {}

    .right {
      display: flex;
      align-items: center;

      .right-nickname {
        width: 150rpx;
        height: 80rpx;
        line-height: 80rpx;
        font-size: 28rpx;
        margin-right: -40rpx;
      }

      .right-text {
        margin-right: 50rpx;
        font-size: 35rpx;
        font-weight: 300;
        color: #b8b1b1;
      }
    }
  }

  .save-button {
    margin-top: 15rpx;
  }
}

2. 收货地址

modules/settingModule/pages/setup/receive-address/receive-address.wxml


modules/settingModule/pages/setup/receive-address/receive-address.scss


3. 问题反馈

modules/settingModule/pages/setup/problem-feedback/problem-feedback.wxml


modules/settingModule/pages/setup/problem-feedback/problem-feedback.scss


4. 联系我们

modules/settingModule/pages/setup/contact-us/contact-us.wxml


modules/settingModule/pages/setup/contact-us/contact-us.scss


5. 授权信息

modules/settingModule/pages/setup/auth-info/auth-info.wxml


modules/settingModule/pages/setup/auth-info/auth-info.scss


5. 修改个人资料

1. 头像上传

通过 bindchooseavatar 事件回调获取到的头像信息的临时路径随时会失效, 因为小程序服务端会检测临时文件超过一定的容量时, 会将临时文件清理掉, 所以需要将头像信息的临时路径上传到自己的后台服务器, 如果想要将本地资源上传到服务器, 就需要使用到小程序提供的 API 方法: wx.uploadFile,

1. 原生 API的语法如下:

wx.uploadFile({
    url: '开发者服务器地址',
    filePath: '要上传文件资源的路径 (本地路径)',
    name: '文件对应的 key',
    header: 'HTTP 请求的 Header',
    // 接口调用成功的回调函数
    success: (res) =>{},
    // 接口调用失败的回调函数
    fail: (err) =>{}
})

2. 使用自己封装好的 uploadFile

api/user.js

import http from '@/utils/http'

export const reqUploadFile = (filePath, name) =>{
    return http.upload('/fileUpload', filePath, name)
}

miniprogram/modules/settingModule/pages/setup/personal-data/personal-data.js

import { reqUploadFile } from '@/api/user'
Page({
  data: {
    avatar: ''
  },
  async getAvatar(event) {
    const avatarUrl = event.detail.avatarUrl

    // 发送请求, 将头像保存在服务器
    const { data:avatar } = await reqUploadFile(avatarUrl)
    this.setData({
      avatar: avatar
    })
  }
})

2. 更新用户昵称 (弹出框)

1. 在app.jsonindex.json中引入组件

"usingComponents": {
  "van-dialog": "@vant/weapp/dialog/index"
}

2. 页面中

modules/settingModule/setup/personal-data.wxml

<view class="nickname">
    <text class="nickname-text">昵称</text>
    <view class="right">
        <!-- 绑定点击事件, 显示弹出框 -->
        <text bindtap="onUpdateNickName" class="right-nickname">{{ userInfo.nickname || '尚硅谷' }}</text>
        <text class="right-text">></text>
    </view>
</view>

<!-- 修改用户昵称的弹出框, 默认隐藏 -->
<van-dialog custom-style="position: relative" use-slot title="修改昵称" show="{{ isShowPopup }}" showConfirmButton="{{false}}" showCancelButton="{{false}}" transition="fade">
    <!-- 需要使用 form 组件 来收集数据, 并绑定提交事件的回调函数-->
    <form bindsubmit="getNickname">
        <!-- 需要给 input 输入框 添加 type="nickname" 来让用户授权获取微信昵称 -->
        <!-- 需要添加 name 属性, form 组件才可以收集到 input 输入框的内容 -->
        <input type="nickname" name="nickname" class="input-name" value="{{userInfo.nickname}}" />
        <view class="dialog-content">
            <!-- 给取消按钮添加 form-type="reset", 来重置表单中的所有数据内容 -->
            <button class="cancel" bindtap="cancelForm" form-type="reset">取消</button>
            <!-- 给确定按钮添加 form-type="submit", 将按钮变为提交按钮,在点击确定按钮时,会触发 from 组件的提交事件  -->
            <button class="confirm" type="primary" form-type="submit">确定</button>
        </view>
    </form>

</van-dialog>

modules/settingModule/setup/personal-data.scss

van-dialog {
    width: 100%;

    .input-name {
        margin: 20rpx 40rpx;
        border-radius: 20rpx;
        height: 80rpx;
        border: 1rpx solid rgb(163, 160, 160);
    }

    .dialog-content {
        display: flex;

    }
}

modules/settingModule/setup/personal-data.js

import {
  reqUpdateUserInfo
} from '@/api/user'
Page({
  data: {
    isShowPopup: false
  },
    
  // 显示修改昵称弹框
  onUpdateNickName() {
    this.setData({
      isShowPopup: true,
      'userInfo.nickname': this.data.userInfo.nickname
    })
  },

  // 弹框取消按钮
  cancelForm(){
    this.setData({
      isShowPopup: false
    })
  },
	
  // 获取用户昵称
  getNickname(event) {
    const { nickname} = event.detail.value
    this.setData({
        'userInfo.nickname': nickname
    })
  },
})

3. 保存更新用户信息

1. 保存按钮绑定点击事件

modules/settingModule/setup/personal-data/personal-data.wxml

<van-button round type="danger" size="large" bindtap="updateUserInfo">保存</van-button>

2. 定义请求 API

api/user.js

import http from '@/utils/http'

/**
 * @description 更新用户信息
 * @param {Object} userInfo 
 * @returns Promise
 */
export const reqUpdateUserInfo = (userInfo) =>{
  return http.post('/weixin/updateUser',userinfo)
}

3. 页面回调函数

modules/settingModule/setup/personal-data/personal-data.js

import {
  reqUpdateUserInfo
} from '@/api/user'
Page({
  data: {

  },

  // 更新用户信息
  async updateUserInfo() {
    const res = await reqUpdateUserInfo(this.data.userinfo)
    if (res.code === 200){
      // 用户信息更新成功后, 需要将最新的用户信息存储到本地, 并同步到store
      setStorage('userInfo', this.data.userInfo)
      this.setUserInfo(this.data.userInfo)
      // 提示用户
      toast({
          title: '用户信息更新成功'
      })
    }
  }
})

6. 我的收货地址

1. 列表

1. 页面构建

modules/settingModule/pages/setup/receive-address/list/list.json

{
  "usingComponents": {
    "van-button": "@vant/weapp/button/index",
    "van-icon": "@vant/weapp/icon/index"
  }
}

miniprogram/modules/settingModule/setup/receive-address/list/list.wxml

<view class="container">
  <view class="address-content">
    <view class="left">
      <view class="content-top">
        <text>测试用户</text>
        <text>17600059660</text>
        <text class="logo">默认</text>
      </view>
      <view class="content-bottom">
        甘肃省甘南藏族自治州碌曲县松日鼎盛大厦1层
      </view>
    </view>
    <navigator url="" class="right">
      <text class="iconfont icon-bianji"></text>
    </navigator>
      
    <view class="editBtn" data-id="{{ item.id }}">
      <van-icon name="edit" size="22px" color="#999" />
    </view>
  </view>


  <navigator url="../add/add" class="address-bottom">

    <van-button round type="danger" size="large">新增地址</van-button>
  </navigator>
</view>

miniprogram/modules/settingModule/setup/receive-address/list/list.scss

.container {
  position: relative;
  height: 1200rpx;
  background-color: #efefef;

  .address-content {
    height: 130rpx;
    background-color: #fff;
    padding: 30rpx;
    border-bottom: 1rpx solid rgb(216, 210, 210);
    margin-bottom: 10rpx;
    display: flex;
    align-items: center;

    .left {
      .content-top {
        display: flex;
        font-size: 26rpx;

        text {
          margin-right: 20rpx;
        }

        .logo {
          background-color: #f6c6c6f3;
          color: red;
        }
      }

      .content-bottom {
        margin-top: 15rpx;
        font-size: 28rpx;
      }
    }
	
    // 将按钮固定在右侧
    .right {
      position: fixed;
      right: 45rpx;

      .iconfont {
        font-size: 34rpx;
      }
    }

  }

  // 将按钮固定在底部
  .address-bottom {
    width: 90%;
    position: fixed;
    bottom: 30rpx;
    left: 40rpx;
  }
    
  .editBtn{
    position: fixed;
    right: 80rpx;
  }

}

2. 数据交互

  1. onShow() 钩子函数中调用请求方法
  2. 在获取到数据以后, 使用后端返回的数据对页面进行渲染

miniprogram/modules/settingModule/setup/receive-address/list/list.js

import {reqAddressList} from '@/api/address'

Page({

  data: {
    addressList: []
  },

  getAddressList(){
    const {data:addressList} = reqAddressList()
    this.setData({
      addressList
    })
  },

  /**
   * 生命周期函数--监听页面初次渲染完成
   */
  onReady() {

  },

  /**
   * 生命周期函数--监听页面显示
   */
  onShow() {
    this.getAddressList()
  },
})

miniprogram/modules/settingModule/setup/receive-address/list/list.wxml

<view class="container" wx:if="{{ addressList.length }}">
  <view class="address-content" wx:for="{{ addressList }}" wx:key="id">
    <view class="left">
      <view class="content-top">
        <text>{{ item.name }}</text>
        <text>{{ item.phone }}</text>
        <text wx:if="{{ item.isDefault === 1 }}" class="logo">默认</text>
      </view>
      <view class="content-bottom">
        {{ item.fullAddress }}
      </view>
    </view>
    <navigator url="" class="right">
      <text class="iconfont icon-bianji"></text>
    </navigator>
  </view>


  <navigator url="../add/add" class="address-bottom">
    <van-button round type="danger" size="large">新增地址</van-button>
  </navigator>
</view>

2. 新增

1. 页面构建

miniprogram/modules/settingModule/setup/receive-address/add/add.json

{
  "usingComponents": {
    "van-switch": "@vant/weapp/switch/index"
  }
}

miniprogram/modules/settingModule/setup/receive-address/add/add.wxml

<view class="container">
  <view class="item flex">
    <text>收货人</text>
    <input type="text" placeholder="请输入收货人姓名" />
  </view>

  <view class="item flex">
    <text>手机号</text>
    <input type="text" placeholder="请输入收货人手机号" />
  </view>

  <view class="item2 flex">
    <text>所在地区</text>
    <!-- 小程序 picker组件 mode:region 省市区选择器 -->
    <!-- value 必须是一个数组, 用来存放选中的省市区 -->
    <picker mode="region" value="{{ [] }}">
      <view class="placeholder">请选择收货人所在城市</view>
    </picker>
  </view>

  <view class="item2 flex">
    <text>详细地址</text>
    <input type="text" placeholder="门牌号(例: 5号楼1单元203室)" />
    <view class="localtion">
      <van-icon name="location-o" />
      <text>定位</text>
    </view>

  </view>

  <view class="default-address item3 flex">
    <text>设置默认地址</text>
    <van-switch size="50rpx" checked="{{ isDefault }}" bind:change="changeDefaultAddress" />
  </view>

  <button class="button">保存</button>
</view>

miniprogram/modules/settingModule/setup/receive-address/add/add.wxml

.flex {
  display: flex;
}


.container {
  padding: 20rpx;

  .item {
    width: 100%;
    height: 80rpx;
    align-items: center;
    font-size: 30rpx;
    border-bottom: 1rpx solid rgb(231, 221, 221);

    text {
      margin-right: 100rpx;
    }

    input {
      width: 60%;
      font-size: 28rpx;
    }
  }

  .item2 {
    width: 100%;
    height: 80rpx;
    align-items: center;
    font-size: 30rpx;
    border-bottom: 1rpx solid rgb(231, 221, 221);

    text {
      width: 20%;
      margin-right: 50rpx;
    }

    textarea {
      margin-left: 20rpx;
    }

    .placeholder {
      font-size: 28rpx;
      color: rgb(138, 135, 135);
    }
    .localtion{
      margin-left: 20rpx;
    }


  }

  .item3 {
    width: 100%;
    height: 80rpx;
    align-items: center;
    font-size: 30rpx;
    border-bottom: 1rpx solid rgb(231, 221, 221);

    text {
      margin-right: 360rpx;
    }
  }

  .button {
    margin-top: 20rpx;
    border: 1rpx solid #111111;
    border-radius: 50rpx;
    color: white;
    background-color: #f3514f;
  }
}

2. 收集省市区数据

miniprogram/modules/settingModule/setup/receive-address/add/add.wxml

<view class="item2 flex">
  <text>所在地区</text>
  <!-- 小程序 picker组件 mode:region 省市区选择器,value 必须是一个数组, 用来存放选中的省市区 -->
  <picker 
    mode="region" 
    value="{{ [provinceName,cityName,districtName] }}" 
    bindchange="onAddressChange"
  >
    <!-- 判断 address.provinceName, 展示不同的标签内容 -->
    <view class="region" wx:if="{{provinceName }}">
      {{provinceName + ' ' + cityName + ' ' + districtName}}
    </view>
    <view class="placeholder" wx:else>请选择收货人所在城市</view>
  </picker>
</view>

miniprogram/modules/settingModule/setup/receive-address/add/add.js

Page({

  data: {
    name: '', // 收货人
    phone: '', // 手机号码
    provinceName: '', // 省
    provinceCode: '', // 省编码 
    cityName: '', // 市
    cityCode: '', // 市编码
    districtName: '', // 区
    districtCode: '', // 区编码
    address: '', // 详细地址
    fullAddress: '', // 完整地址
    isDefault: false // 是否默认地址
  },
  
  /**
   * 设置默认地址按钮的回调函数
   */
  changeDefaultAddress({
    detail
  }) {
    this.setData({
      isDefault: detail
    })
  },
  /**
   * 获取所在地区发生改变的数据, 并赋值给 data.address
   */
  onAddressChange(event) {
    const [provinceCode, cityCode, districtCode] = event.detail.code
    const [provinceName, cityName, districtName] = event.detail.value
    this.setData({
      provinceCode,
      cityCode,
      districtCode,
      provinceName,
      cityName,
      districtName
    })
  }
})

3. 获取表单数据

1. 方式一: 传统方式

  1. 给所有的 input 添加 namevalue
  2. button按钮 添加 form-type="submit"
  3. from表单 绑定 submit 事件回调函数

2. 方式二: 使用双向数据绑定来实现表单数据的获取,不支持对象类型的数据双向绑定

miniprogram/modules/settingModule/setup/receive-address/add/add.wxml

<view class="container">
  <form>
    <view class="item flex">
      <text>收货人</text>
      <!-- model:value 双向数据绑定 -->
      <input 
        model:value="{{ name }}"    
        type="text" 
        placeholder="请输入收货人姓名" 
      />
    </view>

    <view class="item flex">
      <text>手机号</text>
      <!-- model:value 双向数据绑定 -->
      <input 
        model:value="{{ phone }}" 
        type="text" 
        placeholder="请输入收货人手机号" 
      />
    </view>


    <view class="item2 flex">
      <text>详细地址</text>
      <textarea 
        model:value="{{ address }}" 
        auto-height 
        placeholder-style="color:#969799;font-size: 28rpx;" 
        placeholder="门牌号(例: 5号楼1单元203室)" 
      />
    </view>

    <view class="default-address item3 flex">
      <text>设置默认地址</text>
      <!-- model:value 双向数据绑定 -->
      <van-switch 
        model:checked="{{ isDefault }}" 
        size="50rpx" 
        bind:change="changeDefaultAddress" 
      />
    </view>
    <!-- 添加点击事件 -->
    <button class="button" bind:tap="saveAddressForm">保存</button>
  </form>

</view>

4. 获取地理定位坐标

1. 申请开通

暂时只对部分类目的小程序开放, 需要先通过类目审核, 然后在小程序管理后台, 开发 => 开发管理 => 接口设置 中自主开通该接口权限

  1. wx.chooseLocation() 打开地图, 用户自己选择位置
  2. wx.getLocation() 获取当前设备的实时位置

2. 在 app.json 中配置 requiredPrivateInfos 进行声明启用, 如果需要调用 wx.getLocation() 则需要继续配置 permission 字段, 同时使用 scope.userLocation 声明收集用户选择的位置信息的目的

{
  "requiredPrivateInfos": ["chooseLocation","getLocation"],
  "permission": {
    "scope.userLocation": {
      "desc": "获取你的位置信息"
    }
  }
}

3. 页面中添加 定位的字体图标, 并绑定点击事件

miniprogram/modules/settingModule/setup/receive-address/add/add.json

{
  "usingComponents": {
    "van-icon": "@vant/weapp/icon/index"
  }
}

miniprogram/modules/settingModule/setup/receive-address/add/add.wxml

<view class="location" bindtap="onLocation">
    <van-icon name="location-o" color="#777" />
    <text>定位</text>
</view>

miniprogram/modules/settingModule/setup/receive-address/add/add.js

Page({

  /**
   * 获取用户地理位置信息
   */
  async onLocation() {
    // 获取当前用户的地理位置, (经度、纬度、高度)
    const res = await wx.getLocation()
    console.log(res);

    // 获取用户自己选择的地理位置
    const res2 = await wx.chooseLocation()
    console.log(res2);
  }

})

5. 拒绝授权后的解决方案

在调用 wx.getLocation() 获取地理位置时, 如果用户选择拒绝授权, 代码会直接抛出错误, 在拒绝授权后, 再次调用 wx.getLocation() 时, 不会再次弹窗询问用户是否允许授权而是直接报错

import {toast} from '@/utils/extendAPI'
Page({
  /**
   * 获取用户地理位置信息
   */
  async onLocation() {
    // 使用 trycatch 来捕获错误, 并提示
    try {
      const res = await wx.getLocation()
      console.log(res);
    } catch (error) {
      toast({title:'您拒绝授权获取地理位置'})
    }
  }
})

重新授权的流程

1. 方式一: 使用 API 打开小程序客户端授权页面设置授权信息

import {
  toast,
  modal
} from '@/utils/extendAPI'
Page({
    
  /**
   * @description 获取用户地理位置信息
   */
  async onLocaltion() {
    // 获取用户在当前小程序的所有授权信息
    const {
      authSetting
    } = await wx.getSetting()
    // 获取用户是否授权了获取当前位置的授权, 授权过: true,拒绝授权: false,未授权过: undefined
    if (authSetting["scope.userLocation"] === false) {
      // 用户拒绝授权后, 再次申请授权时,需要弹窗提醒用户是否需要授权
      const modalRes = await modal({
        title: '授权提示',
        content: '需要获取地理位置信息, 请确认授权'
      })
      // 如果用户点击了取消, 则说明用户拒绝授权, 需要给用户进行提示
      if (!modalRes) return toast({
        title: "您拒绝了授权"
      })
      // 如果用户点击了确定, 则说明用户想再次申请授权,需要打开微信客户端小程序的授权页面
      const {authSetting} = await wx.openSetting()
      // 如果用户未修改授权信息,则提示用户授权失败
      if (!authSetting["scope.userLocation"]) return toast({title: '授权失败'})
      // 如果用户修改了授权信息, 则获取用户的地理位置信息
      try {
        const locationRes = await wx.getLocation()
        console.log(locationRes);
      } catch (error) {
        toast({
          title: "您拒绝授权获取位置信息"
        })
      }
    } else {
      try {
        const locationRes = await wx.getLocation()
        console.log(locationRes);
      } catch (error) {
        toast({
          title: "您拒绝授权获取位置信息"
        })
      }
    }
  }
})

2. 方式二: 使用 button 组件打开小程序客户端授权页面设置授权信息

<view class="box">
  <!-- 设置 open-type="openSetting" -->
  <button type="primary" open-type="openSetting">打开授权页面</button>
</view>

6. 手动选择位置及逆地址解析

使用 wx.chooseLocation()能够很方便的让用户来选择地理位置, 但是返回的数据并没有包含省市区、省市区编码数据。这时候可以使用 腾讯位置服务 -- 微信小程序SDK,将返回的经度、纬度进行逆地址解析,转成详细地址

1. 配置小程序使用 腾讯位置服务

  1. 申请开发者密钥(key):申请密钥

  2. 开通webserviceAPI服务:控制台 ->应用管理 -> 我的应用 ->添加key-> 勾选WebServiceAPI -> 保存

    (小程序SDK需要用到webserviceAPI的部分服务,所以使用该功能的KEY需要具备相应的权限)

  3. 下载微信小程序JavaScriptSDK,微信小程序 JavaScriptSDK v1.2,并放入项目根目录的/libs/tencentMap/

  4. 安全域名设置,在小程序管理后台 -> 开发 -> 开发管理 -> 开发设置 -> “服务器域名” 中设置request合法域名,添加https://apis.map.qq.com

  5. 配置账户额度, 控制条 -> 配额管理 -> 账户额度 -> 逆地址解析 -> 配额分配 -> 配置应用及额度、并发量

2. LBS 逆地址解析代码示例

import QQMapWX from '@/libs/tencentMap/qqmap-wx-jssdk'

Page({
    onLoad(){
        // 实例化 QQMapWX
        this.qqmapwx = new QQMapWX({
            key: '上面申请的key'
        })
    },
    async onLocaltion() {
    	const {latitude,longitude,name} = await wx.chooseLocation()  
        
        // 使用 reversGeocoder API方法进行逆地址解析
        this.qqmapwx.reversGeocoder({
            location:{
                latitude,
                longitude
            },
            success: (res) =>{
                // 获取省市区及省市区编码信息
                const {adcode,province,city,district} = res.result.ad_info
                
                // 获取街道、门牌(可能都是空字符串)
                const {street, street_number} = res.result.address_component
                
                // 获取标准地址
                const {standard_address} = res.result.formatted_addresses
                
                // 对获取的数据格式化,赋值给 data 中的字段
                this.setData({
                    provinceName: province,
                    provinceCode: adcode.replace(adcode.substring(2,6),'0000'),   // 编码都是根据adcode按照规则组织的, 如果是省, 前两位保留, 后面的都是0
                    cityName: city,
                    cityCode: adcode.replace(adcode.substring(4,6),'00',    // 如果是市, 前四位保留, 后面的都是0
                    districtName: district,
                    districtCode: district && adcode,  // 如果是区, 全部保留, 东莞市、中山市、儋州市、嘉峪关市 因其下无区县级,则是空字符串即可
                    address: street + street_number + name,
                    fullAddress: standard_address + name
                })
            } 
        })
    },
})

7. async-validator 表单验证

async-validator 是一个基于 JavaScript 的表单验证库, 支持异步验证规则和自定义验证规则, 主流的 UI 组件库 Ant-designElement 中的表单验证都是基于 async-validator,可以更好的构建表单验证逻辑, 使得错误提示信息更加友好和灵活

1. 下载

npm i async-validator

2. 使用

import Schema from 'async-validator'

Page({
  data: {
    name: '123'
  },
    
  /**
   * @description 对数据进行验证
   */
  onValidator() {
    // 定义验证规则
    const rules = {
      // key 是验证规则的名字, 和验证的数据保持一致
      name: [{
          required: true, // 校验数据的必填
          message: 'name 不能为空' // 未通过检验的错误提示信息 
        }, {
          type: 'string', // 校验数据的类型
          message: 'name 不是字符串'
        }, {
          min: 2, // 最小长度
          max: 3, // 最大长度
          message: 'name 最少两个字,最多三个字'
        }
        /*
        {
          pattern: '', // 正则校验规则
          message: 'name 格式错误'
        }, {
          validator: () => { // 自定义验证规则

          }
        }
        */
      ]
    }

    // 实例化构造函数, 并传入验证规则
    const validator = new Schema(rules)

    // 调用示例方法, 进行校验, 只会校验和验证规则同名的字段
    // 参数一: 需要验证的数据, 要求数据必须是一个对象
    // 参数二: 回调函数
    validator.validate(this.data, (errors, fields) => {
      // 如果验证成功, errors 是 null, 如果验证失败, errors 是数组, 数组中的每一项是每个校验规则的错误信息
      // fields 是需要验证的属性, 属性值是一个数组, 数组中也包含着错误信息
      if (errors) {
        console.log('校验失败');
        console.log(errors);
        console.log(fields);
      } else {
        console.log('校验成功');
      }
    })
  }
})

8. 校验收货地址表单

miniprogram/modules/settingModule/setup/receive-address/add/add.wxml

  <!-- 添加点击事件 -->
  <button class="button" bind:tap="saveAddressForm">保存</button>

miniprogram/modules/settingModule/setup/receive-address/add/add.js

import Schema from 'async-validator'
import {
  toast
} from '@/utils/extendAPI';

Page({

  data: {
    name: '', // 收货人
    phone: '', // 手机号码
    provinceName: '', // 省
    provinceCode: '', // 省编码 
    cityName: '', // 市
    cityCode: '', // 市编码
    districtName: '', // 区
    districtCode: '', // 区编码
    address: '', // 详细地址
    fullAddress: '', // 完整地址
    isDefault: false // 是否默认地址
  },

  /**
   * @description 收集表单数据, 校验成功后发送请求将收货人地址保存到服务器
   */
  async saveAddressForm() {

    // 组织成后端需要的参数 (完整地址, 是否设置为默认地址)
    const {
      provinceName,
      cityName,
      districtName,
      address,
      isDefault
    } = this.data
    const params = {
      ...this.data,
      fullAddress: provinceName + cityName + districtName + address,
      isDefault: isDefault ? 1 : 0
    }

    // 校验表单数据
    const {
      valid
    } = await this.validatorFormData(params)
    // 如果验证失败, 则直接return, 不执行下面的逻辑
    if (!valid) return

    // 如果验证成功, 发送请求, 保存收货地址到后端
    const res = await reqAddAddress(params)

    if (res.code === 200) {
      wx.navigateBack()

      toast({
        title: '新增收货地址成功'
      })
    }
  },

  /**
   * @description 对收货地址数据进行校验
   * @param {Object} params 
   */
  validatorFormData(params) {
    const nameRegExp = `[A-Za-z\\d\\\u4e00-\\u9fa5]+$`
    const phoneRegExp = `1(?:3\\d|4[4-9]|5[0-35-9]|6[67]|7[0-8]|8\\d|9\\d)\\d{8}$`

    const rules = {
      name: [{
        required: true,
        message: '请输入收货人姓名'
      }, {
        pattern: nameRegExp,
        message: '收货人姓名不合法'
      }],
      phone: [{
        required: true,
        message: '请输入收货人手机号'
      }, {
        pattern: phoneRegExp,
        message: '收货人手机号不合法'
      }],
      provinceName: {
        required: true,
        message: '请选择收货人所在地区'
      },
      address: {
        required: true,
        message: '请输入详细地址'
      }
    }

    const validator = new Schema(rules)

    // 封装为 Promise 方式调用
    return new Promise((resolve) => {
      validator.validate(params, (errors) => {
        if (errors) {
          // 如果验证失败, 则需要给用户弹框提示
          toast({
            title: errors[0].message
          })
          // 如过返回值是 false, 则说明验证失败
          resolve({
            valid: false
          })
        } else {
          // 如过返回值是 true, 则说明验证成功
          resolve({
            valid: true
          })
        }
      })
    })

  },
})

9. 保存收货地址到后端

miniprogram/modules/settingModule/setup/receive-address/add/add.js

import Schema from 'async-validator'
import {
  toast
} from '@/utils/extendAPI';

import {reqAddressData} from '@/api/address'
Page({

  data: {
    name: '', // 收货人
    phone: '', // 手机号码
    provinceName: '', // 省
    provinceCode: '', // 省编码 
    cityName: '', // 市
    cityCode: '', // 市编码
    districtName: '', // 区
    districtCode: '', // 区编码
    address: '', // 详细地址
    fullAddress: '', // 完整地址
    isDefault: false // 是否默认地址
  },

  /**
   * @description 收集表单数据, 校验成功后发送请求将收货人地址保存到服务器
   */
  async saveAddressForm() {

    // 组织成后端需要的参数 (完整地址, 是否设置为默认地址)
    const {
      provinceName,
      cityName,
      districtName,
      address,
      isDefault
    } = this.data
    const params = {
      ...this.data,
      fullAddress: provinceName + cityName + districtName + address,
      isDefault: isDefault ? 1 : 0
    }

    // 校验表单数据
    const {
      valid
    } = await this.validatorFormData(params)
    // 如果验证失败, 则直接return, 不执行下面的逻辑
    if (!valid) return

    // 如果验证成功, 发送请求, 保存收货地址到后端
    const res = await reqAddressData(params)
    if (res.code === 200){
      // 返回收货地址列表页面
      wx.navigateBack()

      // 提示新增成功
      toast({title: '新增收货地址成功! '})
    }else{
      
    }
  },
  /**
   * @description 对收货地址数据进行校验
   * @param {Object} params 
   */
  validatorFormData(params) {
    const nameRegExp = `[A-Za-z\\d\\\u4e00-\\u9fa5]+$`
    const phoneRegExp = `1(?:3\\d|4[4-9]|5[0-35-9]|6[67]|7[0-8]|8\\d|9\\d)\\d{8}$`

    const rules = {
      name: [{
        required: true,
        message: '请输入收货人姓名'
      }, {
        pattern: nameRegExp,
        message: '收货人姓名不合法'
      }],
      phone: [{
        required: true,
        message: '请输入收货人手机号'
      }, {
        pattern: phoneRegExp,
        message: '收货人手机号不合法'
      }],
      provinceName: {
        required: true,
        message: '请选择收货人所在地区'
      },
      address: {
        required: true,
        message: '请输入详细地址'
      }
    }

    const validator = new Schema(rules)

    // 封装为 Promise 方式调用
    return new Promise((resolve) => {
      validator.validate(params, (errors) => {
        if (errors) {
          // 如果验证失败, 则需要给用户弹框提示
          toast({
            title: errors[0].message
          })
          // 如过返回值是 false, 则说明验证失败
          resolve({
            valid: false
          })
        } else {
          // 如过返回值是 true, 则说明验证成功
          resolve({
            valid: true
          })
        }
      })
    })

  }

})

miniprogram/api/address.js

import http from '@/utils/http'

export const reqAddressData = (data) => {
  return http.post('/userAddress/save',data)
}

3. 编辑

新增和编辑收货地址页面用的是同一个页面, 在收货地址列表页面中点击更新按钮时, 需要跳转到新增/更新页面, 同时需要将更新这一项的 id 传递给新增/更新页面

onLoad() 中获取 id, 并且使用 id 来区分用户是进行新增还是编辑操作

如果存在 id, 再获取需要更新的收货地址的数据, 并进行页面的回显收货地址数据, 并且需要更新导航栏标题

miniprogram/modules/settingModule/setup/receive-address/list/list.wxml

<view class="container" wx:if="{{ addressList.length }}">
  <view class="address-content" wx:for="{{ addressList }}" wx:key="id">
    <!-- 绑定点击事件 -->
    <view class="editBtn" data-id="{{ item.id }}" bind:tap="toEdit">
      <van-icon name="edit" size="22px" color="#999" />
    </view>
  </view>
</view>

miniprogram/modules/settingModule/setup/receive-address/list/list.js

Page({

  toEdit(event){
    // 获取要更新的收货地址id
    const {id} = event.currentTarget.dataset
    wx.navigateTo({
      url: `/modules/settingModule/pages/setup/receive-address/add/add?id=${id}`,
    })
  },
})

miniprogram/modules/settingModule/setup/receive-address/add/add.js

Page({
    
  onLoad(options) {
    // 实例化 QQMapWX
    this.qqmapwx = new QQMapWX({
      key: '上面申请的key'
    })

    // 判断是否携带参数id
    if (options.id) this.showAddressInfo
  },

  async showAddressInfo(id){
    // 将id 挂载在当前页面的实例(this)上, 方便在多个方法中使用
    this.addressId = id

    // 动态设置当前页面的导航栏标题
    wx.setNavigationBarTitle({
      title: '修改收货地址',
    })

    // 调用接口API 来获取收货地址详情, 并回显
    const {data} = await reqAddressInfo(id)
    this.setData(data)
  },
    
  /**
   * @description 收集表单数据, 校验成功后发送请求将收货人地址保存到服务器
   */
  async saveAddressForm() {
    // ...... 
    // 根据是否有 addressId 来分辨用户是想编辑还是新增
    const res = this.addressId ? await reqUpdateAddress(params) : await reqAddAddress(params)

    if (res.code === 200) {
      wx.navigateBack()
      toast({
        title: this.addressId ? '修改收货地址成功!' :'新增收货地址成功!'
      })
    }
  }

})

4. 删除

1. 功能实现

SwipeCell 滑动单元格

miniprogram/modules/settingModule/setup/receive-address/list/list.json

{
  "usingComponents": {
    "van-swipe-cell": "@vant/weapp/swipe-cell/index"
  }
}

miniprogram/modules/settingModule/setup/receive-address/list/list.wxml

<view class="container" wx:if="{{ addressList.length }}">
  <view class="address-content" wx:for="{{ addressList }}" wx:key="id">
    <van-swipe-cell right-width="{{ 65 }}">
      <van-cell-group>
        <view class="list-item">
          <van-cell>
            <view class="left">
              <view class="content-top">
                <text>{{ item.name }}</text>
                <text>{{ item.phone }}</text>
                <text wx:if="{{ item.isDefault === 1 }}" class="logo">默认</text>
              </view>
              <view class="content-bottom">
                {{ item.fullAddress }}
              </view>
            </view>
            <view class="editBtn" data-id="{{ item.id }}" bind:tap="toEdit">
              <van-icon name="edit" size="22px" color="#999" />
            </view>
          </van-cell>
        </view>
      </van-cell-group>

      <view slot="right" class="van-swipe-cell__right" bind:tap="delAddress" data-id="{{ item.id }}">
        <text>删除</text>
      </view>
    </van-swipe-cell>
  </view>

  <navigator url="../add/add" class="address-bottom">
    <van-button round type="danger" size="large">新增地址</van-button>
  </navigator>
</view>

miniprogram/modules/settingModule/setup/receive-address/list/list.scss

.container {
  position: relative;
  height: 1200rpx;
  background-color: #efefef;

  .address-content {
    height: 130rpx;
    background-color: #fff;
    padding: 30rpx;
    border-bottom: 1rpx solid rgb(216, 210, 210);
    margin-bottom: 10rpx;
    display: flex;
    align-items: center;

    van-swipe-cell {
      width: 100%;
      van-cell-group {
        .list-item {
          width: 100%;
          display: flex;
          .left {
            .content-top {
              display: flex;
              font-size: 26rpx;

              text {
                margin-right: 20rpx;
              }

              .logo {
                background-color: #f6c6c6f3;
                color: red;
              }
            }

            .content-bottom {
              margin-top: 15rpx;
              font-size: 28rpx;
            }

          }

          .editBtn {
            position: fixed;
            right: 60rpx;
            margin-left: 40rpx;
          }
        }
      }
      .van-swipe-cell__right{
        color: red;
      }
    }

  }

  // 将按钮固定在底部
  .address-bottom {
    width: 90%;
    position: fixed;
    bottom: 30rpx;
    left: 40rpx;
  }

}

miniprogram/modules/settingModule/setup/receive-address/list/list.js

Page({


  // 删除收货地址
  async delAddress(event) {
    const {
      id
    } = event.currentTarget.dataset

    // 询问用户是否删除
    const res = await modal({
      content: "您去人删除该收货地址吗?"
    })
    if (res) {
      // 调用接口,删除该收货地址
      const res = await reqDelAddress(id)
      if (res.code === 200) {
        toast({
          title: "删除成功"
        })
        // 重新获取收货地址列表数据
        this.getAddressList()
      }

    }

  }
})

2. 自动收起滑块优化

由于其他页面也需要使用到这个逻辑, 所以将公共逻辑抽取成behavior

miniprogram/behaviors/swipeCell.js

export const swipeCellBehavior = Behavior({
  data: {
    swipeCellQueue: []
  },

  methods: {
    // 当用户打开滑块时,将id
    swipeCellOpen(event) {
      // 获取当前单元格实例
      const instance = this.selectComponent(`#swipe-cell-${event.currentTarget.dataset.id}`)
      // 将实例追加到数组中
      this.data.swipeCellQueue.push(instance)
    },

    // 关闭滑块公共逻辑
    onSwipeCellCommonClick() {
      this.data.swipeCellQueue.forEach(instance => {
        instance.close()
      })

      // 将滑动单元格数组置空
      this.data.swipeCellQueue = []
    },

    // 利用事件冒泡绑定点击事件
    onSwipeCellPage() {
      this.onSwipeCellCommonClick()
    },

    // 点击滑动单元格的按钮时触发的事件
    onSwipeCellClick() {
      this.onSwipeCellCommonClick()
    }
  }
})

miniprogram/modules/settingModule/setup/receive-address/list/list.wxml

<view class="container" wx:if="{{ addressList.length }}" bind:tap="onSwipeCellPage">
  <view class="address-content" wx:for="{{ addressList }}" wx:key="id">
    <van-swipe-cell id="swipe-cell-{{ item.id }}" data-id="{{ item.id }}" bind:click="onSwipeCellClick" bind:open="swpeCellOpen" right-width="{{ 65 }}">
    </van-swipe-cell>
  </view>
</view>

miniprogram/modules/settingModule/setup/receive-address/list/list.js

import {
  swipeCellBehavior
} from '@/behaviors/swipeCell'

Page({
  behaviros: [swipeCellBehavior],
})
posted @ 2024-07-04 15:36  河图s  阅读(29)  评论(0)    收藏  举报