Xazn-app/pages/login/index.vue
2025-07-03 08:57:13 +08:00

881 lines
23 KiB
Vue
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<view class="login-wrap">
<view class="top-wrap">
<view class="back-btn">
<u-icon v-if="formIndex==0" name="arrow-left" color="#333333" size="18" @click="handleBack()"></u-icon>
<u-icon v-if="formIndex==1" name="arrow-left" color="#333333" size="18"
@click="handleBackLogin"></u-icon>
</view>
<view class="register" @click="handleRegister()" v-if="formIndex===0">{{$tt('register.registration')}}
</view>
<image class="img" src="https://xaznkj.cn/doc/photo/logo.jpg" mode="widthFix"></image>
</view>
<view class="main-wrap">
<view v-if="formIndex==0">
<text class="title">{{ $tt('login.account') }}</text>
</view>
<view class="form" v-if="formIndex==0">
<u--form :model="loginForm" :rules="rules" ref="form" labelWidth="31">
<u-form-item prop="username">
<uni-easyinput v-model="loginForm.username" clearable :inputBorder="false"
:placeholder="$tt('login.inputUserName')" prefixIcon="person"
prefixIconStyle="font-size: 44rpx; margin-right: 10rpx;"></uni-easyinput>
</u-form-item>
<u-form-item prop="password">
<uni-easyinput prefixIcon="locked" prefixIconStyle="font-size: 44rpx; margin-right: 10rpx"
type="password" :inputBorder="false" v-model="loginForm.password"
:placeholder="$tt('login.inputPassword')"></uni-easyinput>
</u-form-item>
<u-form-item prop="code" v-if="captchaOnOff">
<!-- 注意由于兼容性差异如果需要使用前后插槽nvue下需使用u--input非nvue下需使用u-input -->
<!-- 验证码 -->
<uni-easyinput :placeholder="$tt('login.inputCode')" v-model="loginForm.code"
:inputBorder="false" prefixIcon="checkbox"
prefixIconStyle="font-size: 44rpx; margin-right: 10rpx;">
<template slot="right">
<u--image :src="codeUrl" width="186rpx" height="68rpx" radius="12rpx"
@click="getCode"></u--image>
</template>
</uni-easyinput>
</u-form-item>
<!-- 记住密码 -->
<view prop="rememberMe" style="display: flex; justify-content: space-between; padding-top: 28rpx;">
<view>
<u-checkbox-group @change="handleRememberCheckbox" size="34rpx">
<label>
<u-checkbox :checked="loginForm.rememberMe" style="transform:scale(1)" />
</label>
<text style="font-size: 28rpx;">
{{$tt('login.remember')}}
</text>
</u-checkbox-group>
</view>
<!-- 忘记密码 -->
<view style="font-size: 28rpx; color: #486FF2;" @click="forgetPassword">
{{$tt('login.forgetPassword')}}
</view>
</view>
<view style="margin-top: 60rpx;">
<button class="Login-button" @click="handleLogin()">{{$tt('login.login')}}</button>
</view>
</u--form>
</view>
<!-- 忘记密码 -->
<view class="forgetForm" v-if="formIndex==1">
<view style="margin-bottom: 40rpx;">
<text class="title">{{ $tt('login.forgetPassword') }}</text>
</view>
<u--form :model="fpForm" :rules="fpRules" ref="fpForm" labelWidth="31">
<u-form-item prop="phoneNumber" labelWidth="0">
<u-input v-model="fpForm.phoneNumber" clearable :inputBorder="false" border="none"
:maxlength="11" :placeholder="$tt('register.inputPhone')" prefixIcon="phone"
prefixIconStyle="font-size: 42rpx;color: rgb(192, 196, 204); margin-left:10rpx"
placeholderStyle="font-size: 22rpx;"
:customStyle="{padding:'20rpx 0',backgroundColor:' #fff', borderRradius:' 18rpx'}"></u-input>
</u-form-item>
<u-form-item prop="code" labelWidth="0">
<u-input :placeholder="$tt('register.inputCode')" v-model="fpForm.code" clearable border="none"
:customStyle="{padding:'20rpx 0',backgroundColor:' #fff', borderRradius:' 18rpx'}"
prefixIcon="integral-fill"
prefixIconStyle="font-size: 42rpx;color: rgb(192, 196, 204); margin-left:10rpx"
placeholderStyle="font-size: 22rpx;">
<template slot="suffix">
<view style="margin-right: 15rpx;">
<u-code ref="uCode" @change="codeChange" seconds="60"></u-code>
<u-button @click="getSmsCode" :text="tips" type="primary" size="small"
:disabled="fpForm.phoneNumber == ''" style="border:none;"></u-button>
</view>
</template>
</u-input>
</u-form-item>
<u-form-item prop="password" labelWidth="0">
<u-input v-model="fpForm.password" clearable :inputBorder="false" border="none"
prefixIcon="lock-fill" :placeholder="$tt('register.inputPassword')"
prefixIconStyle="font-size: 42rpx;color: rgb(192, 196, 204); margin-left:10rpx"
placeholderStyle="font-size: 22rpx;"
:customStyle="{padding:'20rpx 0',backgroundColor:' #fff', borderRradius:' 18rpx'}"></u-input>
</u-form-item>
<u-form-item prop="confirmPassword" labelWidth="0">
<u-input v-model="fpForm.confirmPassword" clearable :inputBorder="false" border="none"
prefixIcon="lock-fill" type="password" placeholder="确认密码"
prefixIconStyle="font-size: 42rpx;color: rgb(192, 196, 204); margin-left:10rpx"
placeholderStyle="font-size: 22rpx;"
:customStyle="{padding:'20rpx 0',backgroundColor:' #fff', borderRradius:' 18rpx'}"></u-input>
</u-form-item>
<view style="margin-top: 60rpx;">
<button class="Login-button" @click="handleForgetPsd()">确认</button>
</view>
</u--form>
</view>
<view class="footer-wrap" v-if="formIndex===0">
<view class="tipbox">
<view class="divider-wrap">
<u-divider :text="$tt('login.thirdPartyLogin')" textColor="#999999"
lineColor="#D8D8D8"></u-divider>
</view>
<view class="otherUser">
<!-- 短信登录app和h5 -->
<!-- #ifdef APP-PLUS ||H5-->
<view class="item">
<uni-icons style="background-color: #E3EDFF; padding: 18rpx 20rpx; border-radius: 12rpx;"
type="chat-filled"  size="24"  color="#045FFA" @click="gotoSmsLogin()"></uni-icons>
<text class="text">{{$tt('login.smsLogin')}}</text>
</view>
<!-- #endif -->
<!-- 微信登录 -->
<!-- #ifdef APP-PLUS -->
<view class="item">
<uni-icons style="background-color: #E3EDFF; padding: 18rpx 20rpx; border-radius: 12rpx;"
type="weixin"  size="24"  color="#045FFA" @click="handleWeChatLogin()"></uni-icons>
<text class="text">{{$tt('login.weChat')}}</text>
</view>
<!-- #endif -->
<!-- 微信小程序手机一键登录 -->
<!-- #ifdef MP-WEIXIN -->
<view class="item">
<uni-icons style="background-color: #E3EDFF; padding: 18rpx 20rpx; border-radius: 12rpx;"
type="phone-filled"  size="24"  color="#486FF2"
@click="handleWeChatOneClickLogin"></uni-icons>
<text class="text">{{$tt('login.mobileLogin')}}</text>
</view>
<!-- #endif -->
</view>
</view>
</view>
</view>
<view class="other-wrap">
<!-- 微信授权弹出框 -->
<!-- #ifdef MP-WEIXIN -->
<view class="one-click-login-pop-wrap">
<u-popup :show="isShowPop" mode="bottom" :round="10" closeable="true" @close="isShowPop = false">
<view class="content-wrap">
<u--image :showLoading="true" src="https://xaznkj.cn/app/fastbee1_blue.png" width="260rpx"
height="90rpx" customStyle="float:left"></u--image>
<text class="title">{{$tt("login.welcomeToLogin")}}</text>
<view class="btn-login">
<u-button open-type="getPhoneNumber" @getphonenumber="getPhoneNumber"
@click="isShowPop = false;"
type="success">{{$tt("login.phoneAuthorization")}}</u-button>
</view>
</view>
</u-popup>
</view>
<!-- #endif -->
</view>
</view>
</template>
<script>
import {
mapState,
mapMutations
} from 'vuex';
import projectConfig from '@/env.config.js';
import {
getProfile,
getSmsCode,
smsLogin,
forgetPwdReset
} from '@/apis/modules/common.js'
import {
encrypt,
decrypt
} from '@/utils/jsencrypt.js'
export default {
data() {
return {
token: '',
codeUrl: '',
isClause: false,
show: false,
systemLocale: '',
applicationLocale: '',
columns: [
[{
text: this.$tt('locale.en-US'),
code: 'en-US'
},
{
text: this.$tt('locale.zh-CN'),
code: 'zh-CN'
},
]
],
// 忘记密码
fpForm: {
phoneNumber: '',
code: '',
password: '',
confirmPassword: '',
},
captchaOnOff: true, //验证码开关
formIndex: 0,
value: 'English',
loginForm: {
username: '',
password: '',
rememberMe: false,
code: '',
uuid: '',
},
tips: '',
rules: {
username: {
type: 'string',
min: 2,
max: 20,
required: true,
message: this.$tt('login.inputUserName'),
trigger: ['blur', 'change']
},
password: {
type: 'string',
required: true,
min: 5,
max: 20,
message: this.$tt('login.inputPassword'),
trigger: ['blur', 'change']
},
code: {
type: 'integer',
required: true,
message: this.$tt('login.inputCode'),
trigger: ['blur', 'change']
}
},
isShowPop: false,
fpRules: {
phoneNumber: [{
type: 'integer',
min: 11,
max: 11,
required: true,
message: this.$tt('register.inputPhone'),
trigger: ['blur', 'change']
},
{
validator: (rule, value, callback) => {
return uni.$u.test.mobile(value);
},
message: this.$tt('bindRegister.incorrectPhone'),
// 触发器可以同时用blur和change
trigger: ['change', 'blur']
}
],
code: {
type: 'string',
required: true,
min: 5,
max: 20,
message: this.$tt('login.inputCode'),
trigger: ['blur', 'change']
},
password: {
type: 'string',
required: true,
min: 6,
max: 20,
message: '请输入密码长度在6-20个字符',
trigger: ['blur', 'change']
},
confirmPassword: [{
type: 'string',
required: true,
message: this.$tt('user.password7'),
trigger: ['blur', 'change']
},
{
validator: (rule, value, callback) => {
if (this.passwordForm.newPsd !== value) {
callback(new Error(this.$tt('user.password8')));
} else {
callback();
}
},
message: this.$tt('user.password8'),
trigger: ['blur', 'change'],
}
]
}
};
},
onLoad() {
this.loadSelectedLanguage();
},
mounted() {
this.getToken();
if (this.token != '' && this.token != null) {
// 跳转主页
uni.switchTab({
url: '/pages/tabBar/home/index'
});
} else {
this.getCode();
this.getAccount();
}
},
methods: {
loadSelectedLanguage() {
// 获取之前保存的语言设置
const selectedLanguage = wx.getStorageSync('language');
// 如果获取到了,使用该语言设置
if (selectedLanguage === 'zh-CN') {
this.applicationLocale = 'zh-CN';
this.value = '简体中文';
} else if (selectedLanguage === 'en-US') {
this.applicationLocale = 'en-US';
this.value = 'English';
} else {
// 如果没有获取到,使用默认设置
this.applicationLocale = 'zh-CN'; // 默认设置为中文
this.$i18n.locale = this.applicationLocale;
this.value = '简体中文';
}
},
//获取短信验证码
getSmsCode() {
if (this.$refs.uCode.canGetCode) {
if (this.validatePhoneNumber(this.fpForm.phoneNumber)) {
if (this.fpForm.phoneNumber) {
uni.showLoading({
title: this.$tt('smsLogin.obtainSmsCode')
});
getSmsCode(this.fpForm.phoneNumber).then(res => {
if (res.code == 200) {
uni.$u.toast(this.$tt('smsLogin.codeSent'));
// 通知验证码组件内部开始倒计时
this.$refs.uCode.start();
} else {
uni.$u.toast(res.msg);
}
})
.catch(err => {
console.log("err catch", err);
})
} else {
uni.$u.toast(this.$tt('register.inputPhone'));
}
}
} else {
uni.$u.toast(this.$tt('smsLogin.sending'));
}
},
//忘记密码
handleForgetPsd() {
this.$refs.fpForm.validate().then(res => {
this.fpLoading = true;
const {
confirmPassword,
...params
} = this.fpForm;
forgetPwdReset(params)
.then((res) => {
if (res.code === 200) {
uni.showToast({
icon: "success",
title: res.msg,
});
this.formIndex = 0;
} else {
uni.showToast({
icon: "none",
title: res.msg,
});
}
})
.catch(() => {
this.fpLoading = false;
});
});
},
// 定义校验函数
validatePhoneNumber(phone) {
// 正则表达式规则1开头的10位或者13、14、15、16、17、18、19开头的11位数字
const regExp =
/^((1[3-9]\d{9})|((\+?\d{2,3}-)?(13\d{9}|14[57]|15[0-35-9]|16[6]|17[0135678]|18[\d]{9}|\d{3}-\d{8})))$/;
return regExp.test(phone);
},
codeChange(text) {
this.tips = text;
text = this.$tt('smsLogin.obtainCode');
},
//返回
handleBack() {
uni.navigateBack();
},
handleBackLogin() {
this.formIndex = 0;
},
onLocaleChange() {
this.show = true;
},
close() {
this.show = false
},
cancel() {
this.show = false
},
confirm(e) {
this.show = false;
this.value = e.value[0].text;
this.$i18n.locale = e.value[0].code;
uni.setLocale(e.value[0].code);
wx.setStorageSync('language', e.value[0].code)
},
change(e) {},
handleLogin() {
this.$refs.form.validate().then(res => {
// 调用登录
this.$api.common.login(this.loginForm).then(async res => {
if (res.code == 200) {
// 存储token和账号
this.saveToken(res.token);
this.saveAccount();
// 获取用户信息
let profile = await this.getProfile();
uni.$u.vuex('profile', profile);
// 跳转主页
uni.reLaunch({
url: '/pages/tabBar/home/index'
});
} else {
if (res.msg) {
uni.showToast({
icon: "none",
title: res.msg,
complete: (res) => {
setTimeout(() => {
this.getCode();
}, 1500);
}
})
}
}
})
}).catch(errors => {
uni.$u.toast(this.$tt('login.accontMsg'));
});
},
handletestLogin() {
this.loginForm.username = "fastbee"
this.loginForm.password = "123456"
},
// 忘记密码
forgetPassword() {
this.formIndex = 1;
},
// 微信登录
handleWeChatLogin() {
let that = this;
uni.login({
provider: 'weixin',
onlyAuthorize: false,
success: function(loginRes) {
if (loginRes) {
console.log('用户授权成功');
uni.request({
url: projectConfig.baseUrl + '/wechat/mobileLogin',
data: {
//业务服务器通过code + 仅保存在服务器的appsecret参数微信开放平台接口发起网络请求
code: loginRes.code,
accessToken: loginRes.authResult.access_token,
expiresIn: loginRes.authResult.expires_in,
refreshToken: loginRes.authResult.refresh_token,
openId: loginRes.authResult.openid,
unionId: loginRes.authResult.unionid,
},
header: {
language: wx.getStorageSync('language') || 'zh-CN'
},
method: 'post',
success: async res => {
if (res.data.code == 200) {
if (res.data.data.token != null) {
that.saveToken(res.data.data.token);
// 获取用户信息
let profile = await that.getProfile();
uni.$u.vuex('profile', profile);
// 跳转主页面
wx.switchTab({
url: '/pages/tabBar/home/index'
})
} else {
//跳转绑定页面
uni.navigateTo({
url: '/pagesB/login/bindLogin?bindId=' +
res
.data.data.bindId,
})
}
} else {
uni.showToast({
icon: "none",
mask: true,
title: res.data.msg,
duration: 3000,
})
}
},
fail(err) {
console.log(err)
}
});
}
},
fail(err) {
console.log(err)
}
})
},
// 微信一键登录
handleWeChatOneClickLogin() {
this.isShowPop = true;
},
// 获取验证码
getCode() {
this.$api.common.captchaImage(true).then(res => {
this.captchaOnOff = res.captchaEnabled;
if (this.captchaOnOff) {
this.codeUrl = 'data:image/gif;base64,' + res.img;
this.loginForm.uuid = res.uuid;
this.loginForm.code = '';
}
})
.catch(err => {
this.$u.toast(err.msg);
});
},
// 用户注册
handleRegister() {
uni.$u.route('/pagesB/login/register');
},
//短信登录
gotoSmsLogin() {
uni.$u.route('/pagesB/login/smsLogin');
},
// 获取用户信息
getProfile() {
return new Promise((resolve, reject) => {
getProfile().then(res => {
resolve(res.data);
}).catch(err => {
this.$u.toast(err.msg);
})
});
},
saveToken(token) {
// 本地缓存存储token
uni.setStorageSync('token', token);
// vuex存储token
uni.$u.vuex('vuex_token', token);
},
getToken() {
// 本地缓存获取token
this.token = uni.getStorageSync('token');
// vuex存储token
uni.$u.vuex('vuex_token', this.token);
},
// 本地缓存存储
saveAccount() {
if (this.loginForm.rememberMe) {
uni.setStorageSync('username', this.loginForm.username);
uni.setStorageSync('password', encrypt(this.loginForm.password));
uni.setStorageSync('rememberMe', this.loginForm.rememberMe)
} else {
uni.removeStorageSync('username');
uni.removeStorageSync('password');
uni.removeStorageSync('rememberMe');
}
},
// 本地缓存获取
getAccount() {
let username = uni.getStorageSync('username');
let password = uni.getStorageSync('password');
let rememberMe = uni.getStorageSync('rememberMe');
if (username && username != '') {
this.loginForm.username = username;
}
if (password && password != '') {
this.loginForm.password = decrypt(password);
}
if (rememberMe != '') {
this.loginForm.rememberMe = rememberMe;
}
},
//微信小程序登录获取手机号
getPhoneNumber(e) {
let that = this;
wx.login({
success(res) {
if (e.detail.code && res.code) {
console.log('用户授权成功');
//发起网络请求
if (projectConfig.baseUrl.endsWith('/')) {
// 如果以斜杠结尾,则移除它
projectConfig.baseUrl = projectConfig.baseUrl.slice(0, -1);
}
wx.request({
url: projectConfig.baseUrl + '/wechat/miniLogin',
data: {
code: res.code,
phoneCode: e.detail.code,
},
header: {
language: wx.getStorageSync('language') || 'zh-CN'
},
method: 'post',
success: async res => {
if (res.data.code == 200) {
// 存储token和账号
const token = res.data.data.token;
that.saveToken(token);
// 获取用户信息
let profile = await that.getProfile();
uni.$u.vuex('profile', profile);
// 跳转主页
uni.reLaunch({
url: '/pages/tabBar/home/index'
});
} else {
wx.showToast({
icon: "none",
mask: true,
title: res.data.msg,
duration: 3000,
})
}
},
})
} else {
console.log('用户拒绝授权');
}
}
})
},
// 服务协议
handleService() {
let title = this.$tt('login.serviceAgreement');
let url = projectConfig.officialWebUrl + 'service-agreement.html';
uni.navigateTo({
url: `/pages/common/webview/index?title=${title}&url=${url}`
});
},
// 隐私政策
handlePrivacy() {
let title = this.$tt('login.privacyPolicy');
let url = projectConfig.officialWebUrl + 'privacy-policy.html';
uni.navigateTo({
url: `/pages/common/webview/index?title=${title}&url=${url}`
});
},
//记住密码
handleRememberCheckbox(e) {
this.loginForm.rememberMe = !this.loginForm.rememberMe
},
// 勾选协议
handleClauseCheckbox(e) {
this.isClause = !this.isClause;
},
openhpLink() {
uni.navigateTo({
url: '/pages/common/webview/index?url=http://www.hpiot.cn/'
});
}
}
};
</script>
<style lang="scss" scoped>
page {
height: 100%;
}
.copyright {
font-size: 16rpx;
margin-top: 20rpx;
}
.login-wrap {
display: flex;
flex-direction: column;
padding-top: var(--status-bar-height);
height: 100vh;
background-image: linear-gradient(45deg,
rgba(216, 216, 216, 0.3) 0,
rgba(225, 225, 225, 0) 100%);
.top-wrap {
position: relative;
display: flex;
.back-btn {
position: absolute;
top: 70rpx;
left: 46rpx;
}
.register {
position: absolute;
// #ifdef MP-WEIXIN
top: 140rpx;
// #endif
// #ifndef MP-WEIXIN
top: 70rpx;
// #endif
right: 46rpx;
line-height: 36rpx;
font-size: 28rpx;
font-weight: 400;
}
.img {
margin: 0 auto;
width: 338rpx;
margin-top: 236rpx;
}
}
.main-wrap {
flex: 1 auto;
padding-left: 80rpx;
padding-right: 80rpx;
margin-top: 142rpx;
overflow-y: auto;
display: flex;
flex-direction: column;
//display: grid;
.title {
font-size: 40rpx;
letter-spacing: 0.6rpx;
font-weight: 400;
}
.form {
margin-top: 46rpx;
}
.forgetForm {
margin-top: 0;
}
.Login-button {
display: flex;
justify-content: center;
align-items: center;
height: 98rpx;
border-radius: 18rpx;
color: #fff;
background-color: #486FF2;
font-weight: 400;
font-size: 32rpx;
letter-spacing: 0.6rpx;
}
.footer-wrap {
margin-top: auto;
margin-bottom: 38px;
.tipbox {
text-align: center;
.divider-wrap {
margin-bottom: 38rpx;
padding: 0 112rpx;
}
}
.otherUser {
display: flex;
flex-direction: row;
justify-content: center;
align-items: center;
gap: 10px;
.item {
display: flex;
flex-direction: column;
align-items: center;
width: 136rpx;
.text {
margin-top: 10rpx;
letter-spacing: 0.6rpx;
font-size: 20rpx;
font-weight: 400;
}
}
}
.txt {
font-size: 28rpx;
color: #cbcbcb;
}
.item-wrap {
background-color: #eaeaea;
width: 50px;
height: 50px;
border-radius: 50%;
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
margin: 10rpx 20rpx;
}
.clause {
font-size: 28rpx;
display: inline-block;
vertical-align: middle;
.service,
.privacy {
color: #486FF2;
}
}
}
}
.other-wrap {
.one-click-login-pop-wrap {
.content-wrap {
display: flex;
flex-direction: column;
align-items: center;
margin-top: 90rpx;
.title {
margin-top: 30rpx;
font-size: 36rpx;
}
.btn-login {
margin-top: 100rpx;
margin-bottom: 60rpx;
width: 320rpx;
border-radius: 20rpx
}
}
}
}
}
::v-deep .uni-easyinput__content-input {
font-size: 32rpx;
height: 88rpx;
}
::v-deep .uni-easyinput__content {
border-radius: 18rpx;
}
</style>