1. 添加全局登录状态

2. 完成修改安全问题
3. 优化请求处理
This commit is contained in:
范鹏 2024-08-21 16:20:11 +08:00
parent c9a7c24672
commit 0b595ed01c
16 changed files with 591 additions and 75 deletions

12
App.vue
View File

@ -1,11 +1,20 @@
<script>
import { useBluetoothStore } from '@/stores/bluetooth'
import { useUserStore } from '@/stores/user'
import { useBasicStore } from '@/stores/basic'
import { mapState, mapActions } from 'pinia'
import { getUserInfoRequest } from '@/api/user'
let vm
export default {
globalData: {
//
updateIsLogin(isLogin) {
useUserStore().updateLoginStatus(isLogin)
}
},
computed: {
...mapState(useBluetoothStore, ['bluetoothStatus'])
...mapState(useBluetoothStore, ['bluetoothStatus']),
},
onLaunch: function() {
//
@ -29,6 +38,7 @@
...mapActions(useBluetoothStore, ['initBluetooth', 'onBluetoothState', 'updateBluetoothStatus', 'checkSetting',
'onBluetoothConnectStatus', 'onBluetoothCharacteristicValueChange']),
...mapActions(useBasicStore, ['getDeviceInfo', 'getButtonInfo']),
...mapActions(useUserStore, ['updateLoginStatus']),
//
updateMiniProgram() {
const updateManager = uni.getUpdateManager()

View File

@ -22,4 +22,4 @@ const PROD = {
}
// 更换环境的时候 切换导出就行
export default PRE
export default DEV

View File

@ -71,6 +71,15 @@
"navigationStyle": "default"
}
},
{
"path": "pages/updateSafeQuestion/updateSafeQuestion",
"style": {
"navigationBarTitleText": "修改安全问题",
"navigationBarTextStyle": "white",
"navigationBarBackgroundColor": "#63b8af",
"navigationStyle": "default"
}
},
{
"path": "pages/webview/webview",
"style": {

View File

@ -1,6 +1,9 @@
<template>
<view>
home
<view v-if="isLogin">111111</view>
<view v-else>
<view class="button-login" @click="login">登录</view>
</view>
</view>
</template>
@ -8,7 +11,6 @@
import { getUserInfoRequest } from '@/api/user'
import { useUserStore } from '@/stores/user'
import { mapState, mapActions } from 'pinia'
import { getCompanyRequest } from '@/api/app'
export default {
data() {
@ -17,14 +19,21 @@
}
},
computed: {
...mapState(useUserStore, ['userInfo'])
...mapState(useUserStore, ['userInfo', 'isLogin'])
},
async onLoad() {
uni.setStorageSync('token', '3016|NQth8ud3JRGLDKO3Gsmg7gEgv9yBjowrZNijlBCp975d2a97')
this.getUserInfo()
uni.showLoading({
title: '加载中'
})
const token = uni.getStorageSync('token')
if(token) {
this.updateLoginStatus(true)
await this.getUserInfo()
}
uni.hideLoading()
},
methods: {
...mapActions(useUserStore, ['updateUserInfo']),
...mapActions(useUserStore, ['updateUserInfo', 'updateLoginStatus', 'login']),
async getUserInfo() {
const { code, data } = await getUserInfoRequest()
if(code === 0) {
@ -43,5 +52,17 @@ page {
</style>
<style scoped lang="scss">
.button-login {
margin-top: 40vh;
border-radius: 46rpx;
width: 650rpx;
height: 120rpx;
line-height: 120rpx;
text-align: center;
margin-left: 50rpx;
background: #63b8af;
color: #ffffff;
font-size: 48rpx;
font-weight: bold;
}
</style>

View File

@ -2,58 +2,87 @@
<view v-if="buttonInfo">
<image :style="{marginTop: buttonInfo.bottom + 10 + 'px'}" src="/static/images/background_mine.png"
class="background-image"></image>
<view class="view">
<view class="view-button" @click="toUsereInfo">
<view>个人信息</view>
<image class="icon-arrow" src="/static/images/icon_arrow.png" mode="aspectFill"></image>
</view>
<view class="view-line"></view>
<label for="contact">
<view class="view-button">
<view>客服</view>
<view v-if="isLogin">
<view class="view">
<view class="view-button" @click="toUsereInfo">
<view>个人信息</view>
<image class="icon-arrow" src="/static/images/icon_arrow.png" mode="aspectFill"></image>
</view>
<view class="view-line"></view>
<label for="contact">
<view class="view-button">
<view>客服</view>
<image class="icon-arrow" src="/static/images/icon_arrow.png" mode="aspectFill"></image>
</view>
</label>
<view class="view-line"></view>
<view class="view-button" @click="toWebview()">
<view>公司介绍</view>
<image class="icon-arrow" src="/static/images/icon_arrow.png" mode="aspectFill"></image>
</view>
<view class="view-line"></view>
<view class="view-button" @click="toWebview('userAgreement')">
<view>用户协议</view>
<image class="icon-arrow" src="/static/images/icon_arrow.png" mode="aspectFill"></image>
</view>
<view class="view-line"></view>
<view class="view-button" @click="toWebview('privacy')">
<view>隐私政策</view>
<image class="icon-arrow" src="/static/images/icon_arrow.png" mode="aspectFill"></image>
</view>
</label>
<view class="view-line"></view>
<view class="view-button" @click="toWebview">
<view>关于</view>
<image class="icon-arrow" src="/static/images/icon_arrow.png" mode="aspectFill"></image>
</view>
<view class="button-logout" @click="logout">退出</view>
</view>
<view v-else>
<view class="button-login" @click="login">登录</view>
</view>
<view class="button-logout">退出</view>
</view>
<button open-type="contact" id="contact"></button>
</template>
<script>
import { useBasicStore } from '@/stores/basic'
import { useUserStore } from '@/stores/user'
import { mapState, mapActions } from 'pinia'
export default {
data() {
return {
buttonInfo: null,
deviceInfo: null
buttonInfo: null
}
},
computed: {
...mapState(useUserStore, ['isLogin'])
},
async onLoad() {
this.deviceInfo = await this.getDeviceInfo()
this.buttonInfo = await this.getButtonInfo()
console.log(this.deviceInfo)
console.log(this.buttonInfo.top)
},
methods: {
...mapActions(useBasicStore, ['getDeviceInfo', 'getButtonInfo', 'routeJump']),
...mapActions(useBasicStore, ['getButtonInfo', 'routeJump']),
...mapActions(useUserStore, ['updateLoginStatus', 'login']),
toUsereInfo() {
this.routeJump({
name: 'userInfo'
})
},
toWebview() {
toWebview(type) {
this.routeJump({
name: 'webview',
params: {
type: 'default'
type
}
})
},
logout() {
const that = this
uni.showModal({
title: '提示',
content: '确定退出登录吗?',
success: (res) => {
if (res.confirm) {
uni.removeStorageSync('token')
that.updateLoginStatus(false)
}
}
})
}
@ -109,7 +138,7 @@ page {
.button-logout {
position: absolute;
border-radius: 46rpx;
bottom: 40rpx;
bottom: 60rpx;
width: 710rpx;
height: 80rpx;
line-height: 80rpx;
@ -120,4 +149,18 @@ page {
font-size: 40rpx;
font-weight: bold;
}
.button-login {
margin-top: 40vh;
border-radius: 46rpx;
width: 650rpx;
height: 120rpx;
line-height: 120rpx;
text-align: center;
margin-left: 50rpx;
background: #63b8af;
color: #ffffff;
font-size: 48rpx;
font-weight: bold;
}
</style>

View File

@ -1,22 +1,39 @@
<template>
<view>
<view class="text">如果手机丢了可以通过回答设置的安全问题来登录新设备</view>
<view class="safe-question" v-for="item in questionAnswer" :key="item.questionId">
<view class="question">
<view>{{item.question}}</view>
<image class="icon-arrow" src="/static/images/icon_arrow.png" mode="aspectFill"></image>
</view>
<view class="line"></view>
<input class="input" :value="item.answer" maxlength="16" placeholder="请输入答案"
placeholder-class="input-placeholder" :disabled="true"></input>
</view>
<view class="button" @click="updateAnswer">修改</view>
</view>
</template>
<script>
import { getQuestionAnswerRequest, getQuestionListRequest } from '@/api/safeAnswer'
import { getQuestionAnswerRequest, getQuestionListRequest, updateQuestionAnswerRequest } from '@/api/safeAnswer'
import { mapActions } from 'pinia'
import { useBasicStore } from '@/stores/basic'
export default {
data() {
return {
questionAnswer: [],
}
},
onLoad() {
this.getQuestionList()
this.getQuestionAnswer()
},
methods: {
async getQuestionList() {
const { code, data, message } = await getQuestionListRequest()
...mapActions(useBasicStore, ['routeJump']),
async getQuestionAnswer() {
const { code, data, message } = await getQuestionAnswerRequest()
if(code === 0) {
console.log(1, data)
this.questionAnswer = data
} else {
uni.showToast({
title: message,
@ -24,23 +41,97 @@ export default {
})
}
},
async getQuestionAnswer() {
const { code, data, message } = await getQuestionAnswerRequest()
if(code === 0) {
console.log(2, data)
} else {
uni.showToast({
title: message,
icon: 'none'
})
}
updateAnswer() {
this.routeJump({
type: 'redirectTo',
name: 'updateSafeQuestion'
})
}
}
}
</script>
<style scoped lang="scss">
<style lang="scss">
page {
background-color: $uni-bg-color-grey;
}
</style>
<style lang="scss" scoped>
.text {
padding-top: 20rpx;
margin-left: 20rpx;
font-size: 24rpx;
font-weight: bold;
}
.safe-question {
width: 710rpx;
margin-left: 20rpx;
margin-top: 20rpx;
}
.question {
border-radius: 32rpx 32rpx 0 0;
display: flex;
justify-content: space-between;
align-items: center;
padding: 20rpx 32rpx;
background-color: #fff;
}
.icon-arrow {
width: 40rpx;
height: 40rpx;
}
.input {
border-radius: 0 0 32rpx 32rpx;
height: 100rpx;
line-height: 100rpx;
background: #FFFFFF;
padding: 0 32rpx;
}
.input-placeholder {
height: 100rpx;
font-size: 36rpx;
font-weight: bold;
line-height: 100rpx;
}
.line {
width: 100%;
height: 2rpx;
background: #EBEBEB;
}
.popup-title {
font-size: 32rpx;
height: 100rpx;
text-align: center;
line-height: 100rpx;
font-weight: bold;
}
.popup-content {
font-size: 32rpx;
height: 80rpx;
line-height: 80rpx;
text-align: center;
font-weight: bold;
}
.button {
margin-top: 32rpx;
margin-left: 35rpx;
width: 680rpx;
height: 96rpx;
background: #63b8af;
border-radius: 16rpx;
line-height: 96rpx;
text-align: center;
font-size: 32rpx;
color: #FFFFFF;
}
</style>

View File

@ -24,7 +24,8 @@ export default {
text: '获取验证码',
verificationCode: '',
token: '',
email: ''
email: '',
pending: false
}
},
computed: {
@ -74,7 +75,18 @@ export default {
}
},
async toUpdateEmail () {
if(!test.email(this.email)){
uni.showToast({
title: '请输入正确的邮箱',
icon: 'none'
})
return
}
if (this.verificationCode.length === 6 && test.digits(this.verificationCode)) {
if(this.pending){
return
}
this.pending = true
const params = {
verificationCode: this.verificationCode,
email: this.email
@ -102,6 +114,7 @@ export default {
icon: 'none'
})
}
this.pending = false
} else {
uni.showToast({
title: '验证码为6位纯数字',
@ -112,10 +125,11 @@ export default {
updateTime () {
let time = 120
this.text = `${time} s`
const now = new Date().getTime()
const timer = setInterval(() => {
time--
this.text = `${time} s`
if (time === 0) {
const second = parseInt((new Date().getTime() - now) / 1000)
this.text = `${time - second} s`
if (time <= second) {
clearInterval(timer)
this.text = '获取验证码'
}

View File

@ -14,7 +14,8 @@ import { updateUserInfoRequest } from '@/api/user'
export default {
data() {
return {
nickname: ''
nickname: '',
pending: false
}
},
computed: {
@ -37,6 +38,10 @@ export default {
})
return
}
if(this.pending) {
return
}
this.pending = true
const { code } = await updateUserInfoRequest({
nickname: this.nickname
})
@ -59,6 +64,7 @@ export default {
icon: 'none'
})
}
this.pending = false
}
}
}

View File

@ -21,7 +21,8 @@ export default {
return {
oldPassword: '',
newPassword: '',
confirmPassword: ''
confirmPassword: '',
pending: false
}
},
computed: {
@ -61,6 +62,10 @@ export default {
})
return
}
if(this.pending) {
return
}
this.pending = true
const { code, message } = await updatePasswordRequest({
oldPassword: this.oldPassword,
newPassword: this.newPassword,
@ -85,6 +90,7 @@ export default {
icon: 'none'
})
}
this.pending = false
}
}
}

View File

@ -0,0 +1,252 @@
<template>
<view>
<view class="text">如果手机丢了可以通过回答设置的安全问题来登录新设备</view>
<view class="safe-question">
<view class="question" @click="popup('firstList')">
<view>{{answer[0].question}}</view>
<image class="icon-arrow" src="/static/images/icon_arrow.png" mode="aspectFill"></image>
</view>
<view class="line"></view>
<input class="input" :value="answer[0].answer" maxlength="16" placeholder="请输入答案"
placeholder-class="input-placeholder" @input="changeFirstAnswer"></input>
</view>
<view class="safe-question">
<view class="question" @click="popup('secondList')">
<view>{{answer[1].question}}</view>
<image class="icon-arrow" src="/static/images/icon_arrow.png" mode="aspectFill"></image>
</view>
<view class="line"></view>
<input class="input" :value="answer[1].answer" maxlength="16" placeholder="请输入答案"
placeholder-class="input-placeholder" @input="changeSecondAnswer"></input>
</view>
<view class="safe-question">
<view class="question" @click="popup('thirdList')">
<view>{{answer[2].question}}</view>
<image class="icon-arrow" src="/static/images/icon_arrow.png" mode="aspectFill"></image>
</view>
<view class="line"></view>
<input class="input" :value="answer[2].answer" maxlength="16" placeholder="请输入答案"
placeholder-class="input-placeholder" @input="changeThirdAnswer"></input>
</view>
<view class="button" @click="updateAnswer">保存</view>
</view>
<up-popup :show="show" mode="bottom" round="16rpx" @close="close">
<view class="popup-title">选择问题</view>
<view class="line" style="height: 10rpx"></view>
<view v-for="item in currentQuestionList" :key="item.questionId" @click="selectQuestion(item)">
<view class="popup-content">{{item.question}}</view>
<view class="line"></view>
</view>
<view class="line" style="height: 10rpx"></view>
<view class="popup-title" @click="close">取消</view>
</up-popup>
</template>
<script>
import { getQuestionAnswerRequest, getQuestionListRequest, updateQuestionAnswerRequest } from '@/api/safeAnswer'
import { mapActions, mapState } from 'pinia'
import { useUserStore } from '@/stores/user'
export default {
data() {
return {
questionList: {},
currentQuestionList: [],
currentIndex: 0,
answer:[{
question: '问题一',
answer: ''
}, {
question: '问题二',
answer: ''
}, {
question: '问题三',
answer: ''
}],
show: false,
pending: false
}
},
computed: {
...mapState(useUserStore, ['userInfo'])
},
onLoad() {
this.getQuestionList()
},
methods: {
...mapActions(useUserStore, ['updateUserInfo']),
async updateAnswer() {
console.log('答案', this.answer)
for(let i = 0; i < this.answer.length; i++) {
if(!this.answer[i].questionId) {
uni.showToast({
title: '问题不能为空',
icon: 'none'
})
return
}
}
for(let i = 0; i < this.answer.length; i++) {
if(this.answer[i].answer === '') {
uni.showToast({
title: '答案不能为空',
icon: 'none'
})
return
}
}
if(this.pending) {
return
}
this.pending = true
const { code, message } = await updateQuestionAnswerRequest({
questionAndAnswerList: this.answer
})
if(code === 0) {
this.updateUserInfo({
...this.userInfo,
haveSafeAnswer: 1
})
uni.navigateBack({
complete() {
uni.showToast({
title: '安全问题设置成功',
icon: 'none'
})
}
})
} else {
uni.showToast({
title: message,
icon: 'none'
})
}
this.pending = false
},
changeFirstAnswer(data) {
this.answer[0].answer = data.detail.value
},
changeSecondAnswer(data) {
this.answer[1].answer = data.detail.value
},
changeThirdAnswer(data) {
this.answer[2].answer = data.detail.value
},
close() {
this.show = false
},
selectQuestion(item) {
this.answer[this.currentIndex].question = item.question
this.answer[this.currentIndex].questionId = item.questionId
this.show = false
},
popup(type) {
if(type === 'secondList') {
this.currentIndex = 1
} else if(type === 'thirdList') {
this.currentIndex = 2
} else {
this.currentIndex = 0
}
this.currentQuestionList = this.questionList[type]
this.show = true
},
async getQuestionList() {
const { code, data, message } = await getQuestionListRequest()
if(code === 0) {
this.questionList = data
} else {
uni.showToast({
title: message,
icon: 'none'
})
}
}
}
}
</script>
<style lang="scss">
page {
background-color: $uni-bg-color-grey;
}
</style>
<style lang="scss" scoped>
.text {
padding-top: 20rpx;
margin-left: 20rpx;
font-size: 24rpx;
font-weight: bold;
}
.safe-question {
width: 710rpx;
margin-left: 20rpx;
margin-top: 20rpx;
}
.question {
border-radius: 32rpx 32rpx 0 0;
display: flex;
justify-content: space-between;
align-items: center;
padding: 20rpx 32rpx;
background-color: #fff;
}
.icon-arrow {
width: 40rpx;
height: 40rpx;
}
.input {
border-radius: 0 0 32rpx 32rpx;
height: 100rpx;
line-height: 100rpx;
background: #FFFFFF;
padding: 0 32rpx;
}
.input-placeholder {
height: 100rpx;
font-size: 36rpx;
font-weight: bold;
line-height: 100rpx;
}
.line {
width: 100%;
height: 2rpx;
background: #EBEBEB;
}
.popup-title {
font-size: 32rpx;
height: 100rpx;
text-align: center;
line-height: 100rpx;
font-weight: bold;
}
.popup-content {
font-size: 32rpx;
height: 80rpx;
line-height: 80rpx;
text-align: center;
font-weight: bold;
}
.button {
margin-top: 32rpx;
margin-left: 35rpx;
width: 680rpx;
height: 96rpx;
background: #63b8af;
border-radius: 16rpx;
line-height: 96rpx;
text-align: center;
font-size: 32rpx;
color: #FFFFFF;
}
</style>

View File

@ -57,7 +57,7 @@
</view>
</view>
</view>
<button open-type="chooseAvatar" style="display: none" id="avatar" @chooseavatar="chooseAvatar"></button>
<button open-type="chooseAvatar" style="display:none" id="avatar" @chooseavatar="chooseAvatar"></button>
</view>
</template>
@ -69,6 +69,11 @@ import { getUploadParamsRequest } from '@/api/file'
import { updateUserInfoRequest } from '@/api/user'
export default {
data() {
return {
pending: false
}
},
computed: {
...mapState(useUserStore, ['userInfo'])
},
@ -76,7 +81,14 @@ export default {
...mapActions(useBasicStore, ['routeJump']),
...mapActions(useUserStore, ['updateUserInfo']),
chooseAvatar(e) {
console.log(e)
const that = this
if(that.pending) {
return
}
that.pending = true
const path = e.detail.avatarUrl
const list = path.split('/')
const filename = list[list.length - 1]
@ -115,13 +127,16 @@ export default {
icon: 'none'
})
}
that.pending = false
},
fail(res) {
console.log(res)
console.log('上传失败', res)
console.log(data.uploadUrl, path, data.formData)
uni.showToast({
title: '头像更新失败',
icon: 'none'
})
that.pending = false
}
})
} else {
@ -129,6 +144,7 @@ export default {
title: '头像更新失败',
icon: 'none'
})
that.pending = false
}
}
})
@ -155,9 +171,15 @@ export default {
})
},
toSafeQuestion() {
this.routeJump({
name: 'safeQuestion'
})
if(this.userInfo.haveSafeAnswer) {
this.routeJump({
name: 'safeQuestion'
})
} else {
this.routeJump({
name: 'updateSafeQuestion'
})
}
}
}
}

View File

@ -75,10 +75,11 @@ export default {
updateTime() {
let time = 120
this.text = `${time} s`
const now = new Date().getTime()
const timer = setInterval(() => {
time--
this.text = `${time} s`
if(time === 0) {
const second = parseInt((new Date().getTime() - now) / 1000)
this.text = `${time - second} s`
if (time <= second) {
clearInterval(timer)
this.text = '获取验证码'
}

View File

@ -23,6 +23,10 @@ export default {
userAgreement: {
url: '/app/userAgreement',
name: '用户协议'
},
privacy: {
url: '/app/privacy',
name: '隐私政策'
}
}
const item = officialAccounts[options?.type] || officialAccounts['default']

View File

@ -42,6 +42,11 @@ const pages = [
path: '/pages/safeQuestion/safeQuestion',
tabBar: false
},
{
name: 'updateSafeQuestion',
path: '/pages/updateSafeQuestion/updateSafeQuestion',
tabBar: false
},
{
name: 'webview',
path: '/pages/webview/webview',
@ -64,7 +69,6 @@ export const useBasicStore = defineStore('basic', {
/* data name string type string params object delta number
* 具体入参查看文档 https://www.uviewui.com/js/route.html */
routeJump(data) {
console.log(data)
const page = pages.find((page) => {
return page.name === data.name
})

View File

@ -2,16 +2,32 @@
* @description 用户信息数据持久化
*/
import { defineStore } from 'pinia'
import { getUserInfoRequest } from '@/api/user'
export const useUserStore = defineStore('user', {
state() {
return {
userInfo: {}
// 用户信息
userInfo: {},
// 登录状态
isLogin: false
}
},
actions: {
updateUserInfo(data) {
this.userInfo = data
},
updateLoginStatus(status) {
this.isLogin = status
},
async login() {
uni.setStorageSync('token', '1041|9UcJSZO3C1uuDnIgG8jXTxNXDIiCrvvIhdj7bFF8c1c07b2d')
this.isLogin = true
const { code, data } = await getUserInfoRequest()
if(code === 0) {
this.updateUserInfo(data)
}
return this.isLogin
}
}
})

View File

@ -31,12 +31,29 @@ const request = (config) => {
async success(res) {
const { statusCode, data } = res
if (statusCode === 200) {
// 根据情况添加处理代码
resolve({
code: data.errorCode,
data: data.data,
message: data.errorMsg
})
const code = data.errorCode
const message = data.errorMsg
if(code === 403) {
uni.removeStorageSync('token')
getApp().globalData.updateIsLogin(false)
resolve({ code: 403, data, message: '登录已过期' })
uni.showModal({
title: '提示',
content: '登录已过期,请重新登录',
showCancel: false,
success() {
uni.reLaunch({
url: '/pages/home/home'
})
}
})
} else {
resolve({
code,
data: data.data,
message
})
}
} else {
resolve({ code: -1, data, message: '网络不太好哦,请稍后再试' })
}