纵横开阖-微信小程序之通讯录全攻略

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

内容简介:Create byRecently revised in开篇点题:

Create by jsliang on 2018-11-21 20:46:36

Recently revised in 2018-11-25 00:24:14

Hello 小伙伴们,如果觉得本文还不错,记得给个star, 你们的star是我学习的动力! GitHub 地址

开篇点题:

这是一篇专研微信小程序各种功能实现的文章,例如布局、通讯录、组件之底部导航栏等……

感觉不错的小伙伴,点赞点 Star走一波;

感觉文章有误的小伙伴,评论区、QQ群 溜达一番。

虚心求教,不胜感激~

项目成果图:

纵横开阖-微信小程序之通讯录全攻略
纵横开阖-微信小程序之通讯录全攻略
纵横开阖-微信小程序之通讯录全攻略

一 目录

不折腾的前端,和咸鱼有什么区别

文章篇幅甚多,请利用好目录进行跳转!

目录
3.1 排兵布阵 - Flex布局
3.1.1 楼起平地 - 基础概念
3.1.2 搭砖建瓦 - 左右布局
3.1.3 层台累榭 - 混合布局
3.2 沙场点兵 - 通讯录
3.2.1 谋定苍生 - 整体布局
3.2.2 千里寻敌 - 搜索功能
3.2.3 遥控追踪 - 底部导航
3.2.4 拒敌长城 - 弹窗实现
3.2.5 卧薪尝胆 - 思路整理
3.2.6 广聚民心 - 新增功能
3.2.7 化繁为简 - 修改功能
3.2.8 革新去旧 - 删除功能
3.2.9 兵分一路 - 正常加载
3.2.10 兵分二路 - 拼音导航
3.2.11 一统天下 - 归纳总结

二 前言

写文章无形中也会磨炼自己的表达能力。

这周 ( 2018-11-19 ) 在开发微信小程序的定制 通讯录 时,突然发现 微信小程序 bug 集中营 这篇文章不能再继续写了,因为它变得 臃肿丑陋难维护 ,就连我这个写作人都感慨:如果没有 Ctrl + F ,以及我的 目录 写得还不错,我真心不想再翻这篇文章。

为此, jsliang 单独开了一篇文章: 微信小程序功能清单 。用来记录小程序各种功能的实现,例如布局、通讯录、底部导航栏……

然后嘛,为了能吸引小伙伴点进来瞅瞅,起个标新立异的标题吧: 微信小程序之奇技淫巧

三 功能列表

为了小伙伴能快速了解代码中的意思,小伙伴可以去该 项目地址 下载代码到本地运行查看。

敲了再说

敲 看

一 一

遍 遍

? ?

天 谁

差 都

地 可

别 以

! !

顺带附上一些资源网站:

3.1 排兵布阵 - Flex布局

如果你发现你的 CSS 水平还处于 float 布局,你会发现在小程序中你举步维艰,因为单单只用浮动布局,在小程序中它不好用。

所以, Flex 布局,是你的不二选择:布局的传统解决方案,基于盒状模型,依赖 display 属性 + position 属性 + float 属性。它对于那些特殊布局非常不方便,比如,垂直居中就不容易实现。而 Flex 布局。又称弹性布局,可以简便、完整、响应式地实现各种页面布局。

网上较好的教程有:

如果你想全面了解 Flex ,推荐去看上面的文章。

如果你已经了解 Flex 布局,点击寻找更多精彩!

如果你想快速复习浏览 Flex 布局,那么, Here we go ~

Flex 最终实现效果:

纵横开阖-微信小程序之通讯录全攻略

3.1.1 楼起平地 - 基础概念

万丈高楼平地起,熟悉 Flex 需要先了解下面这 7CSS 属性:

/* 设置 Flex 模式 */
display: flex;

/* 决定元素是横排还是竖着排,要不要倒序 */
flex-direction: column;

/* 决定元素换行格式,一行排不下的时候如何排 */
flex-wrap: wrap;

/* flex-flow = flex-direction + flex-wrap */
flex-flow: column wrap;

/* 同一排下对齐方式,空格如何隔开各个元素 */
justify-content: space-between;

/* 同一排下元素如何对齐,顶部对齐、中部对齐还是其他 */
align-items: center;

/* 多行对齐方式 */
align-content: space-between;
复制代码

下面我们详细分析这些元素的情况:

  1. flex-direction :决定主轴的方向
row
row-reverse
column
column-reverse
display: flex;

flex-direction: row | row-reverse | column | column-reverse;
复制代码
纵横开阖-微信小程序之通讯录全攻略
  1. flex-wrap :一条轴线(一行)排不下时如何解决
nowrap
warp
wrap-reverse
display: flex;

flex-wrap: nowrap | wrap | wrap-reverse;  
复制代码
纵横开阖-微信小程序之通讯录全攻略
纵横开阖-微信小程序之通讯录全攻略
  1. flex-flow :flex-flow = flex-direction + flex-wrap。即 flex-flow 是这两个属性的合集
  • row nowrap - (默认)水平方向,起点在左端,不换行
display: flex;

flex-flow: <flex-direction> || <flex-wrap>;
复制代码

详解参考 12

  1. ustify-content :定义项目在主轴上的对齐方式
flex-start
flex-end
center
space-between
space-around
display: flex;

justify-content: flex-start | flex-end | center | space-between | space-around;
复制代码
纵横开阖-微信小程序之通讯录全攻略
  1. align-items :定义项目在交叉轴上如何对齐
flex-start
flex-end
center
stretch
baseline
display: flex;

align-items: flex-start | flex-end | center | stretch | baseline;
复制代码
纵横开阖-微信小程序之通讯录全攻略
  1. align-content :定义多根轴线的对齐方式。如果只有一根轴线(只有一行),该属性不起作用
flex-start
flex-end
center
stretch
space-between
space-around
display: flex;

align-content: flex-start | flex-end | center | space-between | space-around | stretch;
复制代码
纵横开阖-微信小程序之通讯录全攻略

3.1.2 搭砖建瓦 - 左右布局

实现效果如下:

纵横开阖-微信小程序之通讯录全攻略

如图,这是我们要实现的左右布局效果。那么,在微信小程序要怎么做呢?

*.wxml

<view class="left-and-right-layout">
  <view class="left-and-right-layout-floor-one">
    <text>左右布局</text>
  </view>
  <view class="left-and-right-layout-floor-two">
    <text class="left-and-right-layout-floor-two-left">GitHub 地址</text>
    <navigator class="left-and-right-layout-floor-two-right" url="https://github.com/LiangJunrong/document-library/blob/master/other-library/WeChatApplet/WeChatAppletFunctionList.md">查看详情</navigator>
  </view>
</view>
复制代码

*.wxss

.left-and-right-layout {
  padding: 0 30rpx;
}

.left-and-right-layout-floor-one {
  font-size: 32rpx;
  line-height: 32rpx;
  font-weight: bold;
}

.left-and-right-layout-floor-two {
  /* Flex 左右布局关键点 */
  display: flex;
  justify-content: space-between;

  padding: 30rpx 0;
  font-size: 30rpx;
  line-height: 30rpx;
  border-bottom: 1rpx solid #ccc;
}

.left-and-right-layout-floor-two-right {
  color: deepskyblue;
}
复制代码

3.1.3 层台累榭 - 混合布局

实现效果如下:

纵横开阖-微信小程序之通讯录全攻略

如图,这是我们要实现的混合布局效果,那么在微信小程序中要如何编程呢?

*.wxml

<view class="mixed-layout">
  <view class="mixed-layout-floor-one">
    <text>混合布局</text>
  </view>
  <view class="mixed-layout-floor-two">
    <view class="mixed-layout-floor-two-left">
      <text class="mixed-layout-floor-two-left-title">微信小程序之奇技淫巧</text>
      <text class="mixed-layout-floor-two-left-author" url="https://github.com/LiangJunrong/document-library/blob/master/other-library/WeChatApplet/WeChatAppletFunctionList.md">作者:jsliang</text>
    </view>
    <view class="mixed-layout-floor-two-right">
      <navigator>查看详情</navigator>
    </view>
  </view>
  <view class="mixed-layout-floor-three">
    <text>这是一篇专研小程序各种功能实现的文章,例如布局、通讯录、底部导航栏……如果你感觉不错,可以点赞点 Star;如果感觉有错,那就评论区溜达一番,虚心求教,不胜感激~ </text>
  </view>
  <view class="mixed-layout-floor-four">
    <text>2018-11-23</text>
    <text>2018阅读</text>
    <text class="mixed-layout-floor-four-classification">#小程序功能清单#</text>
  </view>
</view>
复制代码

*.wxss

/* 混合布局 */

/* 混合布局包裹层 */
.mixed-layout {
  margin-top: 30rpx;
  padding: 0 30rpx 30rpx;
}

/* 混合布局第一层 */
.mixed-layout-floor-one {
  font-size: 32rpx;
  line-height: 32rpx;
  font-weight: bold;
}

/* 混合布局第二层 */
.mixed-layout-floor-two {
  /* 关键 Flex 布局 */
  display: flex;
  justify-content: space-between;
  align-items: center;

  margin-top: 40rpx;
  font-size: 32rpx;
  border-bottom: 1rpx dotted #ccc;
}
.mixed-layout-floor-two-left {
  /* 左侧竖行排序 */
  display: flex;
  flex-direction: column;
}
.mixed-layout-floor-two-left-title {
  font-weight: bold;
}
.mixed-layout-floor-two-left-author {
  margin-top: 10rpx;
  color: rgb(146, 138, 138);
  font-size: 30rpx;
}
.mixed-layout-floor-two-right {
  color: deepskyblue;
}

/* 混合布局第三层 */
.mixed-layout-floor-three {
  margin-top: 20rpx;
  font-size: 30rpx;
  line-height: 36rpx;
  color: rgb(110, 108, 108);
  text-indent: 1em;
}

/* 混合布局第四层 */
.mixed-layout-floor-four {
  /* 关键 Flex 布局 */
  display: flex;
  justify-content: space-between;

  margin-top: 20rpx;
  font-size: 30rpx;
  line-height: 30rpx;
}
.mixed-layout-floor-four-classification {
  color: #d0a763;
}
复制代码

3.2 沙场点兵 - 通讯录

