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
文章篇幅甚多,请利用好目录进行跳转!编程
返回目录json
写文章无形中也会磨炼本身的表达能力。
这周 (2018-11-19
) 在开发微信小程序的定制 通信录 时,忽然发现 微信小程序 bug 集中营 这篇文章不能再继续写了,由于它变得 臃肿、丑陋 且 难维护,就连我这个写做人都感慨:若是没有 Ctrl + F
,以及个人 目录 写得还不错,我真心不想再翻这篇文章。
为此,jsliang 单独开了一篇文章:微信小程序功能清单。用来记录小程序各类功能的实现,例如布局、通信录、底部导航栏……
而后嘛,为了能吸引小伙伴点进来瞅瞅,起个标新立异的标题吧:微信小程序之奇技淫巧。小程序
返回目录后端
为了小伙伴能快速了解代码中的意思,小伙伴能够去该 项目地址 下载代码到本地运行查看。
敲了再说
敲 看
一 一
遍 遍
? ?
天 谁
差 都
地 可
别 以
! !
顺带附上一些资源网站:
若是你发现你的 CSS
水平还处于 float
布局,你会发如今小程序中你举步维艰,由于单单只用浮动布局,在小程序中它很差用。
因此,Flex
布局,是你的不二选择:布局的传统解决方案,基于盒状模型,依赖 display
属性 + position
属性 + float
属性。它对于那些特殊布局很是不方便,好比,垂直居中就不容易实现。而 Flex
布局。又称弹性布局,能够简便、完整、响应式地实现各类页面布局。
网上较好的教程有:
若是你想全面了解 Flex
,推荐去看上面的文章。
若是你已经了解 Flex
布局,点击 返回目录 寻找更多精彩!
若是你想快速复习浏览 Flex
布局,那么,Here we go
~
Flex
最终实现效果:
万丈高楼平地起,熟悉 Flex
须要先了解下面这 7
个 CSS
属性:
/* 设置 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;
复制代码
下面咱们详细分析这些元素的状况:
flex-direction
:决定主轴的方向row
- (默认)水平方向,起点在左端row-reverse
- 水平方向,起点在右端column
- 垂直方向,起点在上沿column-reverse
- 垂直方向,起点在下沿display: flex;
flex-direction: row | row-reverse | column | column-reverse;
复制代码
flex-wrap
:一条轴线(一行)排不下时如何解决nowrap
- (默认)不换行wrap
- 换行,第一行在上方wrap-reverse
- 换行,第一行在下方display: flex;
flex-wrap: nowrap | wrap | wrap-reverse;
复制代码
flex-flow
:flex-flow = flex-direction + flex-wrap。即 flex-flow 是这两个属性的合集row nowrap
- (默认)水平方向,起点在左端,不换行display: flex;
flex-flow: <flex-direction> || <flex-wrap>;
复制代码
详解参考 1
和 2
justify-content
:定义项目在主轴上的对齐方式flex-start
- 左边对齐flex-end
- 右边对齐center
- 居中对齐space-between
- 两端对齐,空格在中间space-around
- 空格环绕display: flex;
justify-content: flex-start | flex-end | center | space-between | space-around;
复制代码
align-items
:定义项目在交叉轴上如何对齐flex-start
- 顶部对齐,即文字图片等顶部同一条线上flex-end
- 底部对其,即文字图片等底部在同一条线上center
- 中间对其,即文字图片无论多高,都拿它们的中间放在同一条线上stretch
- 将文字图片充满整个容器的高度,强制统一baseline
- 将每项的第一行文字作统一在一条线上对齐display: flex;
align-items: flex-start | flex-end | center | stretch | baseline;
复制代码
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;
复制代码
实现效果以下:
如图,这是咱们要实现的左右布局效果。那么,在微信小程序要怎么作呢?
*.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;
}
复制代码
实现效果以下:
如图,这是咱们要实现的混合布局效果,那么在微信小程序中要如何编程呢?
*.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;
}
复制代码
不知道小伙伴们在平常开发中,有没有碰到各类稀奇古怪的功能效果,咱们以为难以想象,可是在项目经理的眼中它倒是能 “知足客户需求” 的。
因此,拿到 “奇怪的” 需求清单的时候不要恐慌,咱们仔细分析,总能找到它的破绽,从而完成咱们的任务。
通信录功能的开发以下:
开发时间:4 天
实现效果:
工欲善其事,必先利其器。
首先,咱们先将该页面命名为:addressList
,并编写它的 json
门面:
addressList.json
{
"backgroundTextStyle": "light",
"navigationBarBackgroundColor": "#fff",
"navigationBarTitleText": "通信录",
"navigationBarTextStyle": "black"
}
复制代码
接着,咱们明确须要实现的功能点:
而后,咱们明确下页面布局:
如上图,它主要分三大块:头部、内容区、底部。
最后,咱们根据功能实现及页面布局编写 wxml
的布局:
wxml 骨架
<!-- part1 - 搜索区域 -->
<view class="search"></view>
<!-- part2 - 搜索结果 -->
<view class="search-result"></view>
<!-- part3 - 内容区域 -->
<view class="contacts-list"></view>
<!-- part4 - 拼音导航 -->
<view class="pinyin-nav"></view>
<!-- part5 - 底部导航 -->
<view class="bottom-nav"></view>
<!-- part6 - 新增弹窗 -->
<view class="add-prompt"></view>
<!-- part7 - 修改弹窗 -->
<view class="edit-prompt"></view>
复制代码
如上,咱们将页面分为 7 种状况,其中:
part1
、part2
、part4
、part5
part1
、part3
、part4
、part5
、part6
part1
、part3
、part4
、part5
、part7
part1
、part3
、part4
、part5
part1
、part3
、part4
、part5
part1
、part3
、part4
、part5
请注意,出现的 part
部分代表在这种模式下,页面要显示的 part
都有哪些,其余的则暂时隐藏,而加粗的意味着这是这个功能特有的部分。为此,咱们应该在 js
的 data
中定义好这些模式:
js 代码片断
Page({
data: {
/**
* 功能模式
* normalModel - 正常模式
* addModel - 新增模式
* editModel - 修改模式
* deleteModel - 删除模式
* searchModel - 搜索模式
* pinyinNavModel - 拼音导航模式
*/
normalModel: false,
addModel: false,
editModel: false,
deleteModel: false,
searchModel: true,
pinyinNavModel: false,
}
})
复制代码
这样,咱们除了底部导航栏外,为其余功能定义了一个模式,正常状况下咱们开启 normalModel
,其余暂时关闭。
在下文中,咱们将根据模式的开启与关闭,显示/隐藏某些内容,并进行数据的管理,请小伙伴们稍微理解下这种思路。
wxml
代码:Go to wxmlwxss
代码:Go to wxssjs
代码`:Go to js本章节实现效果:
实现思路、编码及代码讲解:
wxml
与 wxss
结构上。 首先,咱们经过 fixed
定位,将 search-form
固定在顶部。
而后,咱们将 search-form
其内部分为 搜索区 search
与 功能区 action
。
接着,咱们将 search
分为 假的搜索区 search-model-one
与 真的搜索区 search-model-two
。为何要分两种状况呢?由于这样咱们就不用烦恼 input
的 placeholder
一会居中一会靠边要怎么区分,思路不容易乱。
最后,根据功能,咱们逐步完善 wxml
与 wxss
代码。
<!-- 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>
复制代码
/* 全局样式 */
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;
}
复制代码
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
来禁止继续加载。
这样,咱们就完美搞定了搜索功能的实现。
本章节实现效果:
众所周知,微信小程序的子页面(除了设置 tabBar
的页面)是没有底部导航栏的。那么,咱们要如何设计,才能编写一个 自定义的底部导航栏 呢?
在平常开发中,咱们经过 fixed
布局,在页面实现一个 自定义的底部导航栏 是很容易的。
可是,考虑到其余页面可能也须要使用这个底部导航栏,咱们就须要想办法将其封装成组件了:
微信小程序 - 自定义组件
是的,微信小程序官方文档中是存在这个东西的。固然,仅有官方文档,是知足不了个人,至于过程当中我百度了几篇文章来辅助写出下面的代码,你猜?
下面贴出实现代码及如何使用:
首先,在根目录中新建 component
目录,用来存放咱们项目的组件。
而后,咱们新建 navBar
目录,用来存放咱们的组件 navBar
。
最后,咱们新建 Component
为 navBar
。
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": {}
}
复制代码
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
的步骤就好了。
固然,咱们须要根据须要活跃的位置,进行 homeActive
、exploreActive
、userActive
这三个活跃状态与否的设置。
这样,咱们就实现了底部导航栏组件的开发及引用。
本章节实现效果:
弹窗?微信小程序就有啊,为啥不用它的呢?
类型 | 说明 | 地址 |
---|---|---|
模态弹窗 | 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;
}
复制代码
这样,咱们就能够经过控制 addModel
的 true
或者 false
,来显示隐藏新增弹窗。
同理,咱们能够依法炮制经过 editModel
控制修改弹窗。
文章写到这里,咱们须要整理下咱们都完成了什么,还缺什么?
如上,咱们实现了:
那么,咱们还缺乏:
很好!咱们实现了一半功能了!可是,小伙伴有没有发现,咱们的主内容区是空白的。
因此,为了剩下的功能实现,咱们应该编写下 内容区域,并进行页面的数据加载:
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()
这个内置的页面加载函数中,虚拟了接口返回的第一页数据,最后将它循环判断,放在不一样的字母中,从而实现了首页的加载。
因此,咱们能够开始实现咱们其余的功能咯~
本章节实现效果:
如上图,咱们实现了新增的功能。那么,它在代码中是如何实现的呢?
首先,咱们要知道弹窗效果是如何出来的:
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: ''
})
}
}
})
复制代码
到此,咱们就实现了新增的功能!
本章节实现效果:
在新增功能的开发后,咱们的修改功能就显得比较容易了。
首先,咱们整理下修改的思路:
因此,在 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;
}
}
}
}
}
}
})
复制代码
这样,咱们就实现了弹窗修改功能!
本章节实现效果:
若是有小伙伴是跟着前面章节一步一步走下来的,会发现我在写 搜索功能 的时候,写上了删除模式 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("取消删除!");
}
}
})
}
})
复制代码
本章节实现效果:
写到这里,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拼音模式上拉");
}
}
})
复制代码
本章节实现效果:
如今,咱们完成最后且最重要的一步,实现 拼音导航 功能。
首先,咱们先实现拼音导航的布局:
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
})
}
}
})
复制代码
如上,咱们成功实现拼音导航所有功能!!!
天下大势,分久必合,合久必分。
写到这里,咱们的通信录已然完结,在此附上 jsliang 的代码地址:项目地址
然而,这是结束吗?并非,咱们的通信录,还有个功能未实现:
如何在新增、删除的时候,对新增的字母进行排序,并导航到具体位置?
在工做项目的开发中,jsliang 曾想到将新增的中文昵称转换为拼音,而后经过二分查找法,找到对应的位置并进行插入……
可是,正印了那句话:个人能力,能够造火箭,我却只有敲钉子的时间!
时间是一切程序猿的杀手,新增排序,我们,有缘再会!
不按期更新,详情可关注 jsliang 的 GitHub 地址
最后的最后,奉上上面实例中的地址:
撰文不易,若是文章对小伙伴有帮助,但愿小伙伴们给勤劳敲代码、辛苦撰文的 jsliang 进行微信打赏,让他更有动力写出更丰富、更精彩的文章,谢谢~
jsliang 的文档库 由 梁峻荣 采用 知识共享 署名-非商业性使用-相同方式共享 4.0 国际 许可协议进行许可。
基于github.om/LiangJunron…上的做品创做。
本许可协议受权以外的使用权限能够从 creativecommons.org/licenses/by… 处得到。