643 lines
23 KiB
Vue
643 lines
23 KiB
Vue
<template>
|
||
<view class="login-wrap" :class="[`${themeInfo.theme}-theme`]">
|
||
<navBar></navBar>
|
||
<view class="container">
|
||
<view class="logoBox">
|
||
<image src="/static/logo.png" mode=""></image>
|
||
<text>EMPOWER YOUR SELF STORAGE</text>
|
||
</view>
|
||
<view class="formBox">
|
||
<view class="form">
|
||
<uv-form labelPosition="left" :model="state" labelWidth="160rpx" :rules="rules" ref="formRef">
|
||
<!-- #ifndef MP-WEIXIN-->
|
||
<!-- #ifndef APP-PLUS -->
|
||
<uv-form-item :label="$t('login.account')" prop="username" borderBottom>
|
||
<uv-input v-model="state.username" :placeholder="$t('login.account')" border="none">
|
||
</uv-input>
|
||
</uv-form-item>
|
||
<uv-form-item :label="$t('login.password')" prop="password" borderBottom>
|
||
<uv-input v-model="state.password" :placeholder="$t('login.password')"
|
||
:type="state.passwordVisible ? 'text' : 'password'" border="none">
|
||
<template #suffix>
|
||
<uv-icon @click="
|
||
state.passwordVisible =
|
||
!state.passwordVisible
|
||
" :name="state.passwordVisible
|
||
? 'eye-off-outline'
|
||
: 'eye'
|
||
" size="18"></uv-icon>
|
||
</template>
|
||
</uv-input>
|
||
</uv-form-item>
|
||
<uv-form-item :label="$t('login.code')" prop="code" borderBottom>
|
||
<uv-input v-model="state.code" :placeholder="$t('login.code')" border="none">
|
||
</uv-input>
|
||
<template #right>
|
||
<img class="code" @click="getCodeImage()" :src="'data:image/gif;base64,' + state.codeImage
|
||
" />
|
||
</template>
|
||
</uv-form-item>
|
||
|
||
<view class="btn-wrap">
|
||
<uv-button shape="circle" block type="primary" @click="onSubmit">
|
||
{{ $t('login.login') }}
|
||
</uv-button>
|
||
|
||
<view class="goLogin" @click="goRegister">
|
||
{{ $t("login.register") }}
|
||
</view>
|
||
<text class="forget-text" @click="goForgotPawd">{{ $t("login.forget") }}</text>
|
||
</view>
|
||
<!-- #endif -->
|
||
<!-- #endif -->
|
||
<!-- #ifdef APP-PLUS -->
|
||
<uv-form labelPosition="left" :model="state" labelWidth="160rpx" :rules="rulesApp" ref="formRef">
|
||
<uv-form-item :label="$t('login.phone')" prop="phone" borderBottom>
|
||
|
||
<uv-input
|
||
v-model="state.phone"
|
||
:placeholder="$t('login.phone')"
|
||
border="none"
|
||
style="flex: 1;"
|
||
>
|
||
<template v-slot:prefix>
|
||
<view @click="showAreaCodePicker" style="padding-right: 16rpx; min-width: 100rpx; color: #333;display: flex;align-items: center;">
|
||
{{ state.areaCode }} <view style="transform: rotate(90deg);"><uv-icon :size="14" name="play-right-fill"></uv-icon></view>
|
||
</view>
|
||
</template>
|
||
</uv-input>
|
||
</uv-form-item>
|
||
<uv-form-item :label="$t('login.code')" prop="smsCode" borderBottom>
|
||
<uv-input v-model="state.smsCode" :placeholder="$t('login.inputCode')" border="none" />
|
||
<template #right>
|
||
<uv-button size="small" type="primary" :disabled="countdown > 0" @click="sendSmsCode">
|
||
{{ countdown > 0 ? `${countdown}s` : $t('login.getCode') }}
|
||
</uv-button>
|
||
</template>
|
||
</uv-form-item>
|
||
<view class="btn-wrap">
|
||
<uv-button shape="circle" block type="primary" @click="onAppLogin">
|
||
{{ $t('login.login') }}
|
||
</uv-button>
|
||
<view class="UserAgreementtips" :class="{'shake UserAgreementtips': state.isShaking}" @click="changeCheck(false)">
|
||
<uv-checkbox-group v-model="state.checked" @change="changeCheck(true)">
|
||
<uv-checkbox class="myCheckbox" :name="true"> </uv-checkbox>
|
||
</uv-checkbox-group> {{ $t('common.FirstTimeLoginTips') }},{{ $t('login.andAgreeTo') }}<text @click.stop="goAgreement">{{ $t('login.UserAgreement') }}</text>。
|
||
</view>
|
||
</view>
|
||
</uv-form>
|
||
<uv-picker
|
||
ref="AreaCodePickerRef"
|
||
:columns="[areaCodeList]"
|
||
:confirmText="$t('common.confirm')"
|
||
:cancelText="$t('common.cancel')"
|
||
keyName="label"
|
||
@confirm="onAreaCodeConfirm"
|
||
/>
|
||
<!-- #endif -->
|
||
<!-- #ifdef MP-WEIXIN -->
|
||
<uv-button customStyle="margin:100rpx 0" color="#5BBC6B" shape="circle" block type="primary"
|
||
@click="wxGetUserProfile">
|
||
{{ $t("login.wxLogin") }}
|
||
</uv-button>
|
||
<!-- #endif -->
|
||
</uv-form>
|
||
|
||
</view>
|
||
</view>
|
||
<!-- #ifdef MP-WEIXIN -->
|
||
<uv-popup ref="popup"
|
||
customStyle="width: 80%; padding:20rpx 0; border-radius: 32rpx; display: flex; flex-direction: column; justify-content: center; align-items: center;"
|
||
@change="popupChange">
|
||
<text>{{ $t("common.bindPhone") }}</text>
|
||
<uv-form labelPosition="left" labelWidth="80" ref="formRef">
|
||
<uv-form-item :label="$t('common.userName')" prop="username" borderBottom>
|
||
<uv-input v-model="state.username" class="weui-input" border="none"
|
||
:placeholder="$t('common.userName')" />
|
||
</uv-form-item>
|
||
</uv-form>
|
||
<text style="padding: 0 40rpx; margin-top: 20rpx; text-align: center;">{{ $t('common.bindPhoneAfter') }}</text>
|
||
<button style="width: 80%; margin-top: 20px; background: #5BBC6B; color: #FFFFFF; line-height: 80rpx;"
|
||
open-type="getPhoneNumber" @getphonenumber="getPhoneNumber">{{ $t("common.QuickBind") }}</button>
|
||
<view style="width: 80%; margin-top: 20px; text-align: center;" @click="isToHome">{{ $t("common.cancelBind") }}</view>
|
||
</uv-popup>
|
||
<!-- #endif -->
|
||
</view>
|
||
<myPopup
|
||
v-model="state.showAgreement"
|
||
type="center"
|
||
:mask-closable="false"
|
||
style="width: 100%; max-height: 80vh;padding:10px;"
|
||
>
|
||
<scroll-view scroll-y style="max-height: 60vh; padding: 16rpx; white-space: pre-line;">
|
||
<view>
|
||
<view class="AgreementTitle">用户协议 User Agreement</view>
|
||
<view>
|
||
<view> 1. 您同意遵守本应用的各项使用规定,不得利用本应用进行违法或侵权行为。 </view>
|
||
<view> You agree to comply with all the rules of this app and shall not use it for illegal or infringing activities.</view>
|
||
|
||
<view>2. 本应用有权根据需要修改或更新本协议内容,修改后的协议将在应用内公告。 </view>
|
||
<view> The app reserves the right to modify or update this agreement as needed, with changes announced within the app.</view>
|
||
|
||
<view>3. 您理解并同意,首次登录即视为同意本协议及隐私政策。 </view>
|
||
<view> You understand and agree that the first login signifies your acceptance of this User Agreement and Privacy Policy.</view>
|
||
</view>
|
||
</view>
|
||
|
||
<view style="margin-top: 24rpx;">
|
||
<view class="AgreementTitle">隐私政策 Privacy Policy</view>
|
||
<view>1. 我们重视您的隐私保护,严格按照相关法律法规收集和使用您的个人信息。 </view>
|
||
<view> We value your privacy and collect and use your personal information in accordance with relevant laws and regulations.</view>
|
||
<view>2. 您的手机号、设备信息、登录状态等信息将用于账户认证和服务优化。 </view>
|
||
<view>Your phone number, device information, login status, and other data will be used for account authentication and service optimization.</view>
|
||
<view>3. 您的信息不会未经授权向第三方披露,除非法律法规另有规定。 </view>
|
||
<view> Your information will not be disclosed to third parties without your authorization, except as required by law.</view>
|
||
<view>4. 您可以通过应用设置管理您的个人信息权限。 </view>
|
||
<view>You can manage your personal information permissions through the app settings. </view>
|
||
</view>
|
||
</scroll-view>
|
||
|
||
<view style="text-align: center; margin-top: 24rpx;">
|
||
<button
|
||
type="primary"
|
||
style="width: 80%; margin: 0 auto;"
|
||
@click="agree"
|
||
>
|
||
同意
|
||
</button>
|
||
</view>
|
||
</myPopup>
|
||
</view>
|
||
</template>
|
||
|
||
<script setup>
|
||
import { ref } from "vue";
|
||
import { onTabItemTap, navigateBack, isToHome } from "/utils/common.js";
|
||
import { useLoginApi } from "@/Apis/login.js";
|
||
import { onLoad, onShow } from "@dcloudio/uni-app";
|
||
import navBar from "@/components/navBar.vue";
|
||
import myPopup from '@/components/myPopup.vue';
|
||
// 主题色配置
|
||
import { useMainStore } from "@/store/index.js";
|
||
const { themeInfo, storeState, getUserInfo } = useMainStore();
|
||
// 国际化配置
|
||
import { useI18n } from 'vue-i18n';
|
||
const { t } = useI18n();
|
||
const AreaCodePickerRef = ref();
|
||
const areaCodeList = ref([
|
||
{ label: '+86 (大陆)', value: '+86' },
|
||
{ label: '+852 (香港)', value: '+852' },
|
||
{ label: '+853 (澳门)', value: '+853' },
|
||
{ label: '+886 (台湾)', value: '+886' },
|
||
{ label: '+1 (美国/加拿大)', value: '+1' },
|
||
{ label: '+44 (英国)', value: '+44' },
|
||
{ label: '+81 (日本)', value: '+81' },
|
||
]);
|
||
const showAreaCodePicker = ()=>{
|
||
console.log(AreaCodePickerRef,"AreaCodePickerRef")
|
||
AreaCodePickerRef.value.open();
|
||
}
|
||
const closeAreaCodePicker = ()=>{
|
||
AreaCodePickerRef.value.close();
|
||
}
|
||
const ifMiniProgram = ref(false);
|
||
const getApi = useLoginApi();
|
||
const formRef = ref();
|
||
const rules = {
|
||
phone: [
|
||
{
|
||
required: true,
|
||
message: t("login.inputPhone"),
|
||
trigger: ["blur", "change"],
|
||
},
|
||
],
|
||
smsCode: [
|
||
{
|
||
required: true,
|
||
message: t("login.inputCode"),
|
||
trigger: ["blur", "change"],
|
||
},
|
||
],
|
||
username: [
|
||
{
|
||
required: true,
|
||
message: t("login.input"),
|
||
trigger: ["blur", "change"],
|
||
},
|
||
],
|
||
password: [
|
||
{
|
||
required: true,
|
||
message: t("login.input"),
|
||
trigger: ["blur", "change"],
|
||
},
|
||
],
|
||
code: [
|
||
{
|
||
required: true,
|
||
message: t("login.input"),
|
||
trigger: ["blur", "change"],
|
||
},
|
||
],
|
||
};
|
||
const goAgreement = ()=>{
|
||
state.value.showAgreement = true;
|
||
}
|
||
const onAreaCodeConfirm = (e) => {
|
||
state.value.areaCode = e.value[0].value;
|
||
closeAreaCodePicker();
|
||
};
|
||
const countdown = ref(0);
|
||
let timer = null;
|
||
const state = ref({
|
||
areaCode: '+86',
|
||
phone: '',
|
||
smsCode: '',
|
||
codeImage: "",
|
||
username: "",
|
||
uuid: "",
|
||
password: "",
|
||
passwordVisible: false,
|
||
code: "",
|
||
isShaking: false,
|
||
showAgreement:false, // 是否显示协议
|
||
checked: [], // 是否同意协议,
|
||
looked: false, // 是否已阅读协议
|
||
});
|
||
|
||
const changeCheck = (isChange)=>{
|
||
if(!state.value.looked){
|
||
if(isChange){
|
||
state.value.checked = !state.value.checked.length ? [true] : [];
|
||
}
|
||
uni.showToast({ title: t("common.checkAgreementUrl"), icon: "none" });
|
||
return
|
||
}
|
||
if(!isChange){
|
||
state.value.checked = !state.value.checked.length ? [true] : [];
|
||
}
|
||
|
||
}
|
||
const agree = ()=>{
|
||
state.value.looked = true;
|
||
state.value.checked = [true];
|
||
state.value.showAgreement = false;
|
||
}
|
||
|
||
const sendSmsCode = async () => {
|
||
if (!state.value.phone.trim()) {
|
||
uni.showToast({ title: t("login.phoneFormat"), icon: "none" });
|
||
return;
|
||
}
|
||
|
||
uni.showLoading({ title: t("login.sending") });
|
||
try {
|
||
const res = await getApi.sendSmsCode({ phone: state.value.phone });
|
||
if (res.code === 200) {
|
||
uni.showToast({ title: t("login.sendSuccess"), icon: "success" });
|
||
countdown.value = 60;
|
||
timer = setInterval(() => {
|
||
countdown.value--;
|
||
if (countdown.value <= 0) clearInterval(timer);
|
||
}, 1000);
|
||
} else {
|
||
uni.showToast({ title: res.msg || "error", icon: "none" });
|
||
}
|
||
} finally {
|
||
uni.hideLoading();
|
||
}
|
||
};
|
||
|
||
const shake = () => {
|
||
state.value.isShaking = true;
|
||
setTimeout(() => {
|
||
state.value.isShaking = false;
|
||
}, 1000);
|
||
}
|
||
|
||
const onAppLogin = () => {
|
||
if(!state.value.checked.length){
|
||
shake();
|
||
return
|
||
}
|
||
// formRef.value
|
||
// .validate()
|
||
// .then(() => {
|
||
// uni.showLoading();
|
||
// getApi.appLogin({
|
||
// phone: state.value.phone,
|
||
// code: state.value.smsCode,
|
||
// }).then((res) => {
|
||
// uni.hideLoading();
|
||
// if (res.code === 200) {
|
||
// uni.setStorageSync("token", res.data.token);
|
||
// isToHome();
|
||
// } else {
|
||
// uni.showToast({ title: res.msg || "登录失败", icon: "none" });
|
||
// }
|
||
// });
|
||
// })
|
||
// .catch((error) => {
|
||
// console.log(error);
|
||
// });
|
||
};
|
||
|
||
const goRegister = () => {
|
||
uni.navigateTo({
|
||
url: "/pages/register/index",
|
||
});
|
||
};
|
||
const goForgotPawd = () => {
|
||
uni.navigateTo({
|
||
url: "/pages/forgotPawd/index",
|
||
});
|
||
};
|
||
const togglePasswordVisibility = () => {
|
||
state.value.passwordVisible = !state.value.passwordVisible;
|
||
};
|
||
// 弹窗开启关闭
|
||
const popupChange = (event) => {
|
||
// 如果关闭 就跳转
|
||
if (!event.show) {
|
||
isToHome()
|
||
}
|
||
}
|
||
const getCodeImage = () => {
|
||
getApi.getCode().then((res) => {
|
||
if (res.code === 200) {
|
||
state.value.uuid = res.data.uuid;
|
||
state.value.codeImage = res.data.img;
|
||
}
|
||
});
|
||
};
|
||
// #ifndef MP-WEIXIN
|
||
getCodeImage();
|
||
// #endif
|
||
const onSubmit = (values) => {
|
||
const data = {
|
||
emailAddress: state.value.username,
|
||
password: state.value.password,
|
||
uuid: state.value.uuid,
|
||
code: state.value.code,
|
||
};
|
||
formRef.value
|
||
.validate()
|
||
.then((res) => {
|
||
uni.showLoading();
|
||
getApi.Login(data).then((res) => {
|
||
uni.hideLoading();
|
||
if (res.code == 200) {
|
||
uni.setStorageSync("token", res.data);
|
||
isToHome()
|
||
} else {
|
||
getCodeImage();
|
||
}
|
||
});
|
||
})
|
||
.catch((error) => {
|
||
console.log(error);
|
||
});
|
||
|
||
};
|
||
|
||
const code = ref("");
|
||
const encryptedData = ref("");
|
||
const iv = ref("");
|
||
const popup = ref()
|
||
const wxLogin = () => {
|
||
// 微信 登陆授权 code
|
||
return new Promise(function (reslove, reject) {
|
||
wx.login({
|
||
success(res) {
|
||
code.value = res.code;
|
||
reslove(res.code);
|
||
},
|
||
fail: (err) => {
|
||
reject(err)
|
||
console.error("wx.login调用失败:", err);
|
||
},
|
||
})
|
||
})
|
||
}
|
||
const AuthorizedLogin = (data) => {
|
||
uni.showLoading()
|
||
getApi.AuthorizedLogin(data)
|
||
.then(async (res) => {
|
||
uni.hideLoading()
|
||
if (res.code == 200) {
|
||
storeState.token = res.data.token;
|
||
uni.setStorageSync("token", res.data.token);
|
||
uni.setStorageSync("openId", res.data.openId);
|
||
uni.removeStorage({key:'Pre_ID'})
|
||
uni.removeStorage({key:'mediatorId'})
|
||
const { data: userInfo } = await getUserInfo()
|
||
if (!userInfo.phone) {
|
||
popup.value.open()
|
||
} else {
|
||
isToHome()
|
||
}
|
||
}
|
||
});
|
||
}
|
||
async function wxGetUserProfile() {
|
||
uni.showLoading()
|
||
wx.getUserProfile({
|
||
desc: "用于完善会员资料", // 获取用户信息的提示语
|
||
success: async (res) => {
|
||
const userInfo = res;
|
||
encryptedData.value = res.encryptedData;
|
||
iv.value = res.iv;
|
||
// 微信 登陆授权 code
|
||
uni.checkSession({
|
||
success: (res) => {
|
||
// 发起网络请求
|
||
AuthorizedLogin({
|
||
code: code.value,
|
||
encryptedData: encryptedData.value,
|
||
iv: iv.value,
|
||
openId: uni.getStorageSync("openId"),
|
||
Pre_ID: uni.getStorageSync("Pre_ID"),
|
||
mediatorId: uni.getStorageSync('mediatorId')
|
||
})
|
||
|
||
},
|
||
fail: (err) => {
|
||
wxLogin().then(res=>{
|
||
AuthorizedLogin({
|
||
code: code.value,
|
||
encryptedData: encryptedData.value,
|
||
iv: iv.value,
|
||
openId: uni.getStorageSync("openId"),
|
||
Pre_ID: uni.getStorageSync("Pre_ID"),
|
||
mediatorId: uni.getStorageSync('mediatorId')
|
||
})
|
||
})
|
||
}
|
||
})
|
||
|
||
|
||
},
|
||
fail: (err) => {
|
||
uni.hideLoading()
|
||
console.error("获取用户信息失败:", err);
|
||
},
|
||
});
|
||
}
|
||
// 获取服务器返回的token
|
||
|
||
async function wxChartLogin() {
|
||
await wxGetUserProfile();
|
||
}
|
||
const phoneNumber = ref('');
|
||
async function getPhoneNumber(e) {
|
||
uni.showLoading()
|
||
phoneNumber.value = e.detail.code;
|
||
if (e.detail.code) {
|
||
getApi.GetPhoneNumber({code:e.detail.code,userName:state.value.username.trim()}).then(res => {
|
||
if (res.code == 200) {
|
||
uni.hideLoading()
|
||
isToHome()
|
||
}
|
||
})
|
||
} else {
|
||
uni.hideLoading()
|
||
uni.showToast({
|
||
title: '获取手机号失败',
|
||
icon: 'none',
|
||
duration: 2000
|
||
});
|
||
}
|
||
}
|
||
onLoad(() => {
|
||
if (uni.getSystemInfoSync().hostName === "WeChat") {
|
||
wxLogin();
|
||
}
|
||
});
|
||
</script>
|
||
<!-- <style>
|
||
page {
|
||
background: linear-gradient(
|
||
0deg,
|
||
rgb(1, 169, 188) 0%,
|
||
rgb(10, 132, 184) 94.004%
|
||
);
|
||
min-height: 100%;
|
||
}
|
||
</style> -->
|
||
<style scoped lang="scss">
|
||
@import '@/static/style/theme.scss';
|
||
// uni-page-body {
|
||
// background: linear-gradient(
|
||
// 0deg,
|
||
// rgb(1, 169, 188) 0%,
|
||
// rgb(10, 132, 184) 94.004%
|
||
// );
|
||
// min-height: 100%;
|
||
// }
|
||
|
||
@keyframes shake {
|
||
0%, 100% { transform: translateX(0); }
|
||
20%, 60% { transform: translateX(-10px); }
|
||
40%, 80% { transform: translateX(10px); }
|
||
}
|
||
|
||
.shake {
|
||
animation: shake 0.8s ease-in-out;
|
||
}
|
||
|
||
.AgreementTitle{
|
||
font-weight: bold;
|
||
font-size: 32rpx;
|
||
margin-bottom: 12rpx;
|
||
}
|
||
.UserAgreementtips{
|
||
margin-top:20rpx;
|
||
display: flex;
|
||
align-items: center;
|
||
flex-wrap: wrap;
|
||
text{
|
||
color: var(--active-color);
|
||
}
|
||
}
|
||
.login-wrap {
|
||
background: linear-gradient(0deg, var(--left-linear) 0%, var(--right-linear) 94.004%);
|
||
height: 100vh;
|
||
}
|
||
|
||
.container {
|
||
padding: 20rpx;
|
||
font-size: 14px;
|
||
line-height: 24px;
|
||
min-width: 100%;
|
||
|
||
.logoBox {
|
||
margin-top: 6%;
|
||
padding: 40upx;
|
||
display: flex;
|
||
flex-direction: column;
|
||
color: #ffffff;
|
||
font-weight: 900;
|
||
font-size: 34rpx;
|
||
|
||
image {
|
||
margin-top: 20rpx;
|
||
width: 256rpx;
|
||
height: 63rpx;
|
||
}
|
||
}
|
||
|
||
.formBox {
|
||
position: fixed;
|
||
bottom: 0;
|
||
width: 100%;
|
||
left: 0;
|
||
padding: 0 20rpx;
|
||
|
||
.form {
|
||
background: #ffffff;
|
||
padding: 60rpx 20rpx;
|
||
border-radius: 24rpx 24rpx 0 0;
|
||
|
||
.goLogin {
|
||
text-align: center;
|
||
padding: 30rpx 0;
|
||
}
|
||
|
||
.forget-text {
|
||
color: var(--btn-color2);
|
||
}
|
||
|
||
::v-deep .uv-form-item__body {
|
||
margin-bottom: 25rpx;
|
||
background-color: #f6f7f9;
|
||
overflow: visible;
|
||
padding: 10px 16px;
|
||
|
||
.uv-form-item__body__left {
|
||
width: auto;
|
||
min-width: 140rpx;
|
||
flex-wrap: nowrap;
|
||
color: #617986;
|
||
font-weight: 600;
|
||
white-space: nowrap;
|
||
display: flex;
|
||
align-items: center;
|
||
}
|
||
}
|
||
|
||
.btn-wrap {
|
||
::v-deep .uv-button {
|
||
border-color: var(--btn-color5);
|
||
background-color: var(--btn-color5);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
.code {
|
||
width: 140rpx;
|
||
height: 56rpx;
|
||
}
|
||
</style>
|