不知道小伙伴们在日常开发中,有没有碰到各种稀奇古怪的功能效果,我们觉得不可思议,但是在项目经理的眼中它却是能 “ 满足客户需求 ” 的。

所以,拿到 “ 奇怪的 ” 需求清单的时候不要恐慌,我们仔细分析,总能找到它的破绽,从而完成我们的任务。

通讯录功能的开发如下:

开发时间:4 天

实现效果:

纵横开阖-微信小程序之通讯录全攻略

3.2.1 谋定苍生 - 整体布局

工欲善其事,必先利其器。

首先,我们先将该页面命名为: addressList ,并编写它的 json 门面:

addressList.json
{
  "backgroundTextStyle": "light",
  "navigationBarBackgroundColor": "#fff",
  "navigationBarTitleText": "通讯录",
  "navigationBarTextStyle": "black"
}
复制代码

接着,我们明确需要实现的功能点:

  • 搜索功能
  • 弹窗新增功能
  • 弹窗修改功能
  • 删除功能
  • 拼音导航功能
  • 底部导航栏

然后,我们明确下页面布局:

纵横开阖-微信小程序之通讯录全攻略

如上图,它主要分三大块:头部、内容区、底部。

最后,我们根据功能实现及页面布局编写 wxml 的布局:

wxml 骨架

<!-- part1 - 搜索区域 -->
<view class="search"></view>

<!-- part2 - 搜索结果 -->
<view class="search-result"></view>

<!-- part3 - 内容区域 -->
<view class="contacts-list"></view>

<!-- part4 - 拼音导航 -->
<view class="pinyin-nav"></view>

<!-- part5 - 底部导航 -->
<view class="bottom-nav"></view>

<!-- part6 - 新增弹窗 -->
<view class="add-prompt"></view>

<!-- part7 - 修改弹窗 -->
<view class="edit-prompt"></view>
复制代码

如上,我们将页面分为 7 种情况,其中:

  • 搜索功能 - part1 part2 part4part5
  • 弹窗新增功能 - part1part3part4part5 part6
  • 弹窗修改功能 - part1part3part4part5 part7
  • 删除功能 - part1part3part4part5
  • 拼音导航功能 - part1part3part4part5
  • 底部导航栏 - part1part3part4part5

请注意,出现的 part 部分表明在这种模式下,页面要显示的 part 都有哪些,其他的则暂时隐藏,而加粗的意味着这是这个功能特有的部分。为此,我们应该在 jsdata 中定义好这些模式:

js 代码片段

Page({
  data: {
    /**
     * 功能模式
     * normalModel - 正常模式
     * addModel - 新增模式
     * editModel - 修改模式
     * deleteModel - 删除模式
     * searchModel - 搜索模式
     * pinyinNavModel - 拼音导航模式
     */
    normalModel: false,
    addModel: false,
    editModel: false,
    deleteModel: false,
    searchModel: true,
    pinyinNavModel: false,
  }
})
复制代码

这样,我们除了底部导航栏外,为其他功能定义了一个模式,正常情况下我们开启 normalModel ,其他暂时关闭。

在下文中,我们将根据模式的开启与关闭,显示/隐藏某些内容,并进行数据的管理,请小伙伴们稍微理解下这种思路。

