SFH5/pages/login/index.vue
2026-03-16 11:10:28 +08:00

643 lines
23 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

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" :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>