全局登录弹窗
你开始你的,我怀念我的,熬过去了呢,我就重新开始,熬不过去我就一直等,或者等你回头,或者等我死心。
最近有一个需求,要在uniapp中制作一个全局的弹出框,用于用户登录。
# H5Box
拿到这个需求之后,我最先想到的就是我之前总结过的全局组件js调用,看上去是可行的,于是我按照这种方法定义了一个全局登录弹窗,代码如下:
- index.vue:搭建弹出框的结构和样式
- index.js:弹出框组件注册挂载
- main.js:在全局注册,之后即可使用
- index.vue
<!-- index.vue -->
<template>
<view class="login" v-show="isShow">
<transition name="fade">
<view class="content">
<!-- 顶部 -->
<view class="top">
<u-image src="@/static/image/loginBg.png" width="100%" mode="widthFix" class="image"></u-image>
<view class="close" @click="close">
<u-image src="@/static/image/comment-close.png" width="100%" mode="widthFix" class="closeImg"></u-image>
</view>
</view>
<!-- 主体 -->
<view class="main">
<u-form class="form" :model="form" ref="uForm" label-width="120" :label-style="{fontSize: '24rpx'}">
<u-form-item label="用户名">
<u-input v-model="form['mem-username']" placeholder="请输入用户名" placeholder-style="font-size:20rpx;" />
</u-form-item>
<u-form-item label="密码">
<u-input v-model="form['mem-password']" type="password" placeholder="请输入密码" placeholder-style="font-size:20rpx;" />
</u-form-item>
<view class="label">
<text class="forget" @click="routerTo('register')">注册账号</text>
<text class="forget" @click="routerTo('forgetPwd')">忘记密码</text>
</view>
</u-form>
<button class="btn" @click="doLogin">登录</button>
<view class="agree">
<u-checkbox v-model="form.checked" label-size="20">
勾选代表同意<text class="txt">《平台服务协议》</text>
</u-checkbox>
</view>
</view>
</view>
</transition>
</view>
</template>
<script>
export default {
data() {
return {
isShow: false,
form: {
"mem-username": "",
"mem-password": "",
checked: true
}
};
},
methods: {
doLogin() {
if (!this.form.checked) {
uni.showToast({
title: "请先勾选同意",
icon: "none"
})
return;
}
this.$api({
url: "user/login",
method: "GET",
data: {
...this.form,
equipmentCode: this.equipmentCode
}
}).then(res => {
// 将登录获取的数据记录下来
this.$store.commit("setLoginInfo", res.data.data)
// 将用户名密码记录在本地
this.common.setStorage('mem-username', this.form["mem-username"])
this.common.setStorage('mem-password', this.form["mem-password"])
uni.showToast({
title: res.data.msg
})
// 返回上一页
this.close();
// 获取用户信息
this.getUserInfo();
})
},
close() {
this.isShow = false;
},
routerTo(name) {
this.close();
this.common.routerTo({
name: name
})
}
},
created() {
this.form["mem-username"] = this.common.getStorage('mem-username') || "";
this.form["mem-password"] = this.common.getStorage('mem-password') || "";
}
};
</script>
<style scoped lang='scss'>
.login {
width: 100%;
max-width: 500px;
height: 100%;
position: absolute;
left: 0;
top: 0;
z-index: 100000000000000000;
overflow: hidden;
background: rgba(0, 0, 0, 0.6);
display: flex;
justify-content: center;
align-items: center;
.content {
width: 80%;
height: 40%;
display: flex;
flex-direction: column;
font-size: 20rpx;
.top {
background: #fff;
position: relative;
.image {
margin-top: -80rpx;
}
.close {
position: absolute;
top: -40rpx;
right: 20rpx;
width: 40rpx;
height: 40rpx;
}
}
.main {
flex: 1;
background: #fff;
padding: 0 30rpx 30rpx;
display: flex;
flex-direction: column;
justify-content: space-between;
border-bottom-left-radius: 14rpx;
border-bottom-right-radius: 14rpx;
.form {
flex: 1;
.label {
padding: 20rpx 10rpx 40rpx;
display: flex;
justify-content: space-between;
.forget {
font-size: 20rpx;
color: #aaa;
text-decoration: underline;
}
}
}
.btn {
width: 100%;
color: #fff;
font-size: 24rpx;
letter-spacing: 1em;
background-image: linear-gradient(to right, #ff416c, #ff4b2b);
}
.agree {
margin-top: 16rpx;
}
}
}
}
/* 动画 */
.fade-enter,
.fade-leave-to {
opacity: 0;
transform: translateY(150px);
}
.fade-enter-active,
.fade-leave-active {
transition: all 0.6s;
}
</style>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
- index.js
// index.js
import Vue from "vue";
import login from "./index.vue";
const LoginBox = Vue.extend(login);
login.install = function(data){
let instance = new LoginBox({
data
}).$mount()
document.body.appendChild(instance.$el)
Vue.nextTick(()=>{
instance.isShow = true;
})
}
export default login
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
- main.js
// main.js
import login from "./pages/login/h5Box/index.js";
Vue.prototype.$login = login.install
2
3
弹出框制作完成,在PC浏览器运行正常,如下图:
然而这貌似没问题的方法在手机基座上运行却一直报错,排查后发现上面这种方法的特点是将弹出框组件挂载到document.body上来实现全局调用,在浏览器中存在document,所以一切正常。但是在uniapp基座中是不存在document对象的,这种方法找不到挂载点,所以就只会报错。
总结
上面这种方法在uniapp中只适合H5使用,不能用于app的开发中。
# APPBox
上面的方法在APP端宣告失败,通过对网上其他方法的筛选,我最终确定使用路由+nvue的方法来实现登录弹出框。
具体思路:新建一个登录nvue页面并给它分配一个路由,把这个页面的背景设置为半透明,中间区域制作成弹框样式。当需要调用登录弹框时,直接uni.navigateTo即可,当要关闭弹框时,直接uni.navigateBack();
思路无碍,实践开始。
- pages.json配置
{
"path": "pages/login/appBox/index",
"style": {
// 取消默认的原生导航栏
"navigationStyle": "custom",
"background": "transparent", //把页面背景设置透明,默认是白色
"animationType": "fade-in"
}
},
2
3
4
5
6
7
8
9
- nvue页面
<template>
<view class="content" @click="close">
<view class="main" @click.stop="noClose">
<!-- 顶部图片 -->
<view class="top">
<image src="../../../static/image/loginBg.png" class="topImg"></image>
</view>
<!-- 主体内容 -->
<view class="body">
<view class="input-item">
<text class="input-label">用户名</text>
<input v-model="form['mem-username']" class="uni-input" placeholder="请输入用户名" placeholder-style="font-size:12px;color:#ccc" />
</view>
<view class="input-item">
<text class="input-label">密码</text>
<input v-model="form['mem-password']" class="uni-input" placeholder="请输入密码" :password="true" placeholder-style="font-size:12px;color:#ccc" />
</view>
<view class="input-linkContent">
<text class="input-link" @click="routerTo('register')">注册账号</text>
<text class="input-link" @click="routerTo('forgetPwd')">忘记密码</text>
</view>
<view class="input-btn">
<button class="btn" type="warn" @click="doLogin">登录</button>
</view>
<view class="input-checked">
<checkbox :value="form.checked" checked color="#e1251b" /><text class="checked">勾选代表同意《平台服务协议》</text>
</view>
</view>
</view>
</view>
</template>
<script>
import Store from '@/store/store';
export default {
data() {
return {
form: {
"mem-username": "",
"mem-password": "",
checked: true
}
}
},
methods: {
close() {
uni.navigateBack();
},
noClose(e) {
e.stopPropagation();
},
doLogin() {
if (!this.form.checked) {
uni.showToast({
title: "请先勾选同意",
icon: "none"
})
return;
}
let that = this;
uni.request({
url: "https://api.sy12306.com/user/login",
method: 'GET',
data: {
...that.form,
client_id: that.$store.state.channel_id, // 渠道号 Store.state.channel_id
app_id: that.$store.state.game_id, // 手机号 100 101 Store.state.game_id
equipmentCode: that.$store.state.equipmentCode, // 手机设备码
format: 'json',
ts: Date.parse(new Date()),
token: that.$store.state.loginInfo.user_token || that.$store.state.init.user_token || ''
},
timeout: 10000,
dataType: 'json',
success: (res) => {
console.log(res);
// 将登录获取的数据记录下来
that.$store.commit("setLoginInfo", res.data.data)
// 将用户名密码记录在本地
uni.setStorageSync('mem-username', that.form["mem-username"])
uni.setStorageSync('mem-password', that.form["mem-password"])
uni.showToast({
title: res.data.msg
})
// 获取用户信息
that.getUserInfo();
},
fail: (err) => {
uni.showToast({
icon: "none",
title: '请求接口失败'
})
reject(err)
}
})
},
getUserInfo() {
let that = this;
console.log(that.$store.state);
uni.request({
url: "https://api.sy12306.com/user/detail",
method: "GET",
data: {
client_id: that.$store.state.channel_id, // 渠道号 Store.state.channel_id
app_id: that.$store.state.game_id, // 手机号 100 101 Store.state.game_id
equipmentCode: that.$store.state.equipmentCode, // 手机设备码
format: 'json',
ts: Date.parse(new Date()),
token: that.$store.state.loginInfo.user_token || that.$store.state.init.user_token || ''
},
timeout: 10000,
dataType: 'json',
success: (res) => {
let username = uni.getStorageSync('mem-username');
if (res.data.code == 200) {
// 当记录的用户登录账户与登录之后返回的账户不匹配时,就不允许登录成功
if (username == res.data.data.mobile || username == res.data.data.username) {
that.$store.commit('setUserInfo', res.data.data);
} else {
uni.showToast({
icon: "none",
title: '登录过期,重新登录'
})
that.$store.commit("setLoginInfo", {})
}
// 关闭弹框
that.close();
}
},
fail: (err) => {
uni.showToast({
icon: "none",
title: '请求接口失败'
})
reject(err)
}
})
},
routerTo(name) {
this.close();
switch (name) {
case 'register':
uni.navigateTo({
url: "/pages/views/register/index"
});
break;
case 'forgetPwd':
uni.navigateTo({
url: "/pages/views/forgetPwd/index"
});
break;
}
}
},
created() {
this.form["mem-username"] = uni.getStorageSync('mem-username') || "";
this.form["mem-password"] = uni.getStorageSync('mem-password') || "";
}
}
</script>
<style>
.content {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 1;
background-color: rgba(0, 0, 0, .6);
justify-content: center;
align-items: center;
padding-left: 40rpx;
padding-right: 40rpx;
}
.main {
width: 300px;
height: 380px;
border-radius: 14rpx;
}
.top {
width: 300px;
height: 140px;
position: relative;
z-index: 22;
position: relative;
}
.close {
position: fixed;
right: 0;
top: 0;
z-index: 99;
color: #000;
}
.topImg {
flex: 1;
}
.body {
flex: 1;
background-color: #fff;
padding: 16rpx;
}
.input-item {
flex: 1;
flex-direction: row;
justify-content: space-between;
align-items: center;
border-bottom-width: 1px;
border-bottom-style: solid;
border-bottom-color: #eee;
}
.input-label {
text-align: center;
width: 60px;
font-size: 28rpx;
line-height: 30px;
}
.input-linkContent {
flex: 1;
flex-direction: row;
justify-content: space-between;
align-items: center;
}
.input-link {
font-size: 24rpx;
color: #ccc;
text-decoration: underline;
margin: 0 20rpx;
}
.uni-input {
flex: 1;
}
.input-btn {
flex: 1;
}
.btn {
color: #fff;
letter-spacing: 1em;
font-size: 28rpx;
background-image: linear-gradient(to right, #ff416c, #ff4b2b);
}
.input-checked {
flex: 0.5;
flex-direction: row;
align-items: flex-end;
}
.checked {
font-size: 24rpx;
line-height: 20px;
color: #222;
}
</style>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
- 打开
// #ifdef APP-PLUS
uni.navigateTo({
url: '/pages/login/appBox/index'
})
// #endif
2
3
4
5
- 关闭弹窗
uni.navigateBack();
本质上讲,这种方法就是一个单独的页面而不能称之为弹窗,只不过因为底色透明,所以看着像弹窗而已。采用这种方法并不能称之为好,只是因为uniapp原生的弹出框样式太难看,而想要自定义弹出框,好用的方法也没几个,这是其中还算不错的方法。页面如下
注意:
这种方法只适合APP,在H5上使用异常,这是因为nvue自身原因。所以有需要开发双端的这两种方法可以同时使用。
吐槽一句,nvue页面的样式真的太难写了,越写越恶心!!!
# 参考
https://www.yuque.com/along-n3gko/ezt5z9/emwy3v