3.2.2 千里寻敌 - 搜索功能

  • 查看 wxml 代码:
  • 查看 wxss 代码:
  • 查看 js 代码`:

本章节实现效果:

纵横开阖-微信小程序之通讯录全攻略

实现思路、编码及代码讲解:

addressList.wxml

  1. wxmlwxss 结构上。

首先,我们通过 fixed 定位,将 search-form 固定在顶部。

然后,我们将 search-form 其内部分为 搜索区 search功能区 action

接着,我们将 search 分为 假的搜索区 search-model-one真的搜索区 search-model-two 。为什么要分两种情况呢?因为这样我们就不用烦恼 inputplaceholder 一会居中一会靠边要怎么区分,思路不容易乱。

最后,根据功能,我们逐步完善 wxmlwxss 代码。

<!-- part1 - 搜索区域 -->
<view class="search-form">
  <!-- 搜索区 -->
  <view class="search">
    <!-- 假的搜索框 -->
    <view wx:if="{{!searchModel}}" class="search-model search-model-one" bindtap="showSearch">
      <image class="icon" src="../../public/img/icon_search.png"></image>
      <text class="search-model-one-text">搜索</text>
    </view>
    <!-- 真的搜索框 -->
    <view wx:if="{{searchModel}}" class="search-model search-model-two">
      <image class="icon search-model-two-icon" src="../../public/img/icon_search.png"></image>
      <!-- 多加层 view 的作用是做到 × 的定位作用 -->
      <view class="search-model-two-form">
        <input type="text" class="search-model-two-input" placeholder="搜索" focus="{{inputFocus}}" value="{{searchVal}}" bindinput="monitorInputVal"></input>
        <text wx:if="{{searchVal.length > 0}}" class="clear-input" bindtap="clearInput">×</text>
      </view>
      <text wx:if="{{searchVal.length <= 0}}" class="search-model-two-button search-model-two-button-cancel" bindtap="showSearch">取消</text>
      <text wx:if="{{searchVal.length > 0}}" class="search-model-two-button search-model-two-button-submit" bindtap="searchSubmit">搜索</text>
    </view>
  </view>
  <!-- 功能区 -->
  <view class="action">
    <text class="action-button action-add" bindtap="showAdd">添加</text>
    <text wx:if="{{!deleteModel}}" class="action-button action-delete" bindtap="showDelete">删除</text>
    <text wx:if="{{deleteModel}}" class="action-button action-delete-comfirm" bindtap="showDelete">完成</text>
  </view>
</view>

<!-- part2 - 搜索结果 -->
<view wx:if="{{searchModel}}" class="search-result">
  <view class="search-result-item" wx:for="{{searchData}}" wx:key="{{searchData.index}}">
    <view class="search-result-item-left">
      <text class="search-result-item-left-name">{{item.userName}}</text>
      <text class="search-result-item-left-phone">{{item.userPhone}}</text>
    </view>
    <view class="search-result-item-right">
      <image class="icon search-result-item-right-edit" src="../../public/img/icon_edit.png"></image>
      <image wx:if="{{deleteModel}}" class="icon search-result-item-right-delete" src="../../public/img/icon_delete.png"></image>
    </view>
  </view>
</view>
复制代码

addressList.wxss

/* 全局样式 */
view {
  box-sizing: border-box;
}
.icon {
  width: 32rpx;
  height: 32rpx;
}

/* 搜索区域 */
.search-form {
  display: flex;
  justify-content: space-around;
  width: 100%;
  height: 100rpx;
  font-size: 32rpx;
  padding: 0 30rpx;
  /* 绝对定位 - 固定搜索部分 */
  position: fixed;
  top: 0;
  left: 0;
  background: #fff;
}

/* 搜索区域 - 结构 1 */
.search {
 width: 60%; 
}
.search-model {
  height: 70rpx;
  line-height: 50rpx;
  padding: 10rpx 0;
}
.search-model-one {
  margin: 15rpx 0;
  background: #f5f5f5;
  text-align: center;
  border-radius: 50rpx;
}
.search-model-one-text {
  margin-left: 30rpx;
  color: #9b9b9b;
  font-size: 30rpx;
}
.search-model-two {
  position: relative;
  display: flex;
  margin-top: 6rpx;
}
.search-model-two-icon {
  position: absolute;
  left: 20rpx;
  top: 30rpx;
  z-index: 10;
}
.search-model-two-form {
  width: 69%;
  height: 70rpx;
  background: #f5f5f5;
  position: relative;
}
.search-model-two-input {
  padding: 0 65rpx 0 65rpx;
  height: 70rpx;
  font-size: 30rpx;
}
.clear-input {
  position: absolute;
  right: 10rpx;
  top: 15rpx;
  display: inline-block;
  width: 30rpx;
  height: 30rpx;
  line-height: 30rpx;
  text-align: center;
  padding: 5rpx;
  color: #fff;
  background: #ccc;
  border-radius: 20rpx;
  z-index: 10;
}
.search-model-two-button {
  display: inline-block;
  text-align: center;
  width: 90rpx;
  height: 60rpx;
  line-height: 60rpx;
  font-size: 24rpx;
  padding: 5rpx 15rpx;
  margin-left: 10rpx;
  color: #fff;
}
.search-model-two-button-cancel {
  background: rgb(8, 202, 186);
}
.search-model-two-button-submit {
  background: rgb(8, 200, 248);
}

/* 搜索区域 - 结构2 */
.action {
  width: 39%;
}
.action-button {
  display: inline-block;
  text-align: center;
  width: 90rpx;
  height: 60rpx;
  line-height: 60rpx;
  font-size: 24rpx;
  margin-top: 15rpx;
  padding: 5rpx 15rpx;
  border: 1rpx solid deepskyblue;
  border-radius: 40rpx;
}
.action-add, .action-delete, .action-delete-comfirm {
  margin-left: 10rpx;
}
.action-delete-comfirm {
  color: #d0a763;
  border: 1rpx solid #d0a763;
}

/* 搜索结果 */
.search-result {
  margin-top: 100rpx;
}
.search-result-item {
  box-sizing: border-box;
  height: 120rpx;
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 27rpx 60rpx 27rpx 30rpx;
  border-bottom: 1rpx solid #f3f3f3;
}
.search-result-item-left {
  display: flex;
  flex-direction: column;
}
.search-result-item-left-name {
  font-size: 30rpx;
  color: #333333;
}
.search-result-item-left-phone {
  font-size: 26rpx;
  color: #999999;
}
.search-result-item-right image {
  width: 32rpx;
  height: 32rpx;
}
.search-result-item-right-edit {
  margin-right: 30rpx;
}
.search-result-item-right-delete {
  margin-right: 30rpx;
}
复制代码

addressList.js

  1. js 上。

我们仔细观察本节开头的 GIF 图,发现它有这几个特点:

×
Page({

  /**
   * 页面的初始数据
   */
  data: {
    /**
     * 功能模式
     * normalModel - 正常模式
     * addModel - 新增模式
     * editModel - 修改模式
     * deleteModel - 删除模式
     * searchModel - 搜索模式
     * pinyinNavModel - 拼音导航模式
     */
    normalModel: true,
    addModel: false,
    editModel: false,
    deleteModel: false,
    searchModel: false,
    pinyinNavModel: false,

    /**
     * 搜索功能
     * inputFocus - 搜索框聚焦
     * searchVal - 搜索内容
     * searchData - 搜索结果
     */
    inputFocus: false,
    searchVal: '',
    searchData: [],
  },

  /**
   * 搜索功能
   * showSearch - 显示搜索框
   * monitorInputVal - 监听搜索框的值
   * searchSubmit - 提交搜索
   * clearInput - 清除搜索
   */
  showSearch(e) {
    this.setData({
      normalModel: !this.data.normalModel,
      searchModel: !this.data.searchModel,
      searchData: [],
      inputFocus: true
    })
  },
  monitorInputVal(e) {
    this.setData({
      searchVal: e.detail.value
    })
  },
  searchSubmit(e) {
    console.log("\n【API - 确认搜索】");
    console.log("搜素字段:" + this.data.searchVal);
    
    // 原数据
    let searchData = this.data.searchData;

    // 搜索数据 - 假设搜索数据是这个,实际应该是接口返回数据
    let newSearchData = [
      {
        userName: '阿狸',
        userPhone: '18811111111',
        pinyin: 'ali'
      },
      {
        userName: '贝吉塔',
        userPhone: '18822222222',
        pinyin: 'beijita'
      },
      {
        userName: '楚怡',
        userPhone: '18833333333',
        pinyin: 'chuyi'
      },
      {
        userName: '邓婕',
        userPhone: '18844444444',
        pinyin: 'dengjie'
      },
      {
        userName: '尔康',
        userPhone: '18855555555',
        pinyin: 'erkang'
      },
      {
        userName: '福狸',
        userPhone: '18866666666',
        pinyin: 'fuli'
      },
      {
        userName: '古狸',
        userPhone: '18877777777',
        pinyin: 'guli'
      },
      {
        userName: '哈狸',
        userPhone: '18888888888',
        pinyin: 'hali'
      },
      {
        userName: 'i狸',
        userPhone: '18899999999',
        pinyin: 'ili'
      },
      {
        userName: '激狸',
        userPhone: '18800000000',
        pinyin: 'jli'
      },
    ]

    // 拼接新旧数据
    searchData.push(...newSearchData);

    console.log("\搜索后数据:");
    console.log(searchData);

    this.setData({
      searchData: searchData
    })

  },
  clearInput(e) {
    console.log("\n清除搜索");
    this.setData({
      searchVal: ''
    })
  },

  /**
   * 删除功能
   */
  showDelete(e) {
    this.setData({
      deleteModel: !this.data.deleteModel
    })
  },

  /**
   * 生命周期函数--监听页面加载
   */
  onLoad: function (options) {
    console.log("\n通讯录");
  },

  /**
   * 页面上拉触底事件的处理函数
   */
  onReachBottom: function () {
    if (this.data.normalModel) { // 正常模式上拉
      console.log("\n正常模式上拉")
    } else if (this.data.searchModel) { // 搜索模式上拉

      console.log("\n搜索模式上拉:");

      // 新数据
      let newSearchData = [
        {
          userName: '克狸',
          userPhone: '18811121112',
          pinyin: 'keli'
        },
      ]

      // 原数据
      let searchData = this.data.searchData;

      // 拼接新旧数据
      searchData.push(...newSearchData);

      console.log("\上拉加载后数据:");
      console.log(searchData);

      this.setData({
        searchData: searchData
      })

    } else if (this.data.pinyinNavModel) { // 拼音模式上拉
      console.log("\n拼音模式上拉");
    }
  },
})
复制代码

到此,我们就实现了搜索功能。尽管它还有点小 bug ,就是不停上拉的时候,它会重复地加载一条数据。

在实际项目中, jsliang 会定义一个 searchNoData 来判断接口是否还在返回数据,如果它不再返回数据,那么通过判断 searchNoData == true 来禁止继续加载。

这样,我们就完美搞定了搜索功能的实现。

3.2.3 遥控追踪 - 底部导航

本章节实现效果:

纵横开阖-微信小程序之通讯录全攻略

众所周知,微信小程序的子页面(除了设置 tabBar 的页面)是没有底部导航栏的。那么,我们要如何设计,才能编写一个 自定义的底部导航栏 呢?

在日常开发中,我们通过 fixed 布局,在页面实现一个 自定义的底部导航栏 是很容易的。

但是,考虑到其他页面可能也需要使用这个底部导航栏,我们就需要想办法将其封装成组件了:

微信小程序 -自定义组件

是的,微信小程序官方文档中是存在这个东西的。当然,仅有官方文档,是满足不了我的,至于过程中我百度了几篇文章来辅助写出下面的代码,你猜?

下面贴出实现代码及如何使用:

  1. 建立目录。

首先,在根目录中新建 component 目录,用来存放我们项目的组件。

然后,我们新建 navBar 目录,用来存放我们的组件 navBar

最后,我们新建 ComponentnavBar

纵横开阖-微信小程序之通讯录全攻略
  1. 进行组件代码编写。
navBar.wxml
<!-- 底部导航条 -->
<view class="navBar">
  <!-- 首页 -->
  <view class="navBar-item navBar-home" bindtap='goHome'>
    <image wx:if="{{homeActive}}" src="../../public/img/tabBar_home.png"></image>
    <image wx:if="{{!homeActive}}" src="../../public/img/tabBar_home_nor.png"></image>
    <text class="{{homeActive ? 'active-text' : 'nor-active-text'}}">首页</text>
  </view>
  <!-- 探索 -->
  <view class="navBar-item navBar-explore" bindtap='goExplore'>
    <image wx:if="{{exploreActive}}" src="../../public/img/tabBar_explore.png"></image>
    <image wx:if="{{!exploreActive}}" src="../../public/img/tabBar_explore_nor.png"></image>
    <text class="{{exploreActive ? 'active-text' : 'nor-active-text'}}">探索</text>
  </view>
  <!-- 我的 -->
  <view class="navBar-item navBar-user" bindtap='goUser'>
    <image wx:if="{{userActive}}" src="../../public/img/tabBar_user.png"></image>
    <image wx:if="{{!userActive}}" src="../../public/img/tabBar_user_nor.png"></image>
    <text class="{{userActive ? 'active-text' : 'nor-active-text'}}">我的</text>
  </view>
</view>
复制代码
navBar.wxss
/* 底部导航条 */
.navBar {
  display: flex;
  justify-content: space-around;
  box-sizing: border-box;
  width: 100%;
  height: 97rpx;
  padding: 5rpx 0;
  border-top: 1rpx solid #cccccc;
  position: fixed;
  bottom: 0;
  background: #F7F7FA;
}
.navBar image {
  width: 55rpx;
  height: 55rpx;
}
.navBar-item {
  display: flex;
  flex-direction: column;
  align-items: center;
  font-size: 20rpx;
  color: #999999;
}
.nor-active-text {
  padding-top: 5rpx;
}
.active-text {
  padding-top: 5rpx;
  color: #d0a763;
}
复制代码

navBar.js

Component({
  /**
   * 组件的属性列表
   */
  properties: {
    homeActive: {
      type: Boolean,
      value: false
    },
    exploreActive: {
      type: Boolean,
      value: false
    },
    userActive: {
      type: Boolean,
      value: false
    }
  },

  /**
   * 组件的初始数据
   */
  data: {

  },

  /**
   * 组件的方法列表
   */
  methods: {
    // 返回首页
    goHome: function (e) {
      wx.switchTab({
        url: '../index/index',
      })
    },
    // 返回探索页
    goExplore: function (e) {
      wx.switchTab({
        url: '../explore/explore',
      })
    },
    // 返回我的
    goUser: function (e) {
      wx.switchTab({
        url: '../user/user',
      })
    }
  }
})
复制代码
navBar.json
{
  "component": true,
  "usingComponents": {}
}
复制代码
  1. 在需要引用的界面引用该组件
addressList.wxml
<!-- part5 - 底部导航 -->
<view class="bottom-nav">
  <navBar homeActive="{{homeActive}}"></navBar>
</view>
复制代码
addressList.json
{
  "backgroundTextStyle": "light",
  "navigationBarBackgroundColor": "#fff",
  "navigationBarTitleText": "通讯录",
  "navigationBarTextStyle": "black",
  "usingComponents": {
    "navBar": "../../component/navBar/navBar"
  }
}
复制代码
addressList.js
Page({
  data: {
    // 引用底部导航
    homeActive: true,
  }
})
复制代码

下次我们还需使用该底部导航栏的时候,我们只需要重复在 addressList 的步骤就行了。

当然,我们需要根据需要活跃的位置,进行 homeActiveexploreActiveuserActive 这三个活跃状态与否的设置。

这样,我们就实现了底部导航栏组件的开发及引用。

3.2.4 拒敌长城 - 弹窗实现

本章节实现效果:

纵横开阖-微信小程序之通讯录全攻略

弹窗?微信小程序就有啊,为啥不用它的呢?

类型 说明 地址
模态弹窗 wx.showModal(Object) - 模态弹窗可以给你选择【取消】或者【确定】 链接
<modal> <modal>是可以提供用户填写 链接
消息弹窗 wx.showToast(Object) - 消息弹窗就是操作成功或者操作失败的那一刻,系统的提示弹窗,无需用户操作,可设定几秒自动关闭 链接
操作菜单 wx.showActionSheet(Object) - 操作菜单类似于弹出的下拉菜单,提供你选择其中某项或者【取消】 链接

然而,逐一尝试,你会发现,上面辣么多弹窗,没有一种符合你的需求的!所以,咱要画一个属于自己的弹窗:

首先,咱在 part6 中新增两个层:遮罩层 jsliang-mask 和弹窗内容 jsliang-alert

然后,往弹窗内容中编写我们需要的标题、 input 输入框以及 text 按钮。

最后,我们逐一细化编写代码。

addressList.wxml
<!-- part6 - 新增弹窗 -->
<view wx:if="{{addModel}}" class="add-prompt">
  <!-- 遮罩层 -->
  <view class="jsliang-mask" bindtap='showAdd'></view>
  <!-- 弹窗内容 -->
  <view class="jsliang-alert">
    <!-- 标题 -->
    <view class="jsliang-alert-title">
      <text>添加成员</text>
      <text class="jsliang-alert-title-close" bindtap='showAdd'>×</text>
    </view>
    <!-- 输入内容 -->
    <view class="jsliang-alert-content">
      <input type="text" placeholder='请输入姓名' placeholder-class='jsliang-alert-content-user-name-placeholder'></input>
      <input type="text" placeholder='请输入电话号码' placeholder-class='jsliang-alert-content-user-phone-placeholder'></input>
    </view>
    <!-- 确定 -->
    <view class="jsliang-alert-submit">
      <text bindtap='addConfirm'>添加</text>
    </view>
  </view>
</view>
复制代码
addressList.wxss
/* 弹窗-添加成员 */
.jsliang-mask {
  z-index: 998;
  position: fixed;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  background: #404040;
  filter: alpha(opacity=90);
  -ms-filter: "alpha(opacity=90)";
  opacity: 0.9;
}
.jsliang-alert {
  z-index: 999;
  position: fixed;
  top: 15%;
  left: 9%;
  width: 620rpx;
  height: 580rpx;
  box-shadow: 2rpx 2rpx 4rpx #A0A0A0, -2rpx -2rpx 4rpx #A0A0A0;
  background-color: #fff;
  border-radius: 15rpx;
}

/* 弹窗标题 */
.jsliang-alert-title {
  height: 120rpx;
  line-height: 120rpx;
  color: #333333;
  background: #f8f0e3;
  font-size: 40rpx;
  font-weight: bold;
  text-align: center;
  position: relative;
  border-radius: 15rpx;
}
.jsliang-alert-title-close {
  display: inline-block;
  color: #999999;
  position: absolute;
  font-size: 50rpx;
  right: 40rpx;
}
/* 弹窗内容 */
.jsliang-alert-content {
  padding: 0 70rpx;
}
.jsliang-alert-content input {
  height: 120rpx;
  line-height: 120rpx;
  font-size: 30rpx;
  border-bottom: 1rpx solid #e6e6e6;
}
.jsliang-alert-content-user-name-placeholder, .jsliang-alert-content-user-phone-placeholder {
  font-size: 30rpx;
  color: #b6b6b6;
}
.jsliang-alert-content-user-phone {
  color: rgb(238, 227, 227);
}
.jsliang-alert-submit {
  font-size: 30rpx;
  margin: 60rpx auto;
  text-align: center;
  width: 400rpx;
  height: 90rpx;
  line-height: 90rpx;
  color: #fff;
  background: deepskyblue;
  border-radius: 50rpx;
}
复制代码

这样,我们就可以通过控制 addModeltrue 或者 false ,来显示隐藏新增弹窗。

同理,我们可以依法炮制通过 editModel 控制修改弹窗。

3.2.5 卧薪尝胆 - 思路整理

文章写到这里,我们需要整理下我们都完成了什么,还缺什么?

纵横开阖-微信小程序之通讯录全攻略

如上,我们实现了:

  • 搜索功能
  • 底部导航
  • 弹窗显示

那么,我们还缺少:

  • 新增成员功能
  • 修改成员功能
  • 删除成员功能
  • 拼音导航功能

很好!我们实现了一半功能了!但是,小伙伴有没有发现,我们的主内容区是空白的。

所以,为了剩下的功能实现,我们应该编写下 内容区域 ,并进行页面的数据加载:

addressList.wxml
<!-- part3 - 内容区域 -->
<view class="contacts-list">
  <!-- 每组字母数据 -->
  <view class="contacts-item" wx:for="{{contactsData}}" wx:for-item="contactsDataItem" wx:key="{{contactsDataItem.index}}">
    <!-- 字母标题 -->
    <view wx:if="{{!contactsDataItem.users.length < 1}}" class="contacts-list-title">
      <text>{{contactsDataItem.groupName}}</text>
    </view>
    <!-- 该组字母的成员 -->
    <view class="contacts-list-user" wx:for="{{contactsDataItem.users}}" wx:for-item="usersItem" wx:key="{{usersItem.index}}">
      <!-- 成员信息展示 -->
      <view class="contacts-list-user-left">
        <text class="contacts-list-user-left-name">{{usersItem.userName}}</text>
        <text class="contacts-list-user-left-phone">{{usersItem.userPhone}}</text>
      </view>
      <!-- 成员操作 -->
      <view class="contacts-list-user-right">
        <image class="icon contacts-list-user-right-edit" src="../../public/img/icon_edit.png"></image>
        <image wx:if="{{deleteModel}}" class="icon contacts-list-user-right-delete" src="../../public/img/icon_delete.png"></image>
      </view>
    </view>
  </view>
</view>
复制代码
addressList.wxss
/* 联系人列表 */
.contacts-list {
  margin-top: 100rpx;
  margin-bottom: 120rpx;
}
.contacts-list-title {
  box-sizing: border-box;
  font-size: 24rpx;
  font-weight: bold;
  height: 44rpx;
  line-height: 44rpx;
  color: #b2b2b2;
  background: #f5f5f5;
  border-bottom: 1rpx solid #efefef;
  padding-left: 30rpx;
}
.contacts-list-user {
  box-sizing: border-box;
  height: 120rpx;
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 27rpx 60rpx 27rpx 30rpx;
  border-bottom: 1rpx solid #f3f3f3;
}
.contacts-list-user-left {
  display: flex;
  flex-direction: column;
}
.contacts-list-user-left-name {
  font-size: 30rpx;
  color: #333333;
}
.contacts-list-user-left-phone {
  font-size: 26rpx;
  color: #999999;
}
.contacts-list-user-right image {
  width: 32rpx;
  height: 32rpx;
}
.contacts-list-user-right-edit {
  margin-right: 30rpx;
}
.contacts-list-user-right-delete {
  margin-right: 30rpx;
}
复制代码
addressList.js
Page({
  data: {
    // 数据定义
    contactsData: [
      { groupName: 'A', users: [] },
      { groupName: 'B', users: [] },
      { groupName: 'C', users: [] },
      { groupName: 'D', users: [] },
      { groupName: 'E', users: [] },
      { groupName: 'F', users: [] },
      { groupName: 'G', users: [] },
      { groupName: 'H', users: [] },
      { groupName: 'I', users: [] },
      { groupName: 'J', users: [] },
      { groupName: 'K', users: [] },
      { groupName: 'L', users: [] },
      { groupName: 'M', users: [] },
      { groupName: 'N', users: [] },
      { groupName: 'O', users: [] },
      { groupName: 'P', users: [] },
      { groupName: 'Q', users: [] },
      { groupName: 'R', users: [] },
      { groupName: 'S', users: [] },
      { groupName: 'T', users: [] },
      { groupName: 'U', users: [] },
      { groupName: 'V', users: [] },
      { groupName: 'W', users: [] },
      { groupName: 'X', users: [] },
      { groupName: 'Y', users: [] },
      { groupName: 'Z', users: [] }
    ],
  },
  
  /**
   * 生命周期函数--监听页面加载
   */
  onLoad: function (options) {
    
    console.log("\n通讯录");

    let that = this;

    // 原数据
    let oldData = that.data.contactsData;

    // 第一页数据
    let newData = [
      {
        userName: '阿狸',
        userPhone: '18811111111',
        pinyin: 'ali'
      },
      {
        userName: '贝吉塔',
        userPhone: '18822222222',
        pinyin: 'beijita'
      },
      {
        userName: '楚怡',
        userPhone: '18833333333',
        pinyin: 'chuyi'
      },
      {
        userName: '邓婕',
        userPhone: '18844444444',
        pinyin: 'dengjie'
      },
      {
        userName: '尔康',
        userPhone: '18855555555',
        pinyin: 'erkang'
      },
      {
        userName: '福狸',
        userPhone: '18866666666',
        pinyin: 'fuli'
      },
      {
        userName: '古狸',
        userPhone: '18877777777',
        pinyin: 'guli'
      },
      {
        userName: '哈狸',
        userPhone: '18888888888',
        pinyin: 'hali'
      },
      {
        userName: 'i狸',
        userPhone: '18899999999',
        pinyin: 'ili'
      },
      {
        userName: '激狸',
        userPhone: '18800000000',
        pinyin: 'jli'
      },
    ]

    // 循环新数据
    for (let newDataItem in newData) {
      // 转换新数据拼音首字母为大写
      let initials = newData[newDataItem].pinyin.substr(0, 1).toUpperCase();
      // 循环旧数据
      for (let oldDataItem in oldData) {
        // 获取旧数据字母分组
        let groupName = oldData[oldDataItem].groupName;

        // 判断两个字母是否相同
        if (initials == groupName) {
          // 使用 array[array.length] 将数据加入到该组中
          oldData[oldDataItem].users[oldData[oldDataItem].users.length] = newData[newDataItem];
        }
      }
    }

    console.log("\页面初始加载数据:");
    console.log(oldData);

    that.setData({
      contactsData: oldData
    })

  }
})
复制代码

如上,我们在前几章节代码的前提下,将 part3 部分进行定义,并在 onLoad() 这个内置的页面加载函数中,虚拟了接口返回的第一页数据,最后将它循环判断,放在不同的字母中,从而实现了首页的加载。

所以,我们可以开始实现我们其他的功能咯~

3.2.6 广聚民心 - 新增功能

本章节实现效果:

纵横开阖-微信小程序之通讯录全攻略

如上图,我们实现了新增的功能。那么,它在代码中是如何实现的呢?

首先,我们要知道弹窗效果是如何出来的:

addressList.wxml 代码片段

<!-- part1 - 搜索区域 -->
<view class="search-form">
  <!-- 搜索区 -->
  <!-- ...... 该部分代码并无修改,故省略 -->
  <!-- 功能区 -->
  <view class="action">
    <text class="action-button action-add" bindtap="showAdd">添加</text>
    <text wx:if="{{!deleteModel}}" class="action-button action-delete" bindtap="showDelete">删除</text>
    <text wx:if="{{deleteModel}}" class="action-button action-delete-comfirm" bindtap="showDelete">完成</text>
  </view>
</view>
复制代码

然后,我们在 js 中设置弹窗事件:

addressList.js 代码片段

showAdd(e) {
  this.setData({
    addModel: !this.data.addModel
  })
},
复制代码

是的,在这里,我们通过 addModel 的模式来控制弹窗,那么,弹窗要怎么编写呢?相信小伙伴在前一章了解过弹窗效果的实现,在这里我们为了连贯,再贴下实现新增弹窗的代码:

addressList.wxml 代码片段

<!-- part6 - 新增弹窗 -->
<view wx:if="{{addModel}}" class="add-prompt">
  <!-- 遮罩层 -->
  <view class="jsliang-mask" bindtap='showAdd'></view>
  <!-- 弹窗内容 -->
  <view class="jsliang-alert">
    <!-- 标题 -->
    <view class="jsliang-alert-title">
      <text>添加成员</text>
      <text class="jsliang-alert-title-close" bindtap='showAdd'>×</text>
    </view>
    <!-- 输入内容 -->
    <view class="jsliang-alert-content">
      <input type="text" placeholder='请输入姓名' placeholder-class='jsliang-alert-content-user-name-placeholder' name="addUserName" bindinput='getAddUserName' maxlength='11' value="{{addUserName}}"></input>
      <input type="text" placeholder='请输入电话号码' placeholder-class='jsliang-alert-content-user-phone-placeholder' name="addUserPhone" bindinput='getAddUserPhone' maxlength='11' value="{{addUserPhone}}"></input>
    </view>
    <!-- 确定 -->
    <view class="jsliang-alert-submit" bindtap='addConfirm'>
      <text>添加</text>
    </view>
  </view>
</view>
复制代码

addressList.wxss 代码片段

/* 弹窗-添加成员 */
.jsliang-mask {
  z-index: 998;
  position: fixed;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  background: #404040;
  filter: alpha(opacity=90);
  -ms-filter: "alpha(opacity=90)";
  opacity: 0.9;
}
.jsliang-alert {
  z-index: 999;
  position: fixed;
  top: 15%;
  left: 9%;
  width: 620rpx;
  height: 580rpx;
  box-shadow: 2rpx 2rpx 4rpx #A0A0A0, -2rpx -2rpx 4rpx #A0A0A0;
  background-color: #fff;
  border-radius: 15rpx;
}

/* 弹窗标题 */
.jsliang-alert-title {
  height: 120rpx;
  line-height: 120rpx;
  color: #333333;
  background: #f8f0e3;
  font-size: 40rpx;
  font-weight: bold;
  text-align: center;
  position: relative;
  border-radius: 15rpx;
}
.jsliang-alert-title-close {
  display: inline-block;
  color: #999999;
  position: absolute;
  font-size: 50rpx;
  right: 40rpx;
}
/* 弹窗内容 */
.jsliang-alert-content {
  padding: 0 70rpx;
}
.jsliang-alert-content input {
  height: 120rpx;
  line-height: 120rpx;
  font-size: 30rpx;
  border-bottom: 1rpx solid #e6e6e6;
}
.jsliang-alert-content-user-name-placeholder, .jsliang-alert-content-user-phone-placeholder {
  font-size: 30rpx;
  color: #b6b6b6;
}
.jsliang-alert-content-user-phone {
  color: rgb(238, 227, 227);
}
.jsliang-alert-submit {
  font-size: 30rpx;
  margin: 60rpx auto;
  text-align: center;
  width: 400rpx;
  height: 90rpx;
  line-height: 90rpx;
  color: #fff;
  background: deepskyblue;
  border-radius: 50rpx;
}
复制代码

最后,我们完善 js 代码,获取 input 的值,动态新增到原数据中:

addressList.js
Page({

  /**
   * 页面的初始数据
   */
  data: {
    /**
     * 新增功能
     * addUserName - 新增的用户名
     * addUserPhone - 新增的电话号码
     */
    addUserName: '',
    addUserPhone: '',
  },

  /**
   * 添加功能
   * showAdd - 显示/隐藏 新增弹窗
   * getAddUserName - 双向绑定成员姓名
   * getAddUserPhone - 双向绑定成员电话
   * addConfirm - 确认添加
   */
  showAdd(e) {
    this.setData({
      addModel: !this.data.addModel
    })
  },
  getAddUserName(e) {
    this.setData({
      addUserName: e.detail.value
    })
  },
  getAddUserPhone(e) {
    this.setData({
      addUserPhone: e.detail.value
    })
  },
  addConfirm(e) {
    console.log("\n【API -添加成员】");

    let userName = this.data.addUserName;
    let userPhone = this.data.addUserPhone;

    if (userName == "") { // 不允许姓名为空
      wx.showModal({
        title: '添加失败',
        content: '姓名不能为空~',
        showCancel: false
      })
    } else if (!(/^[\u4e00-\u9fa5a-zA-Z]{1,11}$/.test(userName))) { // 不允许非中文或者大小写英文
      wx.showModal({
        title: '添加失败',
        content: '请用中文或者大小写英文命名~',
        showCancel: false
      })
    } else if (userPhone == "") { // 不允许电话号码为空
      wx.showModal({
        title: '添加失败',
        content: '电话号码不能为空~',
        showCancel: false
      })
    } else if (!(/^1[345789]\d{9}$/.test(userPhone))) { // 不允许电话号码不是 13/4/5/7/8/9 开头的 11 位数字
      wx.showModal({
        title: '添加失败',
        content: '请输入正确的 11 位电话号码~',
        showCancel: false
      })
    } else { // 添加成功
      
      // 新数据。假设后端接口返回的数据为 newData
      let newData = {
        userName: this.data.addUserName,
        userPhone: this.data.addUserPhone,
        pinyin: 'ali'
      }

      // 旧数据
      let oldData = this.data.contactsData;

      // 获取新数据的首字母并转换为大写
      let initials = newData.pinyin.substr(0, 1).toUpperCase();

      // 循环旧数据
      for (let oldDataItem in oldData) {
        // 获取旧数据字母
        let groupName = oldData[oldDataItem].groupName;
        // 判断这两者字母是否相同
        if (initials === groupName) {
          // 往该字母最后一位数据添加新数据
          oldData[oldDataItem].users[oldData[oldDataItem].users.length] = newData;
        }
      }

      console.log("新增后数据:");
      console.log(oldData);
      
      this.setData({
        contactsData: oldData,
        normalModel: true,
        addModel: false,
        addUserName: '',
        addUserPhone: ''
      })
    }
  }
})
复制代码

到此,我们就实现了新增的功能!

3.2.7 化繁为简 - 修改功能

本章节实现效果:

纵横开阖-微信小程序之通讯录全攻略

在新增功能的开发后,我们的修改功能就显得比较容易了。

首先,我们整理下修改的思路:

  • 用户点击按钮,传递数据给窗口:用户姓名、用户电话。
  • 用户点击修改,循环遍历原数据,找到要修改的字母组下要修改的名字再进行修改,所以,单单是上面的两个字段还不够,应该有:用户所在组、用户原姓名、用户新姓名、用户电话。

所以,在 wxml 中我们应该这么写:

addressList.wxml 代码片段

<!-- part3 - 内容区域 -->
<view class="contacts-list">
  <!-- 每组字母数据 -->
  <view class="contacts-item" wx:for="{{contactsData}}" wx:for-item="contactsDataItem" wx:key="{{contactsDataItem.index}}">
    <!-- 字母标题 -->
    <!-- ... 代码省略 ... -->
    <!-- 该组字母的成员 -->
    <view class="contacts-list-user" wx:for="{{contactsDataItem.users}}" wx:for-item="usersItem" wx:key="{{usersItem.index}}">
      <!-- 成员信息展示 -->
      <!-- ... 代码省略 ... -->
      <!-- 成员操作 -->
      <view class="contacts-list-user-right">
        <image class="icon contacts-list-user-right-edit" src="../../public/img/icon_edit.png" bindtap="showEdit" data-username="{{usersItem.userName}}" data-userphone="{{usersItem.userPhone}}" data-groupname="{{contactsDataItem.groupName}}"></image>
        <image wx:if="{{deleteModel}}" class="icon contacts-list-user-right-delete" src="../../public/img/icon_delete.png"></image>
      </view>
    </view>
  </view>
</view>
复制代码

然后,我们将新增的弹窗照搬过来并加入电话无法修改的效果:

addressList.wxml 代码片段

<!-- part7 - 修改弹窗 -->
<view wx:if="{{editModel}}" class="edit-prompt">
  <!-- 遮罩层 -->
  <view class="jsliang-mask" bindtap='showEdit'></view>
  <!-- 弹窗内容 -->
  <view class="jsliang-alert">
    <!-- 标题 -->
    <view class="jsliang-alert-title">
      <text>修改成员</text>
      <text class="jsliang-alert-title-close" bindtap='showEdit'>×</text>
    </view>
    <!-- 输入内容 -->
    <view class="jsliang-alert-content">
      <input type="text" placeholder='请输入姓名' placeholder-class='jsliang-alert-content-user-name-placeholder' name="editUserName" bindinput='getEditUserName' maxlength='11' value="{{editNewUserName}}"></input>
      <input type="text" class="input-forbid" placeholder='请输入电话号码' placeholder-class='jsliang-alert-content-user-phone-placeholder' name="editUserPhone" bindinput='getEditUserPhone' maxlength='11' value="{{editUserPhone}}" disabled="disabled"></input>
    </view>
    <!-- 确定 -->
    <view class="jsliang-alert-submit" bindtap='editConfirm'>
      <text>修改</text>
    </view>
  </view>
</view>
复制代码

addressList.wxss 代码片段

/* 弹窗-添加成员 */
.jsliang-mask {
  z-index: 998;
  position: fixed;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  background: #404040;
  filter: alpha(opacity=90);
  -ms-filter: "alpha(opacity=90)";
  opacity: 0.9;
}
.jsliang-alert {
  z-index: 999;
  position: fixed;
  top: 15%;
  left: 9%;
  width: 620rpx;
  height: 580rpx;
  box-shadow: 2rpx 2rpx 4rpx #A0A0A0, -2rpx -2rpx 4rpx #A0A0A0;
  background-color: #fff;
  border-radius: 15rpx;
}

/* 弹窗标题 */
.jsliang-alert-title {
  height: 120rpx;
  line-height: 120rpx;
  color: #333333;
  background: #f8f0e3;
  font-size: 40rpx;
  font-weight: bold;
  text-align: center;
  position: relative;
  border-radius: 15rpx;
}
.jsliang-alert-title-close {
  display: inline-block;
  color: #999999;
  position: absolute;
  font-size: 50rpx;
  right: 40rpx;
}
/* 弹窗内容 */
.jsliang-alert-content {
  padding: 0 70rpx;
}
.jsliang-alert-content input {
  height: 120rpx;
  line-height: 120rpx;
  font-size: 30rpx;
  border-bottom: 1rpx solid #e6e6e6;
}
.jsliang-alert-content-user-name-placeholder, .jsliang-alert-content-user-phone-placeholder {
  font-size: 30rpx;
  color: #b6b6b6;
}
.jsliang-alert-content-user-phone {
  color: rgb(238, 227, 227);
}
.jsliang-alert-submit {
  font-size: 30rpx;
  margin: 60rpx auto;
  text-align: center;
  width: 400rpx;
  height: 90rpx;
  line-height: 90rpx;
  color: #fff;
  background: deepskyblue;
  border-radius: 50rpx;
}

/* 弹窗-修改成员 */
.input-forbid {
  color: rgb(202, 196, 196);
}
复制代码

最后,我们在 js 中实现修改的功能:

addressList.js 代码片段

// pages/addressList/addressList.js
Page({

  /**
   * 页面的初始数据
   */
  data: {
    /**
     * 修改功能
     * editOldUserName - 在哪组改动
     * editOldUserName - 原名字
     * editNewUserName - 新名字
     * editUserPhone - 电话
     */
    editGroupName: '',
    editOldUserName: '',
    editNewUserName: '',
    editUserPhone: '',
  },

  /**
   * 修改功能
   * showEdit - 显示修改框
   * getEditUserName - 双向绑定成员名
   * getEditUserPhone - 双向绑定成员电话
   * editConfirm - 确认修改
   */
  showEdit(e) {
    if (!this.data.editModel) { // 显示弹窗则传递数据
      this.setData({
        editModel: true,
        editGroupName: e.currentTarget.dataset.groupname,
        editOldUserName: e.currentTarget.dataset.username,
        editNewUserName: e.currentTarget.dataset.username,
        editUserPhone: e.currentTarget.dataset.userphone,
      })
    } else { // 否则只控制弹窗隐藏
      this.setData({
        editModel: false
      })
    }
  },
  getEditUserName(e) {
    this.setData({
      editNewUserName: e.detail.value
    })
  },
  editUserPhone(e) {
    this.setData({
      editUserPhone: e.detail.value
    })
  },
  editConfirm(e) {

    console.log("\n【API - 修改成员】");

    let userName = this.data.editNewUserName;
    let userPhone = this.data.editUserPhone;

    if (userName == "") { // 不允许姓名为空
      wx.showModal({
        title: '修改失败',
        content: '姓名不能为空~',
        showCancel: false
      })
    } else if (!(/^[\u4e00-\u9fa5a-zA-Z]{1,11}$/.test(userName))) { // 不允许非中文或者大小写英文
      wx.showModal({
        title: '修改失败',
        content: '请用中文或者大小写英文命名~',
        showCancel: false
      })
    } else {

      let contactsData = this.data.contactsData;

      // 循环遍历原数据
      for (let groupInfo in contactsData) {
        // 找到原数据中的该字母组
        if (this.data.editGroupName == contactsData[groupInfo].groupName) {
          // 遍历该组的用户名
          for (let userInfo in contactsData[groupInfo].users) {
            // 找到原数据中相同的姓名
            if (this.data.editOldUserName == contactsData[groupInfo].users[userInfo].userName) {
              // 修改它的姓名
              contactsData[groupInfo].users[userInfo].userName = this.data.editNewUserName;

              console.log("新增后数据:");
              console.log(contactsData);

              this.setData({
                contactsData: contactsData,
                editModel: false,
                normalModel: true
              })

              wx.showToast({
                title: '修改成功~',
              })

              break;

            }
          }
        }
      }
    }
  }
})
复制代码

这样,我们就实现了 弹窗修改功能

3.2.8 革新去旧 - 删除功能

本章节实现效果:

纵横开阖-微信小程序之通讯录全攻略

如果有小伙伴是跟着前面章节一步一步走下来的,会发现我在写 搜索功能 的时候,写上了删除模式 deleteModel ,可以唤出删除按钮:

addressList.wxml 代码片段

<!-- part1 - 搜索区域 -->
<view class="search-form">
  <!-- 搜索区 -->
  <!-- ... 代码省略 ... -->
  <!-- 功能区 -->
  <view class="action">
    <text class="action-button action-add" bindtap="showAdd">添加</text>
    <text wx:if="{{!deleteModel}}" class="action-button action-delete" bindtap="showDelete">删除</text>
    <text wx:if="{{deleteModel}}" class="action-button action-delete-comfirm" bindtap="showDelete">完成</text>
  </view>
</view>
复制代码

它绑定了个 showDelete 的事件,来控制删除按钮的显示隐藏:

addressList.js 代码片段

showDelete(e) {
  this.setData({
    deleteModel: !this.data.deleteModel
  })
},
复制代码

addressList.wxml 代码片段

<!-- part3 - 内容区域 -->
<view class="contacts-list">
  <!-- 每组字母数据 -->
  <view class="contacts-item" wx:for="{{contactsData}}" wx:for-item="contactsDataItem" wx:key="{{contactsDataItem.index}}">
    <!-- 字母标题 -->
    <!-- ... 代码省略 ... -->
    <!-- 该组字母的成员 -->
    <view class="contacts-list-user" wx:for="{{contactsDataItem.users}}" wx:for-item="usersItem" wx:for-index="userIndex" wx:key="{{usersItem.index}}">
      <!-- 成员信息展示 -->
      <!-- ... 代码省略 ... -->
      <!-- 成员操作 -->
      <view class="contacts-list-user-right">
        <image class="icon contacts-list-user-right-edit" src="../../public/img/icon_edit.png" bindtap="showEdit" data-groupname="{{contactsDataItem.groupName}}" data-username="{{usersItem.userName}}" data-userphone="{{usersItem.userPhone}}"></image>
        <image wx:if="{{deleteModel}}" class="icon contacts-list-user-right-delete" src="../../public/img/icon_delete.png" bindtap="showConfirm" data-groupname="{{contactsDataItem.groupName}}" data-username="{{usersItem.userName}}" data-index="{{userIndex}}"></image>
      </view>
    </view>
  </view>
</view>
复制代码

然后,如何实现删除功能呢?我们需要传递什么数据给 js

  • 字母组名
  • 该项所在索引

我们只需要遍历原数据,找到对应的组,并根据传递过来的索引,删除该组中对应索引的值,我们就完成了删除的功能:

addressList.js 代码片段

Page({
  /**
   * 删除功能
   * showDelete - 显示/隐藏 删除图标
   * showConfirm - 确认删除
   */
  showDelete(e) {
    this.setData({
      deleteModel: !this.data.deleteModel
    })
  },
  deleteConfirm(e) {

    console.log("\n【API - 删除用户");

    let userName = e.currentTarget.dataset.username;
    let groupName = e.currentTarget.dataset.groupname;
    let index = e.currentTarget.dataset.index;

    wx.showModal({
      title: '删除确认',
      content: '是否删除成员【' + e.currentTarget.dataset.username + "】?",
      success: (e) => {
        
        if (e.confirm) { // 如果确认删除

          console.log("删除成功!");

          // 原数据
          let contactsData = this.data.contactsData;

          // 遍历原数据
          for (let groupInfo in contactsData) {
            // 找到要删除成员所在的组
            if (groupName == contactsData[groupInfo].groupName) {
              // 根据索引删除该条记录
              contactsData[groupInfo].users.splice(index, 1);
            }
          }

          this.setData({
            contactsData: contactsData
          })

          wx.showToast({
            title: '删除成功~',
          })

        } else if (e.cancel) { // 如果取消
          console.log("取消删除!");
        }

      }
    })
  }
})
复制代码

3.2.9 兵分一路 - 正常加载

本章节实现效果:

纵横开阖-微信小程序之通讯录全攻略

写到这里, jsliang 终于可以松一口气了,咱离胜利不远了~

现在,我们实现正常情况下的不断下拉加载:

正如我们在 搜索功能 实现章节中提及到的,我们分三种上拉模式: 正常模式上拉搜索模式上拉拼音模式上拉

addressList.js 代码片段

page({
  /**
   * 页面上拉触底事件的处理函数
   */
  onReachBottom: function () {
    
    if (this.data.normalModel) { // 正常模式上拉
      console.log("\n正常模式上拉");
    } else if (this.data.searchModel) { // 搜索模式上拉
      console.log("\n搜索模式上拉");
    } else if (this.data.pinyinNavModel) { // 拼音模式上拉
      console.log("\n拼音模式上拉");
    }
  }
})
复制代码

那么,我们只需要参考 onLoad 中的正常加载方式,往正常模式中模拟数据,实现上拉效果,就 OK 了:

addressList.js 代码片段

Page({

  /**
   * 页面的初始数据
   */
  data: {
    /**
     * 上拉触底
     * normalModelNoData - 正常模式没数据加载了
     */
    normalModelNoData: false,
  },

  /**
   * 页面上拉触底事件的处理函数
   */
  onReachBottom: function () {
    if (this.data.normalModel) { // 正常模式上拉
      
      console.log("\n正常模式上拉");

      if (!this.data.normalModelNoData) { // 如果还有数据
        
        // 新数据
        let newData = [
          {
            userName: '克狸',
            userPhone: '18811121112',
            pinyin: 'keli'
          },
          {
            userName: '拉狸',
            userPhone: '18811131113',
            pinyin: 'lali'
          },
          {
            userName: '磨狸',
            userPhone: '18811141114',
            pinyin: 'moli'
          },
          {
            userName: '尼狸',
            userPhone: '18811151115',
            pinyin: 'nili'
          },
          {
            userName: '噢狸',
            userPhone: '18811161116',
            pinyin: 'oli'
          },
          {
            userName: '皮皮狸',
            userPhone: '18811171117',
            pinyin: 'pipili'
          },
          {
            userName: '曲狸',
            userPhone: '18811181118',
            pinyin: 'quli'
          },
          {
            userName: '任狸',
            userPhone: '18811191119',
            pinyin: 'renli'
          },
          {
            userName: '司马狸',
            userPhone: '18811211121',
            pinyin: 'simali'
          },
          {
            userName: '提狸',
            userPhone: '18811221122',
            pinyin: 'tili'
          }
        ]

        // 原数据
        let oldData = this.data.contactsData;

        // 循环新数据
        for (let newDataItem in newData) {
          // 转换新数据拼音首字母为大写
          let initials = newData[newDataItem].pinyin.substr(0, 1).toUpperCase();
          // 循环旧数据
          for (let oldDataItem in oldData) {
            // 获取旧数据字母分组
            let groupName = oldData[oldDataItem].groupName;

            // 判断两个字母是否相同
            if (initials == groupName) {
              // 使用 array[array.length] 将数据加入到该组中
              oldData[oldDataItem].users[oldData[oldDataItem].users.length] = newData[newDataItem];
            }
          }
        }

        console.log("\上拉加载后数据:");
        console.log(oldData);

        this.setData({
          contactsData: oldData,
          normalModelNoData: true
        })
        
      } else { // 如果没数据了
        console.log("正常模式没数据");
      }

    } else if (this.data.searchModel) { // 搜索模式上拉
      console.log("\n搜索模式上拉:");
    } else if (this.data.pinyinNavModel) { // 拼音模式上拉
      console.log("\n拼音模式上拉");
    }
  }
})
复制代码

3.2.10 兵分二路 - 拼音导航

本章节实现效果:

纵横开阖-微信小程序之通讯录全攻略

现在,我们完成最后且最重要的一步,实现 拼音导航 功能。

首先,我们先实现拼音导航的布局:

addressList.wxml 代码片段

<!-- part4 - 拼音导航 -->
<view class="pinyin-nav">
  <view wx:for="{{letters}}" wx:key="{{letters.index}}">
    <text class="pinyin-nav-byte" data-byte="{{item}}" bindtap="pingyinNav">{{item}}</text>
  </view>
</view>
复制代码

addressList.wxss 代码片段

/* 拼音导航 */
.pinyin-nav {
  font-size: 28rpx;
  line-height: 28rpx;
  position: fixed;
  right: 10rpx;
  top: 9%;
  height: 80%;
  text-align: center;
}
.pinyin-nav-byte {
  display: inline-block;
  width: 30rpx;
  border-radius: 20rpx;
  padding: 5rpx 5rpx;
  margin-top: 3rpx;
  color: #fff;
  background: rgb(129, 212, 238);
}
复制代码

addressList.js 代码片段

Page({

  /**
   * 页面的初始数据
   */
  data: {
    /**
     * 拼音导航功能
     * letters - 导航字母
     */
    letters: ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z'],
  },
  /**
   * 拼音导航功能
   * pininNav - 点击字母
   */
  pingyinNav(e) {
    console.log(e.currentTarget.dataset.byte);
  },
})
复制代码

然后,布局有了,我们要如何实现滚动效果呢?

考虑到设备的不同,它的高度也不同,所以我们是需要获取到样式的动态高度的。先看看我们在 wxss 中定义的高度吧:

addressList.wxss 代码片段

.contacts-list-title {
  height: 44rpx;
}
.contacts-list-user {
  height: 120rpx;
}
复制代码

因此,我们的一个字母的高度,为 44rpx ;而一个用户数据的高度,为 120rpx ,即我们要滚动的高度 = 44 * 字母个数 + 120 * 用户条数。

最后,我们先在正常模式下模拟实现一遍拼音导航:

addressList.js 代码片段

Page({

  /**
   * 页面的初始数据
   */
  data: {
    /**
     * 拼音导航功能
     * letters - 导航字母
     * equipmentOneRpx - 设备中 1rpx 为多少 px
     */
    letters: ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z'],
    equipmentOneRpx: '',
  },

  /**
   * 拼音导航功能
   * pininNav - 点击字母
   */
  pingyinNav(e) {
        
    console.log("\n【API - 拼音导航】");

    let byte = e.currentTarget.dataset.byte;

    let dataLength = 0;
    let byteLength = 0;

    let data = this.data.contactsData;

    for (let item in data) {
      // 如果该字母比点击的字母小,则添加数据长度
      if (data[item].groupName < byte) {
        dataLength = dataLength + data[item].users.length;
      }
      // 如果该字母有内容,则加上它的字母长度
      if (data[item].users.length >= 1 && data[item].groupName != byte) {
        byteLength = byteLength + 1;
      }
      // 如果该字母等于点击的字母,则中断循环
      if (data[item].groupName == byte) {
        break;
      }
    }

    console.log("title 长度为:" + byteLength);
    console.log("data 条数为:" + dataLength);

    console.log("\n现在数组为:");
    console.log(data);

    wx.pageScrollTo({
      // 滚动高度
      scrollTop: byteLength * (44 / this.data.equipmentOneRpx) + dataLength * (120 / this.data.equipmentOneRpx)
    })
  },

  /**
   * 生命周期函数--监听页面加载
   */
  onLoad: function (options) {

    console.log("\n通讯录");

    // 设备信息
    wx.getSystemInfo({
      success: res => {
        console.log("\n设备信息为:");
        console.log(res);

        let equipmentOneRpx = 750 / res.windowWidth;
        console.log("换算信息:1rpx = " + equipmentOneRpx + "px");
        this.setData({
          equipmentOneRpx: equipmentOneRpx
        })
      },
    })
  }
})
复制代码

我们在 onLoad 中获取到用户设备的信息,然后计算出 1rpx 等于多少 px 。在 iphone6 中, 1rpx = 2px 。我们只需要将 css 中写的样式高度 / 比例,就能动态计算我们的高度,从而实现滚动到目标位置的效果。

—————— 分割线 ——————

现在,我们开始 真拼音导航 功能的实现:

首先,我们应该考虑到,正常加载模式与拼音导航模式,会对 contactsData 的使用产生冲突:假如用户划拉了几页数据,然后进入拼音导航,那么,用户想下拉刷新页面的时候,可能就加载原本数据了,而不是加载该字母上面的数据……为此,我们在第一次加载拼音模式的时候,应该清空 contactsData (多了也不行,因为用户可能点击其他字母)。

然后,我们关闭正常模式,并开启拼音导航模式,设置拼音导航模式不是第一次加载了。

接着,我们遍历空数据和新数据,删除重复数据后,将数据添加到 contactsData 中。

最后,我们才用上我们前面的页面滚动效果,滚动到我们希望跳转到的位置。

以上,考虑到步骤繁杂,我们应该使用 Promise 来实现:

addressList.js 代码片段

Page({

  /**
   * 页面的初始数据
   */
  data: {
    /**
     * 拼音导航功能
     * letters - 导航字母
     * equipmentOneRpx - 设备中 1rpx 为多少 px
     * firstEntryPinyinModel - 第一次进入拼音导航模式
     */
    letters: ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z'],
    equipmentOneRpx: '',
    firstEntryPinyinModel: true,
  },

  /**
   * 拼音导航功能
   * pininNav - 点击字母
   */
  pinyinNav(e) {

    console.log("\n【API - 拼音导航】");

    let byte = e.currentTarget.dataset.byte;

    // 开启 Promise
    const promise = new Promise((resolve, reject) => {

      console.log("\n第一步:清空原数据");

      let contactsData = [
        {
          groupName: 'A',
          users: []
        },
        {
          groupName: 'B',
          users: []
        },
        {
          groupName: 'C',
          users: []
        },
        {
          groupName: 'D',
          users: []
        },
        {
          groupName: 'E',
          users: []
        },
        {
          groupName: 'F',
          users: []
        },
        {
          groupName: 'G',
          users: []
        },
        {
          groupName: 'H',
          users: []
        },
        {
          groupName: 'I',
          users: []
        },
        {
          groupName: 'J',
          users: []
        },
        {
          groupName: 'K',
          users: []
        },
        {
          groupName: 'L',
          users: []
        },
        {
          groupName: 'M',
          users: []
        },
        {
          groupName: 'N',
          users: []
        },
        {
          groupName: 'O',
          users: []
        },
        {
          groupName: 'P',
          users: []
        },
        {
          groupName: 'Q',
          users: []
        },
        {
          groupName: 'R',
          users: []
        },
        {
          groupName: 'S',
          users: []
        },
        {
          groupName: 'T',
          users: []
        },
        {
          groupName: 'U',
          users: []
        },
        {
          groupName: 'V',
          users: []
        },
        {
          groupName: 'W',
          users: []
        },
        {
          groupName: 'X',
          users: []
        },
        {
          groupName: 'Y',
          users: []
        },
        {
          groupName: 'Z',
          users: []
        }
      ];

      if (this.data.firstEntryPinyinModel) { // 为防止无法下拉,第一次进入拼音导航模式,清空原数据
        this.setData({
          contactsData: contactsData
        })
      }

      // 告诉下一步可以执行了
      let success = true;
      resolve(success);

    }).then(() => {

      console.log("\n第二步:开启拼音导航模式");

      this.setData({
        normalModel: false,
        pinyinNavModel: true,
        firstEntryPinyinModel: false,
      })

    }).then(() => {

      console.log("\n第三步:判断并添加数据");

      let data = this.data.contactsData;
      console.log("\n现在的数据有:");
      console.log(data);

      let newData = [
        {
          userName: '克狸',
          userPhone: '18811121112',
          pinyin: 'keli'
        },
        {
          userName: '拉狸',
          userPhone: '18811131113',
          pinyin: 'lali'
        },
        {
          userName: '磨狸',
          userPhone: '18811141114',
          pinyin: 'moli'
        },
        {
          userName: '尼狸',
          userPhone: '18811151115',
          pinyin: 'nili'
        },
        {
          userName: '噢狸',
          userPhone: '18811161116',
          pinyin: 'oli'
        },
        {
          userName: '皮皮狸',
          userPhone: '18811171117',
          pinyin: 'pipili'
        },
        {
          userName: '曲狸',
          userPhone: '18811181118',
          pinyin: 'quli'
        },
        {
          userName: '任狸',
          userPhone: '18811191119',
          pinyin: 'renli'
        },
        {
          userName: '司马狸',
          userPhone: '18811211121',
          pinyin: 'simali'
        },
        {
          userName: '提狸',
          userPhone: '18811221122',
          pinyin: 'tili'
        }
      ]
      console.log("\n新数据有:");
      console.log(newData);

      console.log("\n组合数据:");
      for (let groupInfo in data) { // 循环原数据
        for (let item in newData) { // 循环新数据

          if (data[groupInfo].groupName == newData[item].pinyin.substr(0, 1).toUpperCase()) { // 如果新数据字母 与 原数据字母相同

            // 清君侧,删除重复数据
            // 循环用户数据,判断 新数据的用户名 是否存在于用户数据,如果存在则删除之
            for (let userInfo in data[groupInfo].users) { // 循环用户原数据
              console.log(newData);
              if (newData.length > 1) {
                if (data[groupInfo].users[userInfo].userName == newData[item].userName) { // 判断 新数据的用户名 是否存在于原用户数据
                  newData.splice(item, 1);
                }
              }
            }

            if (newData.length > 1) { // 判断是否还有数据
              if (data[groupInfo].groupName == newData[item].pinyin.substr(0, 1).toUpperCase()) { // 再判断一次新数据与旧数据字母是否相同
                console.log("添加到组:【" + data[groupInfo].groupName + "】");
                data[groupInfo].users.push(newData[item]);
                console.log(data);
              }
            }

          }
        }
      }

      this.setData({
        contactsData: data,
      })

    }).then(() => {

      console.log("\n第四步:滚动页面");

      let dataLength = 0;
      let byteLength = 0;

      let data = this.data.contactsData;
      console.log(data);

      for (let item in data) {
        // 如果该字母比点击的字母小,则添加数据长度
        if (data[item].groupName < byte) {
          dataLength = dataLength + data[item].users.length;
        }
        // 如果该字母有内容,则加上它的字母长度
        if (data[item].users.length >= 1 && data[item].groupName != byte) {
          byteLength = byteLength + 1;
        }
        // 如果该字母等于点击的字母,则中断循环
        if (data[item].groupName == byte) {
          break;
        }
      }

      console.log("title 长度为:" + byteLength);
      console.log("data 条数为:" + dataLength);

      console.log("\n现在数组为:");
      console.log(data);

      wx.pageScrollTo({
        // 滚动高度
        scrollTop: byteLength * (44 / this.data.equipmentOneRpx) + dataLength * (120 / this.data.equipmentOneRpx)
      })

    })

  }
})
复制代码

如此,我们就实现了拼音导航的点击加载了!下面,我们紧接着将拼音导航功能的 下拉刷新上拉加载 搞定吧~

关于下拉刷新,我们需要现在 json 中开启下拉刷新的功能:

addressList.json
{
  "backgroundTextStyle": "light",
  "navigationBarBackgroundColor": "#fff",
  "navigationBarTitleText": "通讯录",
  "navigationBarTextStyle": "black",
  "enablePullDownRefresh": true,
  "usingComponents": {
    "navBar": "../../component/navBar/navBar"
  }
}
复制代码

然后,我们在 onPullDownRefresh 中实现代码效果即可:

addressList.js 代码片段

Page({
  /**
   * 页面相关事件处理函数--监听用户下拉动作
   */
  onPullDownRefresh: function () {

    if (this.data.pinyinNavModel) { // 拼音下拉刷新

      console.log("\n【API - 拼音下拉刷新】");

      let data = this.data.contactsData;
      console.log("\n现在的数据有:");
      console.log(data);

      let newData = [
        {
          userName: '阿狸',
          userPhone: '18811111111',
          pinyin: 'ali'
        },
        {
          userName: '贝吉塔',
          userPhone: '18822222222',
          pinyin: 'beijita'
        },
        {
          userName: '楚怡',
          userPhone: '18833333333',
          pinyin: 'chuyi'
        },
        {
          userName: '邓婕',
          userPhone: '18844444444',
          pinyin: 'dengjie'
        },
        {
          userName: '尔康',
          userPhone: '18855555555',
          pinyin: 'erkang'
        },
        {
          userName: '福狸',
          userPhone: '18866666666',
          pinyin: 'fuli'
        },
        {
          userName: '古狸',
          userPhone: '18877777777',
          pinyin: 'guli'
        },
        {
          userName: '哈狸',
          userPhone: '18888888888',
          pinyin: 'hali'
        },
        {
          userName: 'i狸',
          userPhone: '18899999999',
          pinyin: 'ili'
        },
        {
          userName: '激狸',
          userPhone: '18800000000',
          pinyin: 'jli'
        },
      ]
      console.log("\n新数据有:");
      console.log(newData);

      console.log("\n组合数据:");
      for (let groupInfo in data) { // 循环原数据
        for (let item in newData) { // 循环新数据

          if (data[groupInfo].groupName == newData[item].pinyin.substr(0, 1).toUpperCase()) { // 如果新数据字母 与 原数据字母相同

            // 清君侧,删除重复数据
            // 循环用户数据,判断 新数据的用户名 是否存在于用户数据,如果存在则删除之
            for (let userInfo in data[groupInfo].users) { // 循环用户原数据
              if (newData.length > 1) {
                if (data[groupInfo].users[userInfo].userName == newData[item].userName) { // 判断 新数据的用户名 是否存在于原用户数据
                  newData.splice(item, 1);
                }
              }
            }

            if (newData.length > 1) { // 判断是否还有数据
              if (data[groupInfo].groupName == newData[item].pinyin.substr(0, 1).toUpperCase()) { // 再判断一次新数据与旧数据字母是否相同
                console.log("添加到组:【" + data[groupInfo].groupName + "】");
                data[groupInfo].users.unshift(newData[item]);
                console.log(data);
              }
            }

          }
        }
      }

      this.setData({
        contactsData: data
      })

    }
  }
})
复制代码

同时,拼音导航功能的上拉功能实现如下:

addressList.js 代码片段

Page({
onReachBottom: function () {
    if (this.data.normalModel) { // 正常模式上拉

      console.log("\n正常模式上拉");

    } else if (this.data.searchModel) { // 搜索模式上拉
      console.log("\n搜索模式上拉:");
    } else if (this.data.pinyinNavModel) { // 拼音模式上拉
      
      console.log("\n拼音模式上拉");

      let data = this.data.contactsData;
      console.log("\n现在的数据有:");
      console.log(data);

      let newData = [
        {
          userName: 'u狸',
          userPhone: '18811311131',
          pinyin: 'uli'
        },
        {
          userName: 'v狸',
          userPhone: '18811321132',
          pinyin: 'vli'
        },
        {
          userName: '无狸',
          userPhone: '18811331133',
          pinyin: 'wuli'
        },
        {
          userName: '犀狸',
          userPhone: '18811341134',
          pinyin: 'xili'
        },
        {
          userName: '毅狸',
          userPhone: '18811351135',
          pinyin: 'yili'
        },
        {
          userName: '醉狸',
          userPhone: '18811361136',
          pinyin: 'zuili'
        }
      ]
      console.log("\n新数据有:");
      console.log(newData);

      console.log("\n组合数据:");
      for (let groupInfo in data) { // 循环原数据
        for (let item in newData) { // 循环新数据

          if (data[groupInfo].groupName == newData[item].pinyin.substr(0, 1).toUpperCase()) { // 如果新数据字母 与 原数据字母相同

            // 清君侧,删除重复数据
            // 循环用户数据,判断 新数据的用户名 是否存在于用户数据,如果存在则删除之
            for (let userInfo in data[groupInfo].users) { // 循环用户原数据
              console.log(newData);
              if (newData.length > 1) {
                if (data[groupInfo].users[userInfo].userName == newData[item].userName) { // 判断 新数据的用户名 是否存在于原用户数据
                  newData.splice(item, 1);
                }
              }
            }

            if (newData.length > 1) { // 判断是否还有数据
              if (data[groupInfo].groupName == newData[item].pinyin.substr(0, 1).toUpperCase()) { // 再判断一次新数据与旧数据字母是否相同
                console.log("添加到组:【" + data[groupInfo].groupName + "】");
                data[groupInfo].users.push(newData[item]);
                console.log(data);
              }
            }

          }
        }
      }

      this.setData({
        contactsData: data
      })

    }
  }
})
复制代码

如上,我们成功实现拼音导航全部功能!!!

3.2.11 一统天下 - 归纳总结

天下大势,分久必合,合久必分。

写到这里,我们的通讯录已然完结,在此附上 jsliang 的代码地址: 项目地址

然而,这是结束吗?并不是,我们的通讯录,还有个功能未实现:

如何在新增、删除的时候,对新增的字母进行排序,并导航到具体位置?

在工作项目的开发中, jsliang 曾想到将新增的中文昵称转换为拼音,然后通过二分查找法,找到对应的位置并进行插入……

但是,正印了那句话: 我的能力,可以造火箭,我却只有敲钉子的时间!

时间是一切程序猿的杀手,新增排序,咱们,有缘再会!

四 项目地址

不定期更新,详情可关注 jsliangGitHub 地址

最后的最后,奉上上面实例中的地址:

项目地址

撰文不易,如果文章对小伙伴有帮助,希望小伙伴们给勤劳敲代码、辛苦撰文的 jsliang 进行微信打赏,让他更有动力写出更丰富、更精彩的文章,谢谢~

纵横开阖-微信小程序之通讯录全攻略

纵横开阖-微信小程序之通讯录全攻略

梁峻荣 采用 知识共享 署名-非商业性使用-相同方式共享 4.0 国际 许可协议 进行许可。

基于 github.om/LiangJunron… 上的作品创作。

本许可协议授权之外的使用权限可以从 creativecommons.org/licenses/by… 处获得。


以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

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

Beginning Apache Struts

Beginning Apache Struts

Arnold Doray / Apress / 2006-02-20 / USD 44.99

Beginning Apache Struts will provide you a working knowledge of Apache Struts 1.2. This book is ideal for you Java programmers who have some JSP familiarity, but little or no prior experience with Ser......一起来看看 《Beginning Apache Struts》 这本书的介绍吧!

Markdown 在线编辑器
Markdown 在线编辑器

Markdown 在线编辑器

RGB CMYK 转换工具
RGB CMYK 转换工具

RGB CMYK 互转工具

HEX CMYK 转换工具
HEX CMYK 转换工具

HEX CMYK 互转工具