继上一篇的小程序文档博客,这篇博客才算是真正的开始吧。
先看看显示效果
这里直接按设计图设计的,大部分内容也不难,就不详细写了。主要有个问题就是 iconfont 的使用,并不能把它当成 icon 设置属性,图片的属性需要通过 font-size 和 color 来设置。
登录下拉框的实现
登录界面没什么难度,这里稍微复杂点的就是这个下拉框的实现了,直接看代码。
- HTML
<view class="input-container">
<!-- 下拉的历史用户 -->
<view class='user-list-container' wx:if="{{isShowUsers}}"
style='height:{{historyUsers.length>5?400:historyUsers.length*80}}rpx;'>
<view class='list-item row-center' style='{{index==historyUsers.length-1&&"border:0;"}}'
wx:for='{{historyUsers}}' wx:key='this'>
<view style="flex:1" data-index='{{index}}' catchtap='selectUser'>
{{item.username}}
</view>
<van-icon data-index='{{index}}' name="close" size="32rpx" catchtap='deleteUser' />
</view>
</view>
<!-- 账号输入框 -->
<view class="input-button-container row-center">
<input class="input flex1" model:value="{{ username }}" type="text" placeholder="请输入用户名"
bindinput="inputChange" />
<view catchtap='showHistoryUsers'>
<van-icon name="{{isShowUsers?'arrow-up':'arrow-down'}}" size="40rpx" color="#949494FF" />
</view>
</view>
<!-- 密码输入框 -->
<view class="input-button-container margin-top-24 row-center">
<input class="input flex1" model:value="{{ password }}" password="{{!isDisplayPassword}}" placeholder="请输入密码"
bindinput="inputChange" />
<view catchtap="displayPassword">
<van-icon name="{{isDisplayPassword?'eye-o':'closed-eye'}}" size="40rpx" color="#949494FF" />
</view>
</view>
- CSS
.input-container {
width: auto;
position: relative;
margin: 0 80rpx;
margin-top: 64rpx;
}
.user-list-container {
height: 0;
width: 100%;
position: absolute;
top: 104rpx;
border: 2rpx solid #efefef;
border-top: 0;
box-sizing: border-box;
overflow-y: auto;
background: #fff;
z-index: 100;
transition: height 0.3s;
}
.list-item {
height: 80rpx;
border-bottom: 2rpx solid #efefef;
padding: 10rpx 40rpx;
box-sizing: border-box;
font-size: 24rpx;
font-family: PingFangSC-Regular, PingFang SC;
font-weight: 400;
color: #949494;
line-height: 40rpx;
}
.input {
height: 104rpx;
font-size: 32rpx;
font-family: PingFangSC-Regular, PingFang SC;
font-weight: 400;
line-height: 44rpx;
border: 0rpx;
}
.input-button-container{
height: 106rpx;
overflow: hidden;
position: relative;
border-bottom: 2rpx solid #E5E5E5;
}
- Page.js
Page({
data: {
//显示密码
isDisplayPassword: false,
//展示历史用户
isShowUsers: false,
//输入框
username: "",
password: "",
historyUsers: [{
username: '张三',
password: '123'
},{
username: '李四',
password: '123'
},{
username: '王麻子',
password: '123'
},{
username: '赵子龙',
password: '123'
},{
username: '去要钱',
password: '123'
},{
username: '孙子',
password: '123'
}],
},
onLoad: function (options) {
//读取历史用户数据
this.loadUsers();
},
onShow: function () {
if (this.data.historyUsers.length > 0) {
let lastIdx = this.data.historyUsers.length - 1
this.setData({
username: this.data.historyUsers[lastIdx].username,
password: this.data.historyUsers[lastIdx].password
})
}
},
//显示密码
displayPassword(e) {
this.setData({
isDisplayPassword: !this.data.isDisplayPassword
})
},
//显示历史用户列表
showHistoryUsers() {
if (this.data.historyUsers.length > 0) {
this.setData({
isShowUsers: !this.data.isShowUsers
});
} else {
app.showErrToast("暂无历史用户")
}
},
loadUsers() {
let users = wx.getStorageSync("historyUsers")
if (users) {
users.forEach(element => {
//解密密码
element.password = encrypt.decryptResult(element.password, encryptInfo);
});
//更新显示数据
this.setData({
historyUsers: users
})
}
},
saveUser(user) {
let users = this.data.historyUsers;
//如果已经存在删除再插入到最后
let index = -1
for (let i = 0; i < users.length; i++) {
let temp = users[i]
if (user.username == temp.username) {
index = i
break
}
}
if (index >= 0) {
users.splice(index, 1)
}
//最后一个表示最近登录
users.push(user)
//更新显示数据
this.setData({
historyUsers: users
})
//保存到本地
this.saveUsers();
},
//深拷贝数组,修改密码保存
saveUsers() {
let users = this.data.historyUsers;
let encryptUsers = []
users.forEach(element => {
encryptUsers.push({
username: element.username,
password: encrypt.encryptParams(element.password, encryptInfo)
})
});
//储存到本地
wx.setStorageSync("historyUsers", encryptUsers);
},
selectUser(e) {
let idx = e.currentTarget.dataset.index;
let user = this.data.historyUsers[idx];
this.setData({
username: user.username,
password: user.password,
isShowUsers: false
});
},
deleteUser(e) {
let that = this
let index = e.currentTarget.dataset.index
let users = that.data.historyUsers
let name = users[index].username
wx.showModal({
content: '确认删除' + name + '?',
success: function (res) {
if (res.confirm) {
//从数组中删除,改变数组大小
users.splice(index, 1)
//更新显示数据
that.setData({
historyUsers: users,
isShowUsers: !that.data.isShowUsers
});
//保存到本地
that.saveUsers();
} else if (res.cancel) {
//do nothing;
}
}
})
},
// 登录成功
onLoginSuccess(res, user, password) {
//保存用户
this.saveUser({
username: user,
password: password
})
},
})
这里把代码删减了一些,也没测测行不行,但是这节主要功能应该都在这了,下面详细讲讲。
历史用户框的列表
列表的渲染和安卓有很大的不同,简单了很多,也就是不用自己写适配器了,可以直接双向绑定,妈妈啊,我也想再安卓里面直接这么用。
先看列表的官方文档
https://developers.weixin.qq.com/miniprogram/dev/reference/wxml/list.html
下面是代码,注意 index 和 item 的使用
<view class='list-item row-center' style='{{index==historyUsers.length-1&&"border:0;"}}' wx:for='{{historyUsers}}' wx:key='this'>
<view style="flex:1" data-index='{{index}}' catchtap='selectUser'>
{{item.username}}
</view>
<van-icon data-index='{{index}}' name="close" size="32rpx" catchtap='deleteUser' />
</view>
历史用户框显示与隐藏
从前面的图可以看到,这里的历史用户框是需要隐藏与显示的,这里使用wx:if条件渲染。在 Page 代码中添加 isShowUsers 变量,点击账号框右边的 icon 触发 showHistoryUsers 方法,显示的同时把高度从零变大,做出一种动画效果。
Page({
data: {
//展示历史用户
isShowUsers: false,
historyUsers: [{
username: '张三',
password: '123'
},]
}
//显示历史用户列表
showHistoryUsers() {
if (this.data.historyUsers.length > 0) {
this.setData({
isShowUsers: !this.data.isShowUsers
});
} else {
app.showErrToast("暂无历史用户")
}
},
})
注意这里用 view 将 icon 包裹起来,扩大点击范围,并阻止点击事件向下穿透。
<view class="input-button-container row-center">
<input class="input flex1" model:value="{{ username }}" type="text" placeholder="请输入用户名" bindinput="inputChange" />
<view catchtap='showHistoryUsers'>
<van-icon name="{{isShowUsers?'arrow-up':'arrow-down'}}" size="40rpx" color="#949494FF" />
</view>
</view>
这里动态的设置了历史用户可显示的最多数目,不超过五条有多少显示多少,超过五条,只显示五条。
<!-- 下拉的历史用户 -->
<view class='user-list-container' wx:if="{{isShowUsers}}"
style='height:{{historyUsers.length>5?400:historyUsers.length*80}}rpx;'>
<view class='list-item row-center' style='{{index==historyUsers.length-1&&"border:0;"}}' wx:for='{{historyUsers}}'
wx:key='this'>
<view style="flex:1" data-index='{{index}}' catchtap='selectUser'>
{{item.username}}
</view>
<van-icon data-index='{{index}}' name="close" size="32rpx" catchtap='deleteUser' />
</view>
</view>
这里的动画,就css中最后一句生效的。
.user-list-container {
height: 0;
width: 100%;
position: absolute;
top: 104rpx;
border: 2rpx solid #efefef;
border-top: 0;
box-sizing: border-box;
overflow-y: auto;
background: #fff;
z-index: 100;
transition: height 0.3s;
}
历史用户数据的保存
历史用户肯定是保存在本地的,这里需要用到微信的存储功能,下面是官方文档
https://developers.weixin.qq.com/miniprogram/dev/api/storage/wx.setStorage.html
这里我们需要保存的是登录成功的单个用户数据,但是并没有可以修改保存内容的功能,这就意味着我们要保存整个历史用户数组,其实都一样。
这里因为涉及到密码,公司要求要加密一下。需要注意的是不能直接对原始数组修改,因为对象是唯一的,把密码加密了,显示的时候也就是加密密码了。所以,应该把数据深拷贝一份,也就是重新构建一个对象,就两个字段,不麻烦。
//深拷贝数组,修改密码保存
saveUsers() {
let users = this.data.historyUsers;
let encryptUsers = []
users.forEach(element => {
encryptUsers.push({
username: element.username,
password: encrypt.encryptParams(element.password, encryptInfo)
})
});
//储存到本地
wx.setStorageSync("historyUsers", encryptUsers);
},
这里有一个如何记住最新用户的逻辑,我这写的就简单了,保存的时候先遍历一遍历史用户数组,将已有用户删除,再将新数据添加到末尾。其实这是对的,因为这样写,如果密码更新了,保存的就是准确数据而不是过时数据。
还有就是 splice 函数还是值得一说的,删除一个元素后,这个函数会将数组移动,保持正确的顺序,不然出现一个空的数据真的很头疼。
saveUser(user) {
let users = this.data.historyUsers;
//如果已经存在删除再插入到最后
let index = -1
for (let i = 0; i < users.length; i++) {
let temp = users[i]
if (user.username == temp.username) {
index = i
break
}
}
if (index >= 0) {
users.splice(index, 1)
}
//最后一个表示最近登录
users.push(user)
//更新显示数据
this.setData({
historyUsers: users
})
//保存到本地
this.saveUsers();
},
历史用户数据的载入
历史数据的载入很简单,和保存类似,用同样的 key 取出数据就是了。
loadUsers() {
let users = wx.getStorageSync("historyUsers")
if (users) {
users.forEach(element => {
//解密密码
element.password = encrypt.decryptResult(element.password, encryptInfo);
});
//更新显示数据
this.setData({
historyUsers: users
})
}
},
历史用户数据的删除
历史数据的删除就有点讲头了,虽然叫删除,但是实际上不就是不保存删除的数据么,这样想就简单了。和前面一样使用 splice 函数,去除该项再保存就好,这里有个问题就是获取选中项的index。
前面我们讲到了 index 的使用,这个就是我们选中先要的。
<view class='list-item row-center' style='{{index==historyUsers.length-1&&"border:0;"}}' wx:for='{{historyUsers}}' wx:key='this'>
<view style="flex:1" data-index='{{index}}' catchtap='selectUser'>
{{item.username}}
</view>
<van-icon data-index='{{index}}' name="close" size="32rpx" catchtap='deleteUser' />
</view>
data-index在这个view绑定了index这个数据,catchtap的方法在点击的时候就能获取这个数据
data-index='{{index}}'
catchtap='deleteUser'
这里用了一个对话框确认一下还是很有必要的,删除数据后调用前面保存的方法就可以了
deleteUser(e) {
let that = this
let index = e.currentTarget.dataset.index
let users = that.data.historyUsers
let name = users[index].username
wx.showModal({
content: '确认删除' + name + '?',
success: function (res) {
if (res.confirm) {
//从数组中删除,改变数组大小
users.splice(index, 1)
//更新显示数据
that.setData({
historyUsers: users,
isShowUsers: !that.data.isShowUsers
});
//保存到本地
that.saveUsers();
} else if (res.cancel) {
//do nothing;
}
}
})
},
历史用户数据的选择
选择没什么好说的,就是记得选中后关闭下拉框就好
selectUser(e) {
let idx = e.currentTarget.dataset.index;
let user = this.data.historyUsers[idx];
this.setData({
username: user.username,
password: user.password,
isShowUsers: false
});
},
动态切换密码显示
实际上上面贴的代码,还有一个动态切换密码显示功能,点击密码右边的按钮切换显示密码。
js代码很简单,只是切换了 isDisplayPassword 这个属性的值
//显示密码
displayPassword(e) {
this.setData({
isDisplayPassword: !this.data.isDisplayPassword
})
},
而在HTML代码中,需要根据 isDisplayPassword 这个属性值,切换显示的图标,以及 input 组件的 password 属性,很简单啊。
<view class="input-button-container margin-top-24 row-center">
<input class="input flex1" model:value="{{ password }}" password="{{!isDisplayPassword}}" placeholder="请输入密码"
bindinput="inputChange" />
<view catchtap="displayPassword">
<van-icon name="{{isDisplayPassword?'eye-o':'closed-eye'}}" size="40rpx" color="#949494FF" />
</view>
</view>
结语
到这里,下拉显示历史账号的功能就告一段落了,用起来还是挺秀的。
end
完美撒花