Commit d4462fc9 authored by youjie's avatar youjie

no message

parent 73fdf525
...@@ -58,7 +58,7 @@ export default { ...@@ -58,7 +58,7 @@ export default {
bindingRecommendCodeRequired: '请输入推荐码', bindingRecommendCodeRequired: '请输入推荐码',
backStep: '上一步', backStep: '上一步',
registerSuccess: '注册成功', registerSuccess: '注册成功',
registerSuccessText: '一起奔赴阳光、沙滩与烟火气吧~', registerSuccessText: '恭喜注册成功!',
agreementText: '「我同意使用条款与隐私政策」', agreementText: '「我同意使用条款与隐私政策」',
forgetPassword: '忘记密码', forgetPassword: '忘记密码',
newPassword: '新密码', newPassword: '新密码',
......
...@@ -11,87 +11,127 @@ ...@@ -11,87 +11,127 @@
<div class="text-[27px] primary6 SourceHanSansCN">{{ t('login.subtitle') }}</div> <div class="text-[27px] primary6 SourceHanSansCN">{{ t('login.subtitle') }}</div>
</div> </div>
</div> --> </div> -->
<div class="w-full flex flex-col loginForm pt-[97px]"> <div class="w-full flex flex-col loginForm pt-[97px]">
<div class="flex justify-center"> <div class="flex justify-center">
<div class="w-[463px] h-[620px] "> <div class="w-[463px] h-[620px] ">
<!-- <div class="loginForm-bg w-full h-[620px] rounded-[18px] absolute top-0 left-0 bottom-0 z-[2]"></div> --> <!-- <div class="loginForm-bg w-full h-[620px] rounded-[18px] absolute top-0 left-0 bottom-0 z-[2]"></div> -->
<!-- absolute top-0 left-0 bottom-0 --> <!-- absolute top-0 left-0 bottom-0 -->
<div class="loginForm-bg w-full h-full rounded-[18px] flex flex-col"> <div class="loginForm-bg w-full h-full rounded-[18px] flex flex-col">
<div class="text-center pt-[46px] primary-6"> <div class="text-center pt-[46px] primary-6">
<div class="text-[32px] font-bold">{{ t('login.loginTo') }}</div> <div class="text-[32px] font-bold">{{ t('login.loginTo') }}</div>
<!-- <div class="text-[17px] mt-[18px]">{{ t('login.loginToSubImm') }}</div> --> <!-- <div class="text-[17px] mt-[18px]">{{ t('login.loginToSubImm') }}</div> -->
<div class="flex justify-center items-center mt-[18px]"> <div class="flex justify-center items-center mt-[18px]">
<img src="@/assets/images/welcome-login.png" alt="" class="h-[26px]"> <img src="@/assets/images/welcome-login.png" alt="" class="h-[26px]">
</div> </div>
</div> </div>
<a-space direction="vertical" class="px-[72px]"> <a-space direction="vertical" class="px-[72px]">
<a-form :model="loginMsg" :rules="rules" @submit="loginHandler" layout="vertical" <a-form :model="loginMsg" :rules="rules" @submit="loginHandler" layout="vertical"
class="mt-[42px]"> class="mt-[42px]">
<a-form-item field="email" :label="t('login.account')"> <a-form-item field="email" :label="t('login.account')">
<a-input class="loginMsg-input" <a-input class="loginMsg-input"
v-model="loginMsg.email" v-model="loginMsg.email"
:placeholder="t('login.accountPlaceholder')" :placeholder="t('login.accountPlaceholder')"
:maxLength="16" size="large"> :maxLength="16" size="large">
<!-- <template #prefix> <!-- <template #prefix>
<icon-user size="16" strokeLinejoin="miter" /> <icon-user size="16" strokeLinejoin="miter" />
</template> --> </template> -->
</a-input> </a-input>
</a-form-item> </a-form-item>
<a-form-item field="password" :label="t('login.password')" class=""> <a-form-item field="password" :label="t('login.password')" class="">
<!-- :invisible-button="true" --> <!-- :invisible-button="true" -->
<!-- :defaultVisibility="true" --> <!-- :defaultVisibility="true" -->
<a-input-password class="loginMsg-input" <a-input-password class="loginMsg-input"
v-model="loginMsg.password" v-model="loginMsg.password"
size="large" size="large"
:placeholder="t('login.passwordPlaceholder')" :placeholder="t('login.passwordPlaceholder')"
:maxLength="50" :maxLength="50"
:invisible-button="false" :invisible-button="false"
> >
<!-- <template #prefix> <!-- <template #prefix>
<icon-key size="16" strokeLinejoin="miter" /> <icon-key size="16" strokeLinejoin="miter" />
</template> --> </template> -->
</a-input-password> </a-input-password>
</a-form-item> </a-form-item>
<div class="mt-[34px] flex flex-row items-center items-center-button"
:class="[loginMsg.password&&loginMsg.password.length>=8&&loginMsg.email?'isClick':'']">
<a-button
type="primary"
size="large"
:loading="loading"
html-type="submit"
class="microsoft-font flex-1 font-bold text-gray-200 !h-[46px] !rounded-[13px] !text-base"
>
{{ t('login.loginButton') }}
</a-button>
</div>
</a-form>
</a-space>
<!-- 谷歌登录 -->
<!-- <div v-else-if="loginMsg.reType === 1" class="login-form-content google-content">
<div class="google-auth-container">
<div
id="g_id_onload"
data-client_id="13534363185-3utcasahjr950mf6uumq8upefl0fu2rl.apps.googleusercontent.com"
data-context="signin"
data-ux_mode="popup"
data-callback="googleCallback"
data-auto_select="false"
data-itp_support="true"
></div>
<div
class="g_id_signin"
data-type="standard"
data-shape="rectangular"
data-theme="outline"
data-text="signin_with"
data-size="large"
data-locale="en-US"
data-logo_alignment="center"
data-width="360"
></div>
</div>
</div> -->
<div class="mt-[34px] flex flex-row items-center items-center-button" <!-- Line登录 -->
:class="[loginMsg.password&&loginMsg.password.length>=8&&loginMsg.email?'isClick':'']"> <!-- <div v-else-if="loginMsg.reType === 3" class="login-form-content scan-content">
<a-button <div class="qr-container">
type="primary" <div class="qr-box line-qr-box">
size="large" <div class="qr-code-placeholder">
:loading="loading" <i class="ki-outline ki-message-text"></i>
html-type="submit" </div>
class="microsoft-font flex-1 font-bold text-gray-200 !h-[46px] !rounded-[13px] !text-base" </div>
> <p class="scan-instruction">{{ t('login.scanTip') }}</p>
{{ t('login.loginButton') }} <p class="scan-status">{{ t('login.scanWaiting') }}</p>
</a-button> </div>
</div> -->
<div class="mt-[40px] flex items-center justify-center">
<a-divider orientation="center" class="text-[16px] text-[#EEEFEB]"></a-divider>
<span class="text-nowrap primary1-3 px-[14px]">{{ t('login.othenLogin') }}</span>
<a-divider orientation="center" class="text-[16px] text-[#EEEFEB]"></a-divider>
</div>
<div class="flex items-center justify-between px-[100px] mt-[20px]">
<!-- loginForm-itemActive loginForm-item-->
<div class="w-[42px] h-[42px]
rounded-full bg-[#FFF]
flex items-center justify-center cursor-pointer loginForm-item"
v-for="(method,index) in loginMethods" :key="index"
@click="toggleLoginType(method.key)">
<img :src="method.url" alt="" class="w-[14px] h-[14px]"/>
</div>
</div> </div>
</a-form>
</a-space>
<div class="mt-[40px] flex items-center justify-center">
<a-divider orientation="center" class="text-[16px] text-[#EEEFEB]"></a-divider>
<span class="text-nowrap primary1-3 px-[14px]">{{ t('login.othenLogin') }}</span>
<a-divider orientation="center" class="text-[16px] text-[#EEEFEB]"></a-divider>
</div>
<div class="flex items-center justify-between px-[100px] mt-[20px]">
<!-- loginForm-itemActive loginForm-item-->
<div class="w-[42px] h-[42px]
rounded-full bg-[#FFF]
flex items-center justify-center cursor-pointer loginForm-item"
v-for="(item,index) in loginTypes" :key="index"
@click="toggleLoginType(item.value)">
<img :src="item.url" alt="" class="w-[14px] h-[14px]"/>
</div> </div>
</div> </div>
</div> </div>
</div>
</div>
<div class="flex justify-center text-white pb-[117px] text-[16px]"> <div class="flex justify-center text-white pb-[117px] text-[16px]">
<div class="px-[17px] py-[30px] cursor-pointer" @click="handleClick('/forgePassword')">{{ t('login.forgotPassword') }}</div> <div class="px-[17px] py-[30px] cursor-pointer" @click="handleClick('/forgePassword')">{{ t('login.forgotPassword') }}</div>
<div class="px-[17px] py-[30px] cursor-pointer" @click="handleClick('/register')">{{ t('login.registerNow') }}</div> <div class="px-[17px] py-[30px] cursor-pointer" @click="handleClick('/register')">{{ t('login.registerNow') }}</div>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</template> </template>
...@@ -104,7 +144,6 @@ import { useSystemConfigStore } from '@/stores/index' ...@@ -104,7 +144,6 @@ import { useSystemConfigStore } from '@/stores/index'
import ErpUserService from '@/services/ErpUserService' import ErpUserService from '@/services/ErpUserService'
import { ApiResult } from '@/types/ApiResult' import { ApiResult } from '@/types/ApiResult'
import loginHeader from "./components/header.vue"; import loginHeader from "./components/header.vue";
import loginForm from "./components/loginForm.vue";
import f from '@/assets/images/login_f.png' import f from '@/assets/images/login_f.png'
import G from '@/assets/images/login_G.png' import G from '@/assets/images/login_G.png'
import tel from '@/assets/images/login_tel.png' import tel from '@/assets/images/login_tel.png'
...@@ -132,25 +171,29 @@ const loginMsg = reactive({ ...@@ -132,25 +171,29 @@ const loginMsg = reactive({
password: '',//123456 password: '',//123456
}) })
const loginTypes = ref([ const loginMethods = ref([
// { key: 'account', label: 'login.accountLogin', icon: 'ki-user' },
// { key: 'wechat', label: 'login.wechatLogin', icon: 'ki-whatsapp' },
// { key: 'google', label: 'login.googleLogin', icon: 'ki-google' },
// { key: 'line', label: 'login.lineLogin', icon: 'ki-message-text' },
{ {
label: 'f', label: 'f',
value: 7, key: 7,
url: f, url: f,
}, },
{ {
label: 'G', label: 'G',
value: 1, key: 1,
url: G, url: G,
}, },
{ {
label: 'tel', label: 'tel',
value: 0, key: 0,
url: tel, url: tel,
}, },
{ {
label: 'inline', label: 'inline',
value: 3, key: 3,
url: line, url: line,
}, },
]) ])
......
<template>
<div class="w-full flex flex-col loginForm">
<div class="flex justify-center">
<div class="w-[463px] h-[635px] ">
<!-- <div class="loginForm-bg w-full h-[620px] rounded-[18px] absolute top-0 left-0 bottom-0 z-[2]"></div> -->
<!-- absolute top-0 left-0 bottom-0 -->
<div class="loginForm-bg w-full h-full rounded-[18px] flex flex-col">
<a-space direction="vertical" class="px-[72px]">
<a-form :model="loginMsg" :rules="rules" @submit="loginHandler" layout="vertical"
class="mt-[42px]">
<a-form-item field="" :label="t('login.bindingName')">
<a-input class="loginMsg-input"
v-model="loginMsg.account"
:placeholder="t('login.bindingNameRequired')"
:maxLength="16" size="large">
<!-- <template #prefix>
<icon-user size="16" strokeLinejoin="miter" />
</template> -->
</a-input>
</a-form-item>
<a-form-item field="" :label="t('login.bindingPhone')" class="">
<a-input class="loginMsg-input"
v-model="loginMsg.account"
:placeholder="t('login.bindingPhoneRequired')"
:maxLength="16" size="large">
<template #prefix>
<a-dropdown position="br" trigger.stop="click">
<div class="flex items-center">
<span class="text-[16px] font-bold mr-[22px]">+86</span>
<icon-down size="16" strokeLinejoin="miter" />
</div>
<template #content>
<a-doption
v-for="option in AreaCodeList"
:key="option.value"
@click.stop="handleAreaCodeChange(option.value)"
:class="{ 'bg-blue-50': currentAreaCode === option.value }"
>
<div class="flex items-center space-x-3 px-2 py-1">
<!-- <span class="text-lg">{{ option.flag }}</span> -->
<span class="font-medium">{{ option.label }}</span>
</div>
</a-doption>
</template>
</a-dropdown>
</template>
</a-input>
</a-form-item>
<a-form-item field="" :label="t('login.bindingWechat')" class="">
<a-input class="loginMsg-input"
v-model="loginMsg.password"
size="large"
:placeholder="t('login.bindingWechatRequired')"
:defaultVisibility="false"
:maxLength="50"
:invisible-button="false"
>
<!-- <template #prefix>
<icon-key size="16" strokeLinejoin="miter" />
</template> -->
</a-input>
</a-form-item>
<a-form-item field="" :label="t('login.bindingRecommendCode')" class="">
<a-input class="loginMsg-input"
v-model="loginMsg.password"
size="large"
:placeholder="t('login.bindingRecommendCodeRequired')"
:defaultVisibility="false"
:maxLength="50"
:invisible-button="false"
>
<!-- <template #prefix>
<icon-key size="16" strokeLinejoin="miter" />
</template> -->
</a-input>
</a-form-item>
<div class="flex items-center justify-center mb-[15px] cursor-pointer"
@click="search.msg.privacy = !search.msg.privacy">
<div class="mr-[12px]">
<div v-if="!search.msg.privacy" class="w-[17px] h-[17px] rounded-[6px] bg-[#008AFF] border-[1px] border-[#C0CEB3] bg-white"></div>
<img v-else src="@/assets/images/login-agr.png" alt="" class="w-[17px] h-[17px]">
</div>
<span class="text-[#008AFF] text-sm">{{ t('login.agreementText') }}</span>
</div>
<div class="mt-[10px] flex flex-row items-center">
<a-button
type="primary"
size="large"
:loading="loading"
html-type="submit"
class="microsoft-font flex-1 font-medium !h-[46px] !rounded-[13px] !text-[16px]"
@click="loginHandler"
>
{{ t('login.register') }}
</a-button>
</div>
<div class="flex flex-row items-center">
<a-button
type="text"
size="large"
html-type="submit"
class="microsoft-font flex-1 font-medium !h-[46px] !rounded-[13px] !text-[#0C150D] !text-[16px]"
@click="backStep"
>
{{ t('login.backStep') }}
</a-button>
</div>
</a-form>
</a-space>
</div>
</div>
</div>
<div class="flex justify-center text-white pb-[117px] text-[16px]">
<div class="px-[17px] py-[30px] cursor-pointer" @click="handleClick('/login')">{{ t('login.backToHome') }}</div>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, reactive, type Ref, computed,inject } from 'vue'
import { useUserStore } from '@/stores/index'
import UserServices from '@/services/ErpUserService'
import { ApiResult } from '@/types/ApiResult'
import { onMounted } from 'vue'
import VueHcaptcha from '@hcaptcha/vue3-hcaptcha'
import { useRouter } from 'vue-router'
import { useI18n } from 'vue-i18n'
import f from '@/assets/images/login_f.png'
import G from '@/assets/images/login_G.png'
import tel from '@/assets/images/login_tel.png'
import line from '@/assets/images/login_line.png'
const { t } = useI18n();
const userStore = useUserStore()
const loading = ref(false)
const currentTenantId = ref(0)
const needVerify = ref(false)
const validateToken = ref('')
const router = useRouter()
const invisibleHcaptcha = ref()
const loginMsg = reactive({
account: '17308037817',
password: '123456',
tenantId: null as string | null,
read: true,
})
const search = inject('search') as any;
// console.log(search.msg,'---------')
const AreaCodeList = ref([
{
value: '+86',
label: '中国',
},
{
value: '+1',
label: '美国',
},
])
// 验证规则调整
const rules = computed(() => ({
account: [
{
required: true,
message: t('login.accountRequired'),
},
],
password: [
{
required: true,
message: t('login.passwordRequired'),
},
],
}))
const currentAreaCode = ref('+86')
const verifyHandler = (token: string, ekey: string) => {
validateToken.value = token
}
const verifyCheckHandler = async () => {
const response = await UserServices.NeedVerifyStatusAsync()
if (response.data.resultCode == ApiResult.SUCCESS) {
needVerify.value = response.data.data == 1
} else needVerify.value = true
}
const handleAreaCodeChange = (value: string) => {
loginMsg.account = value + loginMsg.account.slice(2)
}
const handleClick = (path: string) => {
router.push(path)
}
const backStep = () => {
search.loginType = 2
}
const loginHandler = async ({ values, errors }: any) => {
if (errors || loading.value) return
loading.value = true
const result = await userStore.setUserPasswordLoginAsync(
loginMsg.account,
loginMsg.password,
validateToken.value,
loginMsg.tenantId?.toString() || '',
)
loading.value = false
currentTenantId.value = 0
if (result.status == 'SUCCESS') {
const forward = localStorage.getItem('forward')
localStorage.removeItem('forward')
router.push({
path: forward ? forward : '/',
})
} else {
if (!needVerify.value) needVerify.value = result.verify
if (invisibleHcaptcha.value && needVerify.value) invisibleHcaptcha.value.reset()
}
}
const initGoogleLogin = () => {
const cb = (data:any) => {
loading.value = true;
//that.onGoogleCredential(data.credential);
userStore.setUserGoogleLoginAsync(data.credential)
};
window.googleCallback = cb;
const s = document.createElement('script');
s.src = 'https://accounts.google.com/gsi/client';
document.body.appendChild(s);
}
onMounted(async () => {
//await verifyCheckHandler()
//initGoogleLogin()
})
</script>
<style scoped lang="scss">
:deep(.arco-form-item-content){
min-height: 46px;
border-radius: 12px;
}
:deep(.arco-input-wrapper){
height: 46px;
border-radius: 12px !important;
background-color: #EEEFEB;
}
:deep(.arco-input-focus){
border-radius: 12px;
box-shadow: rgba(60,85,62,0.18) 0px 5px 15px;
border: 2px solid var(--primary-2);
background-color: #FFFFFF;
}
:deep(.arco-form-item-label){
font-size: 16px;
padding-left: 17px;
}
:deep(.arco-input-wrapper .arco-input.arco-input-size-large){
font-size: 16px;
}
:deep(.arco-btn-primary){
box-shadow: rgba(74,102,77,0.15) 0px 5px 15px;
}
:deep(.arco-divider-text){
/*background: #fff;*/
font-size: 16px;
color: #C2C3C0;
font-weight: 400;
}
:deep(.arco-divider-horizontal){
width: 93px;
min-width: 93px;
border-bottom: 1px solid #EEF0E8;
}
:deep(.arco-input){
text-indent: 4px;
}
:deep(.arco-btn-primary){
background-color: var(--primary1-7);
}
:deep(.arco-btn-primary:hover){
background-color: var(--primary1-5);
}
:deep(.arco-btn-text:hover){
background-color: rgba(0, 0, 0, 0.0);
}
:deep(.arco-divider-vertical){
border-left: 1px solid var(--primary1-4);
}
.loginForm-bg{
background: url('../../../assets/images/login_formbg.png')no-repeat white;
background-size: 100% 100%;
}
.loginForm-item{
border: 1px solid var(--primary1-4);
}
.loginForm-itemActive{
border: 2px solid var(--primary1-1);
}
</style>
<template>
<div class="w-full flex flex-col loginForm pt-[97px]">
<div class="flex justify-center">
<div class="w-[463px] h-[620px] ">
<!-- <div class="loginForm-bg w-full h-[620px] rounded-[18px] absolute top-0 left-0 bottom-0 z-[2]"></div> -->
<!-- absolute top-0 left-0 bottom-0 -->
<div class="loginForm-bg w-full h-full rounded-[18px] flex flex-col">
<div class="text-center pt-[46px] primary-6">
<div class="text-[32px] font-bold">{{ t('login.loginTo') }}</div>
<!-- <div class="text-[17px] mt-[18px]">{{ t('login.loginToSubImm') }}</div> -->
<div class="flex justify-center items-center mt-[18px]">
<img src="@/assets/images/welcome-login.png" alt="" class="h-[26px]">
</div>
</div>
<a-space direction="vertical" class="px-[72px]">
<a-form :model="loginMsg" :rules="rules" @submit="loginHandler" layout="vertical"
class="mt-[42px]">
<a-form-item field="email" :label="loginMsg.reType==0?t('login.account'):t('login.emailLogin')">
<a-input class="loginMsg-input"
v-model="loginMsg.email"
:placeholder="loginMsg.reType==0?t('login.accountPlaceholder'):t('login.emailPasswordPlaceholder')"
:maxLength="16" size="large">
<!-- <template #prefix>
<icon-user size="16" strokeLinejoin="miter" />
</template> -->
</a-input>
</a-form-item>
<a-form-item field="password" :label="t('login.password')" class="">
<!-- :invisible-button="true" -->
<!-- :defaultVisibility="true" -->
<a-input-password class="loginMsg-input"
v-model="loginMsg.password"
size="large"
:placeholder="t('login.passwordPlaceholder')"
:maxLength="50"
:invisible-button="false"
>
<!-- <template #prefix>
<icon-key size="16" strokeLinejoin="miter" />
</template> -->
</a-input-password>
</a-form-item>
<div class="mt-[34px] flex flex-row items-center">
<a-button
type="primary"
size="large"
:loading="loading"
html-type="submit"
class="microsoft-font flex-1 font-bold text-gray-200 !h-[46px] !rounded-[13px] !text-base"
>
{{ t('login.loginButton') }}
</a-button>
</div>
</a-form>
</a-space>
<div class="mt-[40px] flex items-center justify-center">
<a-divider orientation="center" class="text-[16px] text-[#EEEFEB]"></a-divider>
<span class="text-nowrap primary1-3 px-[14px]">{{ t('login.othenLogin') }}</span>
<a-divider orientation="center" class="text-[16px] text-[#EEEFEB]"></a-divider>
</div>
<div class="flex items-center justify-between px-[100px] mt-[20px]">
<!-- loginForm-itemActive loginForm-item-->
<div class="w-[42px] h-[42px]
rounded-full bg-[#FFF]
flex items-center justify-center cursor-pointer loginForm-item"
v-for="(item,index) in loginTypes" :key="index"
@click="toggleLoginType(item.value)">
<img :src="item.url" alt="" class="w-[14px] h-[14px]"/>
</div>
</div>
</div>
</div>
</div>
<div class="flex justify-center text-white pb-[117px] text-[16px]">
<div class="px-[17px] py-[30px] cursor-pointer" @click="handleClick('/forgePassword')">{{ t('login.forgotPassword') }}</div>
<div class="px-[17px] py-[30px] cursor-pointer" @click="handleClick('/register')">{{ t('login.registerNow') }}</div>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, reactive, computed,inject } from 'vue'
import { useUserStore } from '@/stores/index'
import UserServices from '@/services/ErpUserService'
import ErpUserService from '@/services/ErpUserService'
import { ApiResult } from '@/types/ApiResult'
import { onMounted } from 'vue'
import VueHcaptcha from '@hcaptcha/vue3-hcaptcha'
import { useRouter } from 'vue-router'
import { useI18n } from 'vue-i18n'
import { useSystemConfigStore } from '@/stores/index'
import f from '@/assets/images/login_f.png'
import G from '@/assets/images/login_G.png'
import tel from '@/assets/images/login_tel.png'
import line from '@/assets/images/login_line.png'
const { t } = useI18n();
const userStore = useUserStore()
const systemConfigStore = useSystemConfigStore()
const loading = ref(false)
const currentTenantId = ref(0)
const needVerify = ref(false)
const validateToken = ref('')
const router = useRouter()
const invisibleHcaptcha = ref()
const loginMsg = reactive({
tenantId: systemConfigStore.tenantId || null,
reType: 0,//登录方式 0账号密码 1谷歌授权 3LINE授权 7FaceBook授权
openId: "",
email: "2310721242@qq.com",
password: '123456',
})
const loginTypes = ref([
{
label: 'f',
value: 7,
url: f,
},
{
label: 'G',
value: 1,
url: G,
},
{
label: 'tel',
value: 0,
url: tel,
},
{
label: 'inline',
value: 3,
url: line,
},
])
// const search = inject('search') as any;
// 验证规则调整
const rules = computed(() => ({
email: [
{
required: true,
message: t('login.accountRequired'),
},
],
password: [
{
required: true,
message: t('login.passwordRequired'),
},
],
}))
const verifyHandler = (token: string, ekey: string) => {
validateToken.value = token
}
const verifyCheckHandler = async () => {
const response = await ErpUserService.NeedVerifyStatusAsync()
if (response.data.resultCode == ApiResult.SUCCESS) {
needVerify.value = response.data.data == 1
} else needVerify.value = true
}
const handleClick = (path: string) => {
router.push(path)
}
const toggleLoginType = (type: number) => {
loginMsg.reType = type
if (type == 1) {
initGoogleLogin()
}
}
const loginHandler = async ({ values, errors }: any) => {
if (errors || loading.value) return
loading.value = true
try {
const result = await userStore.setUserPasswordLoginAsync(
loginMsg.email,
loginMsg.password,
loginMsg.tenantId?.toString() || '',
loginMsg.reType,
loginMsg.openId?.toString() || '',
)
loading.value = false
currentTenantId.value = 0
if (result.status == 'SUCCESS') {
const forward = localStorage.getItem('forward')
localStorage.removeItem('forward')
router.push({
path: forward ? forward : '/',
})
} else {
if (!needVerify.value) needVerify.value = result.verify
if (invisibleHcaptcha.value && needVerify.value) invisibleHcaptcha.value.reset()
}
} finally {
loading.value = false
}
}
// 初始化谷歌登录
const initGoogleLogin = () => {
const cb = async (data: any) => {
loading.value = true
try {
const result = await userStore.setUserGoogleLoginAsync(data.credential)
if (result.status === 'SUCCESS') {
// 登录成功,跳转到Dashboard
const forward = localStorage.getItem('forward')
localStorage.removeItem('forward')
router.push({
path: forward || '/dashboard',
})
}
} finally {
loading.value = false
}
}
;(window as any).googleCallback = cb
const script = document.createElement('script')
script.src = 'https://accounts.google.com/gsi/client'
document.body.appendChild(script)
}
// 检查是否需要验证
const checkNeedVerify = async () => {
try {
const response = await ErpUserService.NeedVerifyStatusAsync()
if (response.data.resultCode === ApiResult.SUCCESS) {
needVerify.value = response.data.data === 1
} else {
needVerify.value = true
}
} catch (error) {
needVerify.value = true
}
}
// 初始化
const init = async () => {
// 如果已经登录且是正常代理商,直接跳转到仪表盘
if (userStore.getUserToken && userStore.getUser) {
const userStatus = userStore.getUser.status
const disableStatus = userStore.getUser.disableStatus
// 只有状态正常的代理商才跳转到仪表盘
// 申请中(PendingReview)和被拒绝(Rejected)状态不跳转,允许继续登录
if (userStatus !== 'PendingReview' && userStatus !== 'Rejected' && disableStatus !== 'Disable') {
router.replace('/dashboard')
return
}
}
await checkNeedVerify()
initGoogleLogin()
}
init()
</script>
<style scoped lang="scss">
:deep(.arco-form-item-content){
min-height: 46px;
border-radius: 12px;
}
:deep(.arco-input-wrapper){
height: 46px;
border-radius: 12px !important;
background-color: #EEEFEB;
}
:deep(.arco-input-focus){
border-radius: 12px;
box-shadow: rgba(60,85,62,0.18) 0px 5px 15px;
border: 2px solid #8AAA8A;
background-color: #FFFFFF;
}
:deep(.arco-form-item-label){
font-size: 16px;
padding-left: 17px;
}
:deep(.arco-input-wrapper .arco-input.arco-input-size-large){
font-size: 16px;
}
:deep(.arco-btn-primary){
box-shadow: rgba(74,102,77,0.15) 0px 5px 15px;
}
:deep(.arco-divider-text){
/*background: #fff;*/
font-size: 16px;
color: #C2C3C0;
font-weight: 400;
}
:deep(.arco-divider-horizontal){
width: 93px;
min-width: 93px;
border-bottom: 1px solid #EEF0E8;
}
:deep(.arco-input){
text-indent: 4px;
}
:deep(.arco-form-item-label-required-symbol){
display: none;
}
:deep(.arco-form-item-message){
color: rgba(255,0,0,0);
}
.loginForm-bg{
background: url('../../../assets/images/login_formbg.png')no-repeat white;
background-size: 100% 100%;
}
/*.loginForm-bg:deep(.arco-btn-primary){
background-color: var(--primary1-6) !important;
}*/
.loginForm-item{
border: 1px solid var(--primary1-3);
}
.loginForm-item:hover{
border: 2px solid var(--primary1-5);
}
.loginForm-itemActive{
border: 2px solid var(--primary1-5);
}
</style>
<template>
<div class="w-full flex flex-col loginForm">
<div class="flex justify-center">
<div class="w-[463px] h-[580px] ">
<!-- <div class="loginForm-bg w-full h-[620px] rounded-[18px] absolute top-0 left-0 bottom-0 z-[2]"></div> -->
<!-- absolute top-0 left-0 bottom-0 -->
<div class="loginForm-bg w-full h-full rounded-[18px] flex flex-col">
<a-space direction="vertical" class="px-[72px]">
<a-form :model="formData" :rules="rules" @submit="handleSendCode" layout="vertical"
class="mt-[42px]">
<!-- <div class="flex items-end">
<div class="flex-1">
<a-form-item field="emailNum" :label="t('login.email')">
<a-input class="formData-input"
v-model="formData.emailNum"
placeholder=""
:maxLength="16" size="large">
</a-input>
</a-form-item>
</div>
<a-form-item class="text-[16px] text-1 px-[6px] font-medium !w-[31px]">
<div>@</div>
</a-form-item>
<a-form-item field="emailSuffix" label="" class="!w-[107px]">
<a-input class="formData-input"
v-model="formData.emailSuffix"
placeholder=""
:maxLength="16" size="large">
</a-input>
</a-form-item>
</div> -->
<a-form-item field="email" :label="t('login.email')">
<a-input class="formData-input"
v-model="formData.email"
placeholder=""
:maxLength="16" size="large">
</a-input>
</a-form-item>
<a-form-item field="verificationCode" :label="t('login.verifyCode')" class="">
<a-input class="formData-input"
v-model="formData.verificationCode"
size="large"
:placeholder="t('login.verifyCodeRequired')"
:defaultVisibility="false"
:maxLength="50"
:invisible-button="false"
@input="handleCodeInput"
>
<template #suffix>
<a-divider direction="vertical" class="text-[16px] text-[#EEEFEB]"></a-divider>
<div class="cursor-pointer primary1 font-medium text-[16px]">
<!-- <div v-if="search.countdown > 0">
{{ t('login.resendCode', { seconds: search.countdown }) }}
</div>
<div v-else @click="handleSendCode">
{{ t('login.getVerifyCode') }}
</div> -->
<icon-check-circle-fill v-if="codeValidateStatus === 'success'" class="text-green-700" />
<icon-close-circle-fill v-else-if="codeValidateStatus === 'error'" class="text-red-500" />
<a-button @click="handleSendCode" :disabled="!canSendCode || sendCodeCountdown > 0"
:loading="sendingCode" size="large" type="text" class="w-full">
<span class="primary1-5">
{{ sendCodeCountdown > 0 ? `${sendCodeCountdown}s ${t('register.resend')}` :
t('login.getVerifyCode') }}</span>
</a-button>
</div>
</template>
</a-input>
</a-form-item>
<a-form-item field="password" :label="t('login.setPassword')" class="">
<a-input-password class="formData-input"
v-model="formData.password"
size="large"
:placeholder="t('login.setPasswordRequired')"
:defaultVisibility="false"
:maxLength="50"
:invisible-button="false"
>
<!-- <template #prefix>
<icon-key size="16" strokeLinejoin="miter" />
</template> -->
</a-input-password>
</a-form-item>
<a-form-item field="confirmPassword" :label="t('login.repeatedpassword')" class="">
<a-input-password class="formData-input"
v-model="formData.confirmPassword"
size="large"
:placeholder="t('login.confirmPasswordRequired')"
:defaultVisibility="false"
:maxLength="50"
:invisible-button="false"
>
</a-input-password>
</a-form-item>
<div class="mt-[27px] flex flex-row items-center">
<a-button
type="primary"
size="large"
:loading="loading"
html-type="submit"
class="microsoft-font flex-1 font-bold text-gray-200 !h-[46px] !rounded-[13px] !text-base"
@click="loginHandler"
>
{{ t('login.nextStep') }}
</a-button>
</div>
</a-form>
</a-space>
</div>
</div>
</div>
<div class="flex justify-center text-white pb-[117px] text-[16px]">
<div class="px-[17px] py-[30px] cursor-pointer" @click="handleClick('/login')">{{ t('login.backToHome') }}</div>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, reactive, type Ref, computed,inject } from 'vue'
import { useUserStore } from '@/stores/index'
import ErpUserService from '@/services/ErpUserService'
import { ApiResult } from '@/types/ApiResult'
import { onMounted } from 'vue'
import VueHcaptcha from '@hcaptcha/vue3-hcaptcha'
import { useRouter } from 'vue-router'
import { useI18n } from 'vue-i18n'
import { Message } from '@arco-design/web-vue'
import UserService from '@/services/UserService'
const { t } = useI18n();
const userStore = useUserStore()
const loading = ref(false)
const currentTenantId = ref(0)
const needVerify = ref(false)
const validateToken = ref('')
const router = useRouter()
const invisibleHcaptcha = ref()
const search = inject('search') as any;
const formData = reactive(search.msg)
// formData = search.msg
// 邮箱验证状态
const emailChecking = ref(false)
const emailValidateStatus = ref<'' | 'success' | 'error' | 'warning'>('')
const emailValidateMessage = ref('')
// 验证码相关状态
const sendingCode = ref(false)
const sendCodeCountdown = ref(0)
const codeValidateStatus = ref<'' | 'success' | 'error'>('')
const codeValidateMessage = ref('')
const emailVerified = ref(false)
const verificationToken = ref('')
const lastVerifiedEmail = ref('')
console.log(search.msg,'---------')
// 验证规则调整
const rules = computed(() => ({
email: [
{ required: true, message: t('login.emailRequired') },
{ type: 'email', message: t('login.emailInvalid') }
],
emailNum: [
{
required: true,
message: t('login.emailFormat'),
},
],
emailSuffix: [
{
required: true,
message: t('login.emailFormat'),
},
],
verificationCode: [
{
required: true,
message: t('login.verifyCodeRequired'),
},
],
password: [
{
required: true,
message: t('login.passwordRequired'),
},
{
validator: (value: any, cb: any) => {
if (!/^(?=.*[A-Za-z])(?=.*\d).{8,}$/.test(value)) {
cb(t('login.passwordFormat'))
}else if (value !== formData.confirmPassword) {
cb(t('login.passwordMismatch'))
} else {
cb()
}
}
}
],
confirmPassword: [
{ required: true, message: t('login.confirmPasswordRequired') },
{
validator: (value: any, cb: any) => {
if (!/^(?=.*[A-Za-z])(?=.*\d).{8,}$/.test(value)) {
cb(t('login.passwordFormat'))
}else if (value !== formData.password) {
cb(t('login.passwordMismatch'))
} else {
cb()
}
}
}
],
}))
const verifyHandler = (token: string, ekey: string) => {
validateToken.value = token
}
const verifyCheckHandler = async () => {
const response = await ErpUserService.NeedVerifyStatusAsync()
if (response.data.resultCode == ApiResult.SUCCESS) {
needVerify.value = response.data.data == 1
} else needVerify.value = true
}
const handleClick = (path: string) => {
router.push(path)
}
// 判断是否可以发送验证码
const canSendCode = computed(() => {
return formData.email && emailValidateStatus.value === 'success'
})
// 邮箱验证延时器
let emailCheckTimer: number | null = null
// 处理邮箱失焦验证
const handleEmailBlur = async () => {
const email = formData.email.trim()
// 清除之前的定时器
if (emailCheckTimer) {
clearTimeout(emailCheckTimer)
}
// 如果邮箱为空或格式不正确,不验证
if (!email) {
emailValidateStatus.value = ''
emailValidateMessage.value = ''
return
}
// 简单的邮箱格式验证
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/
if (!emailRegex.test(email)) {
emailValidateStatus.value = 'error'
emailValidateMessage.value = t('register.emailInvalid')
return
}
// 延迟 500ms 验证,避免频繁请求
emailCheckTimer = setTimeout(async () => {
await validateEmail(email)
}, 500)
}
// 验证邮箱是否可用
const validateEmail = async (email: string) => {
emailChecking.value = true
emailValidateStatus.value = ''
emailValidateMessage.value = ''
try {
const result = await UserService.validateEmailAsync(email, formData.tenantId || 'default')
if (result.isValid) {
// 邮箱可用
emailValidateStatus.value = 'success'
emailValidateMessage.value = t('emailValidation.available')
} else if (result.isExist) {
// 邮箱已存在
emailValidateStatus.value = 'error'
emailValidateMessage.value = t('emailValidation.exists')
} else if (!result.isValidFormat) {
// 格式不正确
emailValidateStatus.value = 'error'
emailValidateMessage.value = t('emailValidation.invalidFormat')
} else {
// 其他错误
emailValidateStatus.value = 'error'
emailValidateMessage.value = result.message || t('emailValidation.checkFailed')
}
} catch (error: any) {
console.error('邮箱验证失败:', error)
emailValidateStatus.value = 'warning'
emailValidateMessage.value = t('emailValidation.checkFailed')
} finally {
emailChecking.value = false
}
}
// 处理邮箱输入变化
const handleEmailChange = () => {
// 如果邮箱已验证,且当前邮箱与已验证邮箱不同,则重置验证状态
if (emailVerified.value && formData.email !== lastVerifiedEmail.value) {
emailVerified.value = false
verificationToken.value = ''
codeValidateStatus.value = ''
codeValidateMessage.value = ''
formData.verificationCode = ''
Message.warning(t('register.emailChangedNeedVerify'))
}
}
// 发送验证码
const handleSendCode = async () => {
if (!formData.email || emailValidateStatus.value !== 'success') {
Message.warning(t('register.pleaseInputValidEmail'))
return
}
sendingCode.value = true
try {
const result = await UserService.sendVerificationCodeAsync(formData.email, formData.tenantId || 'default')
if (result.success) {
Message.success(result.message || t('register.codeSent'))
// 开始倒计时
sendCodeCountdown.value = result.expiresIn || 60
const timer = setInterval(() => {
sendCodeCountdown.value--
if (sendCodeCountdown.value <= 0) {
clearInterval(timer)
}
}, 1000)
} else {
Message.error(result.message || t('register.codeSendFailed'))
}
} catch (error: any) {
console.error('发送验证码失败:', error)
Message.error(error.message || t('register.codeSendFailed'))
} finally {
sendingCode.value = false
}
}
// 处理验证码输入
const handleCodeInput = () => {
// 重置验证状态
codeValidateStatus.value = ''
codeValidateMessage.value = ''
// 当输入6位时自动验证
if (formData.verificationCode.length === 6) {
verifyCode()
}
}
// 验证验证码(在用户输入完6位后自动验证)
const verifyCode = async () => {
if (!formData.verificationCode || formData.verificationCode.length !== 6) {
return
}
try {
const result = await UserService.verifyEmailCodeAsync(
formData.email,
formData.verificationCode,
formData.tenantId || 'default'
)
if (result.isValid) {
codeValidateStatus.value = 'success'
codeValidateMessage.value = t('register.codeVerified')
emailVerified.value = true
verificationToken.value = result.verificationToken || ''
lastVerifiedEmail.value = formData.email
Message.success(t('register.codeVerified'))
} else {
codeValidateStatus.value = 'error'
codeValidateMessage.value = result.message || t('register.codeInvalid')
emailVerified.value = false
verificationToken.value = ''
}
} catch (error: any) {
console.error('验证码验证失败:', error)
codeValidateStatus.value = 'error'
codeValidateMessage.value = error.message || t('register.codeVerifyFailed')
emailVerified.value = false
verificationToken.value = ''
}
}
// 处理 OTA 授权验证
const handleOtaAuth = async (token: string) => {
const loading = Message.loading(t('otaAuth.verifying'))
try {
const result = await OtaAuthService.validateOtaToken(token)
if (result.success) {
// 保存临时令牌和用户信息到 sessionStorage
sessionStorage.setItem('tempToken', result.temporaryAccessToken)
sessionStorage.setItem('otaUserInfo', JSON.stringify(result.userInfo))
// 根据建议路由跳转
const suggestedRoute = result.distributorStatus.suggestedRoute
router.replace({ path: suggestedRoute })
} else {
Message.error(t('otaAuth.authFailed'))
router.replace('/login')
}
} catch (error: any) {
console.error('OTA 授权验证失败:', error)
Message.error(error.message || t('otaAuth.authFailed'))
router.replace('/login')
} finally {
loading.close()
}
}
const handleFileUpload = (options: any) => {
const { fileItem, onSuccess, onError } = options
// TODO: 实现真实的文件上传到服务器
// 现在先模拟上传成功
setTimeout(() => {
formData.documentFilePath = `/uploads/${fileItem.file.name}`
onSuccess()
Message.success(t('register.uploadSuccess'))
}, 1000)
}
const handlePrevious = () => {
currentStep.value--
}
const handleNext = () => {
// 简单验证必填字段
if (currentStep.value === 1) {
// 未登录用户需要验证邮箱和密码
if (!isLoggedIn.value && !isFromOta.value) {
if (!formData.email || !formData.password || !formData.confirmPassword) {
Message.warning(t('register.pleaseComplete'))
return
}
if (formData.password !== formData.confirmPassword) {
Message.warning(t('register.passwordMismatch'))
return
}
// 检查邮箱是否已验证
if (!emailVerified.value) {
Message.warning(t('register.pleaseVerifyEmail'))
return
}
// 检查验证码
if (!formData.verificationCode || codeValidateStatus.value !== 'success') {
Message.warning(t('register.pleaseVerifyCode'))
return
}
}
} else if (currentStep.value === 2) {
if (!formData.companyOrBrandName || !formData.contactPerson || !formData.contactPhone || !formData.documentFilePath) {
Message.warning(t('register.pleaseComplete'))
return
}
}
currentStep.value++
}
</script>
<style scoped lang="scss">
:deep(.arco-form-item-content){
min-height: 46px;
border-radius: 12px;
}
:deep(.arco-input-wrapper){
height: 46px;
border-radius: 12px !important;
background-color: #EEEFEB;
}
:deep(.arco-input-focus){
border-radius: 12px;
box-shadow: rgba(60,85,62,0.18) 0px 5px 15px;
border: 2px solid var(--primary-2);
background-color: #FFFFFF;
}
:deep(.arco-form-item-label){
font-size: 16px;
padding-left: 17px;
}
:deep(.arco-input-wrapper .arco-input.arco-input-size-large){
font-size: 16px;
}
:deep(.arco-btn-primary){
box-shadow: rgba(74,102,77,0.15) 0px 5px 15px;
}
:deep(.arco-divider-text){
/*background: #fff;*/
font-size: 16px;
color: #C2C3C0;
font-weight: 400;
}
:deep(.arco-divider-horizontal){
width: 93px;
min-width: 93px;
border-bottom: 1px solid #EEF0E8;
}
:deep(.arco-input){
text-indent: 4px;
}
.loginForm-bg{
background: url('../../../assets/images/login_formbg.png')no-repeat white;
background-size: 100% 100%;
}
:deep(.arco-btn-primary){
background-color: var(--primary1-7);
}
:deep(.arco-btn-primary:hover){
background-color: var(--primary1-5);
}
:deep(.arco-form-item-label-required-symbol){
display: none;
}
:deep(.arco-form-item-message){
// color: rgba(255,0,0,0);
}
:deep(.arco-divider-vertical){
border-left: 1px solid var(--primary1-4);
}
</style>
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment