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

jsliang發表於2019-03-03

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 – (預設)不換行
  • 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("
【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("
清除搜尋");
    this.setData({
      searchVal: ``
    })
  },

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

  /**
   * 生命週期函式--監聽頁面載入
   */
  onLoad: function (options) {
    console.log("
通訊錄");
  },

  /**
   * 頁面上拉觸底事件的處理函式
   */
  onReachBottom: function () {
    if (this.data.normalModel) { // 正常模式上拉
      console.log("
正常模式上拉")
    } else if (this.data.searchModel) { // 搜尋模式上拉

      console.log("
搜尋模式上拉:");

      // 新資料
      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("
拼音模式上拉");
    }
  },
})
複製程式碼

 到此,我們就實現了搜尋功能。儘管它還有點小 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("
通訊錄");

    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("
【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("
【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("
【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("
正常模式上拉");
    } else if (this.data.searchModel) { // 搜尋模式上拉
      console.log("
搜尋模式上拉");
    } else if (this.data.pinyinNavModel) { // 拼音模式上拉
      console.log("
拼音模式上拉");
    }
  }
})
複製程式碼

 那麼,我們只需要參考 onLoad 中的正常載入方式,往正常模式中模擬資料,實現上拉效果,就 OK 了:

addressList.js 程式碼片段

Page({

  /**
   * 頁面的初始資料
   */
  data: {
    /**
     * 上拉觸底
     * normalModelNoData - 正常模式沒資料載入了
     */
    normalModelNoData: false,
  },

  /**
   * 頁面上拉觸底事件的處理函式
   */
  onReachBottom: function () {
    if (this.data.normalModel) { // 正常模式上拉
      
      console.log("
正常模式上拉");

      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("
搜尋模式上拉:");
    } else if (this.data.pinyinNavModel) { // 拼音模式上拉
      console.log("
拼音模式上拉");
    }
  }
})
複製程式碼

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("
【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("
現在陣列為:");
    console.log(data);

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

  /**
   * 生命週期函式--監聽頁面載入
   */
  onLoad: function (options) {

    console.log("
通訊錄");

    // 裝置資訊
    wx.getSystemInfo({
      success: res => {
        console.log("
裝置資訊為:");
        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("
【API - 拼音導航】");

    let byte = e.currentTarget.dataset.byte;

    // 開啟 Promise
    const promise = new Promise((resolve, reject) => {

      console.log("
第一步:清空原資料");

      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("
第二步:開啟拼音導航模式");

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

    }).then(() => {

      console.log("
第三步:判斷並新增資料");

      let data = this.data.contactsData;
      console.log("
現在的資料有:");
      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("
新資料有:");
      console.log(newData);

      console.log("
組合資料:");
      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("
第四步:滾動頁面");

      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("
現在陣列為:");
      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("
【API - 拼音下拉重新整理】");

      let data = this.data.contactsData;
      console.log("
現在的資料有:");
      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("
新資料有:");
      console.log(newData);

      console.log("
組合資料:");
      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("
正常模式上拉");

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

      let data = this.data.contactsData;
      console.log("
現在的資料有:");
      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("
新資料有:");
      console.log(newData);

      console.log("
組合資料:");
      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… 處獲得。

相關文章