feat: add registration page for new miniprogram users
- New register page with chooseAvatar, nickname input (random default), and required phone number authorization via getPhoneNumber - Login simplified: removed deprecated wx.getUserProfile, checks is_new_user flag to redirect new users to registration - Server login response now includes is_new_user field
这个提交包含在:
@@ -2,6 +2,7 @@
|
|||||||
"pages": [
|
"pages": [
|
||||||
"pages/index/index",
|
"pages/index/index",
|
||||||
"pages/login/login",
|
"pages/login/login",
|
||||||
|
"pages/register/register",
|
||||||
"pages/scan/scan",
|
"pages/scan/scan",
|
||||||
"pages/ble-connect/ble-connect",
|
"pages/ble-connect/ble-connect",
|
||||||
"pages/bind-success/bind-success",
|
"pages/bind-success/bind-success",
|
||||||
|
|||||||
@@ -1,15 +1,12 @@
|
|||||||
var app = getApp()
|
var app = getApp()
|
||||||
var http = require('../../utils/request')
|
|
||||||
|
|
||||||
Page({
|
Page({
|
||||||
data: {
|
data: {
|
||||||
statusBarHeight: 44,
|
statusBarHeight: 44,
|
||||||
loading: false,
|
loading: false
|
||||||
userProfile: null
|
|
||||||
},
|
},
|
||||||
|
|
||||||
onLoad: function () {
|
onLoad: function () {
|
||||||
var app = getApp()
|
|
||||||
this.setData({ statusBarHeight: app.globalData.statusBarHeight })
|
this.setData({ statusBarHeight: app.globalData.statusBarHeight })
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -17,73 +14,20 @@ Page({
|
|||||||
var self = this
|
var self = this
|
||||||
self.setData({ loading: true })
|
self.setData({ loading: true })
|
||||||
|
|
||||||
self.getWechatProfile().then(function (profile) {
|
app.doLogin().then(function (data) {
|
||||||
self.setData({ userProfile: profile })
|
|
||||||
return app.doLogin()
|
|
||||||
}).then(function () {
|
|
||||||
return self.saveProfile()
|
|
||||||
}).then(function () {
|
|
||||||
self.setData({ loading: false })
|
self.setData({ loading: false })
|
||||||
|
if (data && data.is_new_user) {
|
||||||
|
wx.redirectTo({ url: '/pages/register/register' })
|
||||||
|
} else {
|
||||||
wx.switchTab({ url: '/pages/index/index' })
|
wx.switchTab({ url: '/pages/index/index' })
|
||||||
|
}
|
||||||
}).catch(function (err) {
|
}).catch(function (err) {
|
||||||
self.setData({ loading: false })
|
self.setData({ loading: false })
|
||||||
wx.showToast({ title: err.message || '登录失败', icon: 'none' })
|
wx.showToast({ title: err.message || '登录失败', icon: 'none' })
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|
||||||
getWechatProfile: function () {
|
|
||||||
return new Promise(function (resolve) {
|
|
||||||
if (!wx.getUserProfile) {
|
|
||||||
resolve(null)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
wx.getUserProfile({
|
|
||||||
desc: '用于完善会员资料',
|
|
||||||
success: function (res) {
|
|
||||||
resolve(res.userInfo || null)
|
|
||||||
},
|
|
||||||
fail: function () {
|
|
||||||
resolve(null)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
|
||||||
},
|
|
||||||
|
|
||||||
saveProfile: function () {
|
|
||||||
var profile = this.data.userProfile || {}
|
|
||||||
if (!profile.nickName && !profile.avatarUrl) return Promise.resolve()
|
|
||||||
return this.updateProfile({
|
|
||||||
nickname: profile.nickName || '',
|
|
||||||
avatar: profile.avatarUrl || '',
|
|
||||||
gender: profile.gender || 0
|
|
||||||
})
|
|
||||||
},
|
|
||||||
|
|
||||||
updateProfile: function (payload) {
|
|
||||||
return http.put('/api/v1/user/profile', payload).then(function () {
|
|
||||||
return app.loadProfile()
|
|
||||||
})
|
|
||||||
},
|
|
||||||
|
|
||||||
onAgreement: function () {
|
onAgreement: function () {
|
||||||
wx.showModal({ title: '提示', content: '用户协议和隐私政策内容建设中', showCancel: false })
|
wx.showModal({ title: '提示', content: '用户协议和隐私政策内容建设中', showCancel: false })
|
||||||
},
|
|
||||||
|
|
||||||
onGetPhoneNumber: function (e) {
|
|
||||||
var self = this
|
|
||||||
if (!e.detail || !e.detail.code) {
|
|
||||||
wx.showToast({ title: '已跳过手机号授权', icon: 'none' })
|
|
||||||
return
|
|
||||||
}
|
|
||||||
self.setData({ loading: true })
|
|
||||||
app.doLogin().then(function () {
|
|
||||||
return http.post('/api/v1/user/phone', { code: e.detail.code })
|
|
||||||
}).then(function () {
|
|
||||||
self.setData({ loading: false })
|
|
||||||
wx.switchTab({ url: '/pages/index/index' })
|
|
||||||
}).catch(function (err) {
|
|
||||||
self.setData({ loading: false })
|
|
||||||
wx.showToast({ title: err.message || '手机号授权失败', icon: 'none' })
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<view class="page">
|
<view class="page">
|
||||||
<view class="page-header page-header-pink" style="padding-top: {{statusBarHeight + 24}}px;">
|
<view class="page-header page-header-pink" style="padding-top: {{statusBarHeight + 24}}px;">
|
||||||
<view class="page-header-title">光子美容仪</view>
|
<view class="page-header-title">光子美容仪</view>
|
||||||
<view class="page-header-subtitle">微信授权登录</view>
|
<view class="page-header-subtitle">微信登录</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<view class="page-content">
|
<view class="page-content">
|
||||||
@@ -9,17 +9,11 @@
|
|||||||
|
|
||||||
<view class="flower-area">
|
<view class="flower-area">
|
||||||
<text class="flower">🌸</text>
|
<text class="flower">🌸</text>
|
||||||
<view class="desc">授权获取微信昵称和头像</view>
|
<view class="desc">登录后即可使用全部功能</view>
|
||||||
<view class="desc">用于完善会员资料</view>
|
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<button class="login-btn" loading="{{loading}}" bindtap="onLogin" disabled="{{loading}}">
|
<button class="login-btn" loading="{{loading}}" bindtap="onLogin" disabled="{{loading}}">
|
||||||
微信授权登录
|
微信登录
|
||||||
</button>
|
|
||||||
|
|
||||||
<!-- 手机号授权已预留。需要时展示该按钮并隐藏上面的普通登录按钮。 -->
|
|
||||||
<button wx:if="{{false}}" class="phone-btn disabled-phone-btn" open-type="getPhoneNumber" bindgetphonenumber="onGetPhoneNumber" disabled="{{loading}}">
|
|
||||||
授权手机号登录(预留)
|
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<view class="agreement" bindtap="onAgreement">登录即表示同意《用户协议》和《隐私政策》</view>
|
<view class="agreement" bindtap="onAgreement">登录即表示同意《用户协议》和《隐私政策》</view>
|
||||||
|
|||||||
@@ -46,26 +46,6 @@
|
|||||||
border: none;
|
border: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.phone-btn {
|
|
||||||
display: block;
|
|
||||||
width: 100%;
|
|
||||||
padding: 28rpx;
|
|
||||||
background: #ffffff;
|
|
||||||
color: #E6508C;
|
|
||||||
border: 2rpx solid #E6508C;
|
|
||||||
border-radius: 20rpx;
|
|
||||||
font-size: 30rpx;
|
|
||||||
text-align: center;
|
|
||||||
margin-top: 24rpx;
|
|
||||||
}
|
|
||||||
|
|
||||||
.phone-btn::after {
|
|
||||||
border: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.disabled-phone-btn {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.agreement {
|
.agreement {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
|||||||
@@ -0,0 +1,102 @@
|
|||||||
|
var app = getApp()
|
||||||
|
var http = require('../../utils/request')
|
||||||
|
|
||||||
|
var DEFAULT_AVATAR = 'https://mmbiz.qpic.cn/mmbiz/icTdbqWNOwNRna42FI9t2NHfLvvMiaiaCJQn3KP2P0vAHp1QBP0piavhGfhKCARlGd6z1EbR58ibJXKCp5WVlNnojwA/640'
|
||||||
|
|
||||||
|
function randomNickname() {
|
||||||
|
var digits = ''
|
||||||
|
for (var i = 0; i < 4; i++) {
|
||||||
|
digits += Math.floor(Math.random() * 10)
|
||||||
|
}
|
||||||
|
return '用户' + digits
|
||||||
|
}
|
||||||
|
|
||||||
|
Page({
|
||||||
|
data: {
|
||||||
|
statusBarHeight: 44,
|
||||||
|
loading: false,
|
||||||
|
avatarUrl: DEFAULT_AVATAR,
|
||||||
|
nickname: '',
|
||||||
|
phone: ''
|
||||||
|
},
|
||||||
|
|
||||||
|
onLoad: function () {
|
||||||
|
this.setData({
|
||||||
|
statusBarHeight: app.globalData.statusBarHeight,
|
||||||
|
nickname: randomNickname()
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
onChooseAvatar: function (e) {
|
||||||
|
if (e.detail && e.detail.avatarUrl) {
|
||||||
|
this.setData({ avatarUrl: e.detail.avatarUrl })
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
onNicknameInput: function (e) {
|
||||||
|
this.setData({ nickname: e.detail.value || '' })
|
||||||
|
},
|
||||||
|
|
||||||
|
onNicknameBlur: function (e) {
|
||||||
|
var val = (e.detail.value || '').trim()
|
||||||
|
if (!val) {
|
||||||
|
this.setData({ nickname: randomNickname() })
|
||||||
|
} else {
|
||||||
|
this.setData({ nickname: val })
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
onGetPhoneNumber: function (e) {
|
||||||
|
var self = this
|
||||||
|
if (!e.detail || !e.detail.code) {
|
||||||
|
wx.showToast({ title: '需要授权手机号才能完成注册', icon: 'none' })
|
||||||
|
return
|
||||||
|
}
|
||||||
|
self.setData({ loading: true })
|
||||||
|
http.post('/api/v1/user/phone', { code: e.detail.code }).then(function (data) {
|
||||||
|
self.setData({
|
||||||
|
loading: false,
|
||||||
|
phone: data.phone || data.pure_phone_number || '已授权'
|
||||||
|
})
|
||||||
|
wx.showToast({ title: '手机号授权成功', icon: 'success' })
|
||||||
|
}).catch(function (err) {
|
||||||
|
self.setData({ loading: false })
|
||||||
|
wx.showToast({ title: err.message || '手机号授权失败', icon: 'none' })
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
onSubmit: function () {
|
||||||
|
var self = this
|
||||||
|
var nickname = (self.data.nickname || '').trim()
|
||||||
|
var avatarUrl = self.data.avatarUrl
|
||||||
|
|
||||||
|
if (!self.data.phone) {
|
||||||
|
wx.showToast({ title: '请先授权手机号', icon: 'none' })
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!nickname) {
|
||||||
|
wx.showToast({ title: '请输入昵称', icon: 'none' })
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
self.setData({ loading: true })
|
||||||
|
|
||||||
|
http.put('/api/v1/user/profile', {
|
||||||
|
nickname: nickname,
|
||||||
|
avatar: avatarUrl !== DEFAULT_AVATAR ? avatarUrl : ''
|
||||||
|
}).then(function () {
|
||||||
|
return app.loadProfile()
|
||||||
|
}).then(function () {
|
||||||
|
self.setData({ loading: false })
|
||||||
|
wx.switchTab({ url: '/pages/index/index' })
|
||||||
|
}).catch(function (err) {
|
||||||
|
self.setData({ loading: false })
|
||||||
|
wx.showToast({ title: err.message || '保存失败', icon: 'none' })
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
onAgreement: function () {
|
||||||
|
wx.showModal({ title: '提示', content: '用户协议和隐私政策内容建设中', showCancel: false })
|
||||||
|
}
|
||||||
|
})
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"navigationBarTitleText": "完善资料",
|
||||||
|
"navigationStyle": "custom"
|
||||||
|
}
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
<view class="page">
|
||||||
|
<view class="page-header page-header-pink" style="padding-top: {{statusBarHeight + 24}}px;">
|
||||||
|
<view class="page-header-title">光子美容仪</view>
|
||||||
|
<view class="page-header-subtitle">完善个人资料</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view class="page-content">
|
||||||
|
<view class="section-title">设置头像</view>
|
||||||
|
<view class="avatar-area">
|
||||||
|
<button class="avatar-btn" open-type="chooseAvatar" bindchooseavatar="onChooseAvatar">
|
||||||
|
<image class="avatar-img" src="{{avatarUrl}}" mode="aspectFill"></image>
|
||||||
|
<view class="avatar-edit-hint">点击更换</view>
|
||||||
|
</button>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view class="section-title">设置昵称</view>
|
||||||
|
<view class="input-wrap">
|
||||||
|
<input type="nickname" class="weui-input nickname-input" value="{{nickname}}" bindinput="onNicknameInput" bindblur="onNicknameBlur" placeholder="请输入昵称" />
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view class="section-title">授权手机号</view>
|
||||||
|
<view class="phone-area">
|
||||||
|
<block wx:if="{{phone}}">
|
||||||
|
<view class="phone-done">
|
||||||
|
<text class="phone-icon">✓</text>
|
||||||
|
<text class="phone-text">{{phone}}</text>
|
||||||
|
</view>
|
||||||
|
</block>
|
||||||
|
<block wx:else>
|
||||||
|
<button class="phone-btn" open-type="getPhoneNumber" bindgetphonenumber="onGetPhoneNumber" disabled="{{loading}}">
|
||||||
|
授权手机号
|
||||||
|
</button>
|
||||||
|
<view class="phone-hint">手机号用于接收服务通知,必须授权</view>
|
||||||
|
</block>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<button class="submit-btn" bindtap="onSubmit" loading="{{loading}}" disabled="{{loading}}">
|
||||||
|
完成注册
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<view class="agreement" bindtap="onAgreement">注册即表示同意《用户协议》和《隐私政策》</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
@@ -0,0 +1,138 @@
|
|||||||
|
.page {
|
||||||
|
min-height: 100vh;
|
||||||
|
background: #ffffff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section-title {
|
||||||
|
font-size: 30rpx;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #333333;
|
||||||
|
margin-bottom: 20rpx;
|
||||||
|
margin-top: 36rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section-title:first-child {
|
||||||
|
margin-top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.avatar-area {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
margin-bottom: 12rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.avatar-btn {
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
line-height: normal;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.avatar-btn::after {
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.avatar-img {
|
||||||
|
width: 160rpx;
|
||||||
|
height: 160rpx;
|
||||||
|
border-radius: 50%;
|
||||||
|
border: 4rpx solid #f0f0f0;
|
||||||
|
background: #f5f5f5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.avatar-edit-hint {
|
||||||
|
font-size: 24rpx;
|
||||||
|
color: #999999;
|
||||||
|
margin-top: 12rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.input-wrap {
|
||||||
|
background: #f5f5f5;
|
||||||
|
border-radius: 16rpx;
|
||||||
|
padding: 24rpx 28rpx;
|
||||||
|
margin-bottom: 12rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nickname-input {
|
||||||
|
font-size: 30rpx;
|
||||||
|
color: #333333;
|
||||||
|
height: 48rpx;
|
||||||
|
line-height: 48rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.phone-area {
|
||||||
|
margin-bottom: 12rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.phone-btn {
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
padding: 28rpx;
|
||||||
|
background: #ffffff;
|
||||||
|
color: #E6508C;
|
||||||
|
border: 2rpx solid #E6508C;
|
||||||
|
border-radius: 20rpx;
|
||||||
|
font-size: 30rpx;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.phone-btn::after {
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.phone-hint {
|
||||||
|
font-size: 24rpx;
|
||||||
|
color: #999999;
|
||||||
|
text-align: center;
|
||||||
|
margin-top: 12rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.phone-done {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
background: #f0fff4;
|
||||||
|
border: 2rpx solid #52c41a;
|
||||||
|
border-radius: 20rpx;
|
||||||
|
padding: 28rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.phone-icon {
|
||||||
|
color: #52c41a;
|
||||||
|
font-size: 36rpx;
|
||||||
|
margin-right: 16rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.phone-text {
|
||||||
|
font-size: 30rpx;
|
||||||
|
color: #333333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.submit-btn {
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
padding: 32rpx;
|
||||||
|
background: #E6508C;
|
||||||
|
color: #ffffff;
|
||||||
|
border: none;
|
||||||
|
border-radius: 20rpx;
|
||||||
|
font-size: 32rpx;
|
||||||
|
font-weight: 600;
|
||||||
|
text-align: center;
|
||||||
|
margin-top: 60rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.submit-btn::after {
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.agreement {
|
||||||
|
text-align: center;
|
||||||
|
font-size: 24rpx;
|
||||||
|
color: #999999;
|
||||||
|
margin-top: 32rpx;
|
||||||
|
}
|
||||||
@@ -16,7 +16,9 @@ router.post('/auth/login', wrap(async (req, res) => {
|
|||||||
|
|
||||||
const session = await code2Session(req.body.code || '')
|
const session = await code2Session(req.body.code || '')
|
||||||
let user = await userDao.findByOpenid(session.openid)
|
let user = await userDao.findByOpenid(session.openid)
|
||||||
|
let isNewUser = false
|
||||||
if (!user) {
|
if (!user) {
|
||||||
|
isNewUser = true
|
||||||
const result = await userDao.create(session.openid)
|
const result = await userDao.create(session.openid)
|
||||||
user = await userDao.findById(result.insertId)
|
user = await userDao.findById(result.insertId)
|
||||||
await logDao.write({ user_id: user.user_id, action: 'user_register', detail: '新用户注册', ip: req.ip })
|
await logDao.write({ user_id: user.user_id, action: 'user_register', detail: '新用户注册', ip: req.ip })
|
||||||
@@ -26,6 +28,7 @@ router.post('/auth/login', wrap(async (req, res) => {
|
|||||||
res.json(ok({
|
res.json(ok({
|
||||||
token,
|
token,
|
||||||
user_id: String(user.user_id),
|
user_id: String(user.user_id),
|
||||||
|
is_new_user: isNewUser,
|
||||||
user_info: {
|
user_info: {
|
||||||
user_id: String(user.user_id),
|
user_id: String(user.user_id),
|
||||||
nickname: user.nickname || '用户' + String(user.user_id),
|
nickname: user.nickname || '用户' + String(user.user_id),
|
||||||
|
|||||||
在新工单中引用
屏蔽一个用户