縱橫開闔-微信小程序之通信錄全攻略

Create by jsliang on 2018-11-21 20:46:36
Recently revised in 2018-11-25 00:24:14css


Hello 小夥伴們,若是以爲本文還不錯,記得給個 star , 大家的 star 是我學習的動力!GitHub 地址html


【2019-08-16】Hello 小夥伴們,因爲 jsliang 對文檔庫進行了重構,這篇文章的一些連接可能失效,而 jsliang 沒有精力維護掘金這邊的舊文章,對此深感抱歉。請須要獲取最新文章的小夥伴,點擊上面的 GitHub 地址,去文檔庫查看調整後的文章。前端


開篇點題
 這是一篇專研微信小程序各類功能實現的文章,例如佈局、通信錄、組件之底部導航欄等……
 感受不錯的小夥伴,點贊點 Star走一波;
 感受文章有誤的小夥伴,評論區、QQ羣 溜達一番。
 虛心求教,不勝感激~git


項目成果圖es6


一 目錄

不折騰的前端,和鹹魚有什麼區別github

文章篇幅甚多,請利用好目錄進行跳轉!編程

目錄
一 目錄
二 前言
三 功能列表
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 一統天下 - 概括總結
四 項目地址

二 前言

返回目錄json


 寫文章無形中也會磨鍊本身的表達能力。
 這周 (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 - (默認)不換行
  • wrap - 換行,第一行在上方
  • 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. justify-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 種狀況,其中:

  • 搜索功能 - part1part2part4part5
  • 彈窗新增功能 - part1part3part4part5part6
  • 彈窗修改功能 - part1part3part4part5part7
  • 刪除功能 - 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 千里尋敵 - 搜索功能

返回目錄


 本章節實現效果:


 實現思路、編碼及代碼講解:

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 進行微信打賞,讓他更有動力寫出更豐富、更精彩的文章,謝謝~


知識共享許可協議
jsliang 的文檔庫梁峻榮 採用 知識共享 署名-非商業性使用-相同方式共享 4.0 國際 許可協議進行許可。
基於github.om/LiangJunron…上的做品創做。
本許可協議受權以外的使用權限能夠從 creativecommons.org/licenses/by… 處得到。

相關文章
相關標籤/搜索