|
|
|
@ -1,85 +1,3 @@ |
|
|
|
<template> |
|
|
|
<section class="login-page"> |
|
|
|
<div class="login-visual"> |
|
|
|
<div class="login-visual-map"></div> |
|
|
|
<div class="login-visual-shade"></div> |
|
|
|
<div class="login-brand"> |
|
|
|
<div class="brand-mark"><span></span><span></span><span></span></div> |
|
|
|
<div><strong>嘉谷</strong><small>低空智控平台</small></div> |
|
|
|
</div> |
|
|
|
<div class="login-visual-copy"> |
|
|
|
<span>LOW-ALTITUDE INTELLIGENCE</span> |
|
|
|
<h1>让每一次飞行<br />都可控、可追溯</h1> |
|
|
|
<p>统一管理机巢、无人机、任务与视频资产</p> |
|
|
|
</div> |
|
|
|
<div class="login-visual-footer"><span>嘉谷低空智控平台</span><span>© 2026 嘉谷科技</span></div> |
|
|
|
</div> |
|
|
|
|
|
|
|
<div class="login-form-side"> |
|
|
|
<div class="login-form-wrap"> |
|
|
|
<div class="login-panel" :class="{ active: mode === 'login' }"> |
|
|
|
<div class="login-method-tabs"> |
|
|
|
<button :class="{ active: method === 'password' }" type="button" @click="method = 'password'">账号密码登录</button> |
|
|
|
<button :class="{ active: method === 'sms' }" type="button" @click="method = 'sms'">手机号登录</button> |
|
|
|
</div> |
|
|
|
<form novalidate @submit.prevent="submitLogin"> |
|
|
|
<div class="login-method-fields password-fields" v-show="method === 'password'"> |
|
|
|
<label><span>账号</span><div class="login-input"><svg><use href="#i-user"/></svg><input v-model="account" type="text" placeholder="请输入手机号或企业邮箱" autocomplete="username" /></div></label> |
|
|
|
<label><span>密码</span><div class="login-input"><svg><use href="#i-lock"/></svg><input v-model="password" :type="pwdVisible ? 'text' : 'password'" placeholder="请输入登录密码" autocomplete="current-password" /><button type="button" @click="pwdVisible = !pwdVisible" :title="pwdVisible ? '隐藏密码' : '显示密码'"><svg><use href="#i-eye"/></svg></button></div></label> |
|
|
|
</div> |
|
|
|
<div class="login-method-fields sms-fields" v-show="method === 'sms'"> |
|
|
|
<label><span>手机号</span><div class="login-input"><svg><use href="#i-phone"/></svg><input v-model="smsPhone" type="tel" placeholder="请输入绑定手机号" /></div></label> |
|
|
|
<label><span>短信验证码</span><div class="login-input"><svg><use href="#i-message"/></svg><input v-model="smsCode" inputmode="numeric" maxlength="6" placeholder="请输入短信验证码" /><button class="send-code-button" type="button" :disabled="loginCodeTimer > 0" @click="sendLoginCode">{{ loginCodeTimer > 0 ? loginCodeTimer + 's 后重试' : '获取验证码' }}</button></div></label> |
|
|
|
</div> |
|
|
|
<label><span>图形验证码</span><div class="login-input captcha-input"><svg><use href="#i-shield"/></svg><input v-model="captcha" maxlength="4" placeholder="请输入验证码" /><button class="captcha-image" type="button" title="刷新验证码" @click="refreshCaptcha">{{ captchaValue }}</button></div></label> |
|
|
|
<div class="login-options"> |
|
|
|
<label><input v-model="remember" type="checkbox" /><span></span>记住登录状态</label> |
|
|
|
<button type="button" @click="forgotVisible = true">忘记密码?</button> |
|
|
|
</div> |
|
|
|
<div class="login-error">{{ loginError }}</div> |
|
|
|
<button class="login-submit" type="submit" :disabled="loading">登录平台<svg><use href="#i-chevron"/></svg></button> |
|
|
|
</form> |
|
|
|
<small class="login-help">还没有企业账号?<button type="button" class="login-switch" @click="mode = 'register'">注册企业账号</button></small> |
|
|
|
</div> |
|
|
|
|
|
|
|
<div class="login-panel" :class="{ active: mode === 'register' }"> |
|
|
|
<div class="login-form-heading"><span>企业入驻</span><h2>注册账号</h2><p>创建企业账户,开始使用低空智控平台</p></div> |
|
|
|
<form novalidate @submit.prevent="submitRegister"> |
|
|
|
<label><span>企业名称</span><div class="login-input"><svg><use href="#i-box"/></svg><input v-model="reg.company" type="text" placeholder="请输入企业全称" /></div></label> |
|
|
|
<div class="login-form-row"> |
|
|
|
<label><span>联系人</span><input v-model="reg.contact" type="text" placeholder="姓名" /></label> |
|
|
|
<label><span>联系电话</span><input v-model="reg.phone" type="tel" placeholder="手机号" /></label> |
|
|
|
</div> |
|
|
|
<label><span>企业邮箱</span><div class="login-input"><svg><use href="#i-user"/></svg><input v-model="reg.email" type="email" placeholder="用于接收平台通知" /></div></label> |
|
|
|
<div class="login-form-row"> |
|
|
|
<label><span>设置密码</span><input v-model="reg.password" type="password" placeholder="至少 6 位" /></label> |
|
|
|
<label><span>确认密码</span><input v-model="reg.confirm" type="password" placeholder="再次输入密码" /></label> |
|
|
|
</div> |
|
|
|
<label class="register-agreement"><input v-model="reg.agreement" type="checkbox" /><span></span>我已阅读并同意《平台服务协议》和《隐私政策》</label> |
|
|
|
<div class="login-error">{{ registerError }}</div> |
|
|
|
<button class="login-submit" type="submit" :disabled="loading">提交注册<svg><use href="#i-chevron"/></svg></button> |
|
|
|
</form> |
|
|
|
<small class="login-help">已有企业账号?<button type="button" class="login-switch" @click="mode = 'login'">返回登录</button></small> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
|
|
|
|
<div class="modal-backdrop" :class="{ open: forgotVisible }"> |
|
|
|
<div class="modal forgot-password-dialog" role="dialog" aria-modal="true"> |
|
|
|
<header><div><span>账号安全</span><h3>找回登录密码</h3></div><button class="icon-button" type="button" @click="forgotVisible = false"><svg><use href="#i-x"/></svg></button></header> |
|
|
|
<form novalidate @submit.prevent="submitForgot"> |
|
|
|
<label><span>登录账号</span><input v-model="forgot.account" type="text" placeholder="请输入手机号" /></label> |
|
|
|
<label><span>绑定手机号</span><input v-model="forgot.phone" type="tel" placeholder="请输入接收验证码的手机号" /></label> |
|
|
|
<label><span>短信验证码</span><div class="login-input"><svg><use href="#i-message"/></svg><input v-model="forgot.code" inputmode="numeric" maxlength="6" placeholder="请输入短信验证码" /><button class="send-code-button" type="button" @click="sendForgotCode">获取验证码</button></div></label> |
|
|
|
<label><span>设置新密码</span><input v-model="forgot.password" type="password" placeholder="至少 6 位" /></label> |
|
|
|
<div class="form-error">{{ forgotError }}</div> |
|
|
|
<footer><button class="plain-command" type="button" @click="forgotVisible = false">取消</button><button class="primary-command" type="submit">确认重置</button></footer> |
|
|
|
</form> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
</section> |
|
|
|
</template> |
|
|
|
|
|
|
|
<script setup> |
|
|
|
import { reactive, ref } from 'vue' |
|
|
|
import { useRouter } from 'vue-router' |
|
|
|
@ -106,7 +24,15 @@ const captchaValue = ref('7K4P') |
|
|
|
const loginError = ref('') |
|
|
|
const loginCodeTimer = ref(0) |
|
|
|
|
|
|
|
const reg = reactive({ company: '', contact: '', phone: '', email: '', password: '', confirm: '', agreement: false }) |
|
|
|
const reg = reactive({ |
|
|
|
company: '', |
|
|
|
contact: '', |
|
|
|
phone: '', |
|
|
|
email: '', |
|
|
|
password: '', |
|
|
|
confirm: '', |
|
|
|
agreement: false, |
|
|
|
}) |
|
|
|
const registerError = ref('') |
|
|
|
|
|
|
|
const forgotVisible = ref(false) |
|
|
|
@ -120,13 +46,28 @@ function refreshCaptcha() { |
|
|
|
|
|
|
|
async function submitLogin() { |
|
|
|
loginError.value = '' |
|
|
|
if (!captcha.value) { loginError.value = '请输入图形验证码。'; return } |
|
|
|
if (captcha.value.toUpperCase() !== captchaValue.value) { loginError.value = '图形验证码不正确,请重新输入。'; return } |
|
|
|
if (method.value === 'password' && (!account.value || !password.value)) { loginError.value = '请输入账号和密码。'; return } |
|
|
|
if (method.value === 'sms') { loginError.value = '短信登录暂未开放,请使用账号密码登录。'; return } |
|
|
|
if (!captcha.value) { |
|
|
|
loginError.value = '请输入图形验证码。' |
|
|
|
return |
|
|
|
} |
|
|
|
if (captcha.value.toUpperCase() !== captchaValue.value) { |
|
|
|
loginError.value = '图形验证码不正确,请重新输入。' |
|
|
|
return |
|
|
|
} |
|
|
|
if (method.value === 'password' && (!account.value || !password.value)) { |
|
|
|
loginError.value = '请输入账号和密码。' |
|
|
|
return |
|
|
|
} |
|
|
|
if (method.value === 'sms') { |
|
|
|
loginError.value = '短信登录暂未开放,请使用账号密码登录。' |
|
|
|
return |
|
|
|
} |
|
|
|
loading.value = true |
|
|
|
try { |
|
|
|
const data = await request.post('/v1/auth/login', { phone: account.value, password: password.value }) |
|
|
|
const data = await request.post('/v1/auth/login', { |
|
|
|
phone: account.value, |
|
|
|
password: password.value, |
|
|
|
}) |
|
|
|
userStore.setLogin(data.accessToken, data.user) |
|
|
|
ui.toast('登录成功') |
|
|
|
router.push('/monitor') |
|
|
|
@ -140,15 +81,25 @@ async function submitLogin() { |
|
|
|
async function submitRegister() { |
|
|
|
registerError.value = '' |
|
|
|
if (!reg.company || !reg.contact || !reg.phone || !reg.email || !reg.password || !reg.confirm) { |
|
|
|
registerError.value = '请完整填写注册信息。'; return |
|
|
|
registerError.value = '请完整填写注册信息。' |
|
|
|
return |
|
|
|
} |
|
|
|
if (reg.password.length < 6 || reg.password !== reg.confirm) { |
|
|
|
registerError.value = '请确认密码一致且不少于 6 位。'; return |
|
|
|
registerError.value = '请确认密码一致且不少于 6 位。' |
|
|
|
return |
|
|
|
} |
|
|
|
if (!reg.agreement) { |
|
|
|
registerError.value = '请先同意平台服务协议和隐私政策。' |
|
|
|
return |
|
|
|
} |
|
|
|
if (!reg.agreement) { registerError.value = '请先同意平台服务协议和隐私政策。'; return } |
|
|
|
loading.value = true |
|
|
|
try { |
|
|
|
const data = await request.post('/v1/auth/register', { name: reg.company, phone: reg.phone, email: reg.email, password: reg.password }) |
|
|
|
const data = await request.post('/v1/auth/register', { |
|
|
|
name: reg.company, |
|
|
|
phone: reg.phone, |
|
|
|
email: reg.email, |
|
|
|
password: reg.password, |
|
|
|
}) |
|
|
|
if (data && data.accessToken) { |
|
|
|
userStore.setLogin(data.accessToken, data.user) |
|
|
|
ui.toast('注册成功') |
|
|
|
@ -167,7 +118,10 @@ async function submitRegister() { |
|
|
|
} |
|
|
|
|
|
|
|
function sendLoginCode() { |
|
|
|
if (!smsPhone.value) { loginError.value = '请先输入手机号。'; return } |
|
|
|
if (!smsPhone.value) { |
|
|
|
loginError.value = '请先输入手机号。' |
|
|
|
return |
|
|
|
} |
|
|
|
if (loginCodeTimer.value) return |
|
|
|
loginCodeTimer.value = 60 |
|
|
|
const timer = setInterval(() => { |
|
|
|
@ -179,19 +133,632 @@ function sendLoginCode() { |
|
|
|
} |
|
|
|
|
|
|
|
function sendForgotCode() { |
|
|
|
if (!forgot.phone) { forgotError.value = '请先输入手机号。'; return } |
|
|
|
if (!forgot.phone) { |
|
|
|
forgotError.value = '请先输入手机号。' |
|
|
|
return |
|
|
|
} |
|
|
|
request.post('/v1/auth/sms-code', { phone: forgot.phone }).catch(() => {}) |
|
|
|
ui.toast('短信验证码已发送') |
|
|
|
} |
|
|
|
|
|
|
|
async function submitForgot() { |
|
|
|
forgotError.value = '' |
|
|
|
if (!forgot.account || !forgot.phone || !forgot.code || !forgot.password) { forgotError.value = '请完整填写找回信息。'; return } |
|
|
|
if (forgot.password.length < 6) { forgotError.value = '新密码至少需要 6 位。'; return } |
|
|
|
if (!forgot.account || !forgot.phone || !forgot.code || !forgot.password) { |
|
|
|
forgotError.value = '请完整填写找回信息。' |
|
|
|
return false |
|
|
|
} |
|
|
|
if (forgot.password.length < 6) { |
|
|
|
forgotError.value = '新密码至少需要 6 位。' |
|
|
|
return false |
|
|
|
} |
|
|
|
try { |
|
|
|
await request.post('/v1/auth/reset-password', { phone: forgot.phone, smsCode: forgot.code, newPassword: forgot.password }) |
|
|
|
await request.post('/v1/auth/reset-password', { |
|
|
|
phone: forgot.phone, |
|
|
|
smsCode: forgot.code, |
|
|
|
newPassword: forgot.password, |
|
|
|
}) |
|
|
|
forgotVisible.value = false |
|
|
|
ui.toast('密码已重置,请使用新密码登录') |
|
|
|
} catch (e) {} |
|
|
|
} catch (e) { |
|
|
|
return false |
|
|
|
} |
|
|
|
} |
|
|
|
</script> |
|
|
|
|
|
|
|
<template> |
|
|
|
<section :class="s.page"> |
|
|
|
<div :class="s.visual"> |
|
|
|
<div :class="s.visualMap" /> |
|
|
|
<div :class="s.visualShade" /> |
|
|
|
<div :class="s.brand"> |
|
|
|
<div :class="s.brandMark"><span /><span /><span /></div> |
|
|
|
<div> |
|
|
|
<strong>嘉谷</strong> |
|
|
|
<small>低空智控平台</small> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
<div :class="s.visualCopy"> |
|
|
|
<span>LOW-ALTITUDE INTELLIGENCE</span> |
|
|
|
<h1>让每一次飞行<br />都可控、可追溯</h1> |
|
|
|
<p>统一管理机巢、无人机、任务与视频资产</p> |
|
|
|
</div> |
|
|
|
<div :class="s.visualFooter"> |
|
|
|
<span>嘉谷低空智控平台</span> |
|
|
|
<span>© 2026 嘉谷科技</span> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
|
|
|
|
<div :class="s.formSide"> |
|
|
|
<div :class="s.formWrap"> |
|
|
|
<div v-show="mode === 'login'" :class="s.panel"> |
|
|
|
<div :class="s.methodTabs"> |
|
|
|
<button |
|
|
|
type="button" |
|
|
|
:class="[s.tab, { [s.tabActive]: method === 'password' }]" |
|
|
|
@click="method = 'password'" |
|
|
|
> |
|
|
|
账号密码登录 |
|
|
|
</button> |
|
|
|
<button |
|
|
|
type="button" |
|
|
|
:class="[s.tab, { [s.tabActive]: method === 'sms' }]" |
|
|
|
@click="method = 'sms'" |
|
|
|
> |
|
|
|
手机号登录 |
|
|
|
</button> |
|
|
|
</div> |
|
|
|
|
|
|
|
<t-form label-width="0" @submit="submitLogin"> |
|
|
|
<template v-if="method === 'password'"> |
|
|
|
<t-form-item> |
|
|
|
<t-input |
|
|
|
v-model="account" |
|
|
|
size="large" |
|
|
|
clearable |
|
|
|
placeholder="请输入手机号或企业邮箱" |
|
|
|
autocomplete="username" |
|
|
|
> |
|
|
|
<template #prefix-icon> |
|
|
|
<t-icon name="user" /> |
|
|
|
</template> |
|
|
|
</t-input> |
|
|
|
</t-form-item> |
|
|
|
<t-form-item> |
|
|
|
<t-input |
|
|
|
v-model="password" |
|
|
|
size="large" |
|
|
|
clearable |
|
|
|
:type="pwdVisible ? 'text' : 'password'" |
|
|
|
placeholder="请输入登录密码" |
|
|
|
autocomplete="current-password" |
|
|
|
> |
|
|
|
<template #prefix-icon> |
|
|
|
<t-icon name="lock-on" /> |
|
|
|
</template> |
|
|
|
<template #suffix-icon> |
|
|
|
<t-icon |
|
|
|
:name="pwdVisible ? 'browse' : 'browse-off'" |
|
|
|
:class="s.iconClick" |
|
|
|
@click="pwdVisible = !pwdVisible" |
|
|
|
/> |
|
|
|
</template> |
|
|
|
</t-input> |
|
|
|
</t-form-item> |
|
|
|
</template> |
|
|
|
|
|
|
|
<template v-else> |
|
|
|
<t-form-item> |
|
|
|
<t-input |
|
|
|
v-model="smsPhone" |
|
|
|
size="large" |
|
|
|
clearable |
|
|
|
type="tel" |
|
|
|
placeholder="请输入绑定手机号" |
|
|
|
> |
|
|
|
<template #prefix-icon> |
|
|
|
<t-icon name="mobile" /> |
|
|
|
</template> |
|
|
|
</t-input> |
|
|
|
</t-form-item> |
|
|
|
<t-form-item> |
|
|
|
<div :class="s.codeRow"> |
|
|
|
<t-input |
|
|
|
v-model="smsCode" |
|
|
|
size="large" |
|
|
|
clearable |
|
|
|
:maxlength="6" |
|
|
|
placeholder="请输入短信验证码" |
|
|
|
> |
|
|
|
<template #prefix-icon> |
|
|
|
<t-icon name="mail" /> |
|
|
|
</template> |
|
|
|
</t-input> |
|
|
|
<t-button |
|
|
|
theme="default" |
|
|
|
variant="outline" |
|
|
|
size="large" |
|
|
|
:disabled="loginCodeTimer > 0" |
|
|
|
@click="sendLoginCode" |
|
|
|
> |
|
|
|
{{ loginCodeTimer > 0 ? `${loginCodeTimer}s 后重试` : '获取验证码' }} |
|
|
|
</t-button> |
|
|
|
</div> |
|
|
|
</t-form-item> |
|
|
|
</template> |
|
|
|
|
|
|
|
<t-form-item> |
|
|
|
<div :class="s.codeRow"> |
|
|
|
<t-input |
|
|
|
v-model="captcha" |
|
|
|
size="large" |
|
|
|
clearable |
|
|
|
:maxlength="4" |
|
|
|
placeholder="请输入验证码" |
|
|
|
> |
|
|
|
<template #prefix-icon> |
|
|
|
<t-icon name="secured" /> |
|
|
|
</template> |
|
|
|
</t-input> |
|
|
|
<t-button |
|
|
|
theme="default" |
|
|
|
variant="outline" |
|
|
|
size="large" |
|
|
|
:class="s.captchaBtn" |
|
|
|
title="刷新验证码" |
|
|
|
@click="refreshCaptcha" |
|
|
|
> |
|
|
|
{{ captchaValue }} |
|
|
|
</t-button> |
|
|
|
</div> |
|
|
|
</t-form-item> |
|
|
|
|
|
|
|
<div :class="s.options"> |
|
|
|
<t-checkbox v-model="remember">记住登录状态</t-checkbox> |
|
|
|
<t-button theme="primary" variant="text" @click="forgotVisible = true"> |
|
|
|
忘记密码? |
|
|
|
</t-button> |
|
|
|
</div> |
|
|
|
|
|
|
|
<div v-if="loginError" :class="s.error">{{ loginError }}</div> |
|
|
|
|
|
|
|
<t-form-item> |
|
|
|
<t-button |
|
|
|
block |
|
|
|
size="large" |
|
|
|
type="submit" |
|
|
|
theme="primary" |
|
|
|
:loading="loading" |
|
|
|
> |
|
|
|
登录平台 |
|
|
|
</t-button> |
|
|
|
</t-form-item> |
|
|
|
</t-form> |
|
|
|
|
|
|
|
<p :class="s.help"> |
|
|
|
还没有企业账号? |
|
|
|
<t-button theme="primary" variant="text" @click="mode = 'register'"> |
|
|
|
注册企业账号 |
|
|
|
</t-button> |
|
|
|
</p> |
|
|
|
</div> |
|
|
|
|
|
|
|
<div v-show="mode === 'register'" :class="s.panel"> |
|
|
|
<div :class="s.heading"> |
|
|
|
<span>企业入驻</span> |
|
|
|
<h2>注册账号</h2> |
|
|
|
<p>创建企业账户,开始使用低空智控平台</p> |
|
|
|
</div> |
|
|
|
|
|
|
|
<t-form label-width="0" @submit="submitRegister"> |
|
|
|
<t-form-item> |
|
|
|
<t-input |
|
|
|
v-model="reg.company" |
|
|
|
size="large" |
|
|
|
clearable |
|
|
|
placeholder="请输入企业全称" |
|
|
|
> |
|
|
|
<template #prefix-icon> |
|
|
|
<t-icon name="root-list" /> |
|
|
|
</template> |
|
|
|
</t-input> |
|
|
|
</t-form-item> |
|
|
|
|
|
|
|
<div :class="s.row"> |
|
|
|
<t-form-item> |
|
|
|
<t-input v-model="reg.contact" size="large" clearable placeholder="联系人姓名" /> |
|
|
|
</t-form-item> |
|
|
|
<t-form-item> |
|
|
|
<t-input |
|
|
|
v-model="reg.phone" |
|
|
|
size="large" |
|
|
|
clearable |
|
|
|
type="tel" |
|
|
|
placeholder="联系电话" |
|
|
|
/> |
|
|
|
</t-form-item> |
|
|
|
</div> |
|
|
|
|
|
|
|
<t-form-item> |
|
|
|
<t-input |
|
|
|
v-model="reg.email" |
|
|
|
size="large" |
|
|
|
clearable |
|
|
|
type="email" |
|
|
|
placeholder="企业邮箱,用于接收平台通知" |
|
|
|
> |
|
|
|
<template #prefix-icon> |
|
|
|
<t-icon name="mail" /> |
|
|
|
</template> |
|
|
|
</t-input> |
|
|
|
</t-form-item> |
|
|
|
|
|
|
|
<div :class="s.row"> |
|
|
|
<t-form-item> |
|
|
|
<t-input |
|
|
|
v-model="reg.password" |
|
|
|
size="large" |
|
|
|
type="password" |
|
|
|
clearable |
|
|
|
placeholder="设置密码(至少 6 位)" |
|
|
|
/> |
|
|
|
</t-form-item> |
|
|
|
<t-form-item> |
|
|
|
<t-input |
|
|
|
v-model="reg.confirm" |
|
|
|
size="large" |
|
|
|
type="password" |
|
|
|
clearable |
|
|
|
placeholder="确认密码" |
|
|
|
/> |
|
|
|
</t-form-item> |
|
|
|
</div> |
|
|
|
|
|
|
|
<t-form-item> |
|
|
|
<t-checkbox v-model="reg.agreement"> |
|
|
|
我已阅读并同意《平台服务协议》和《隐私政策》 |
|
|
|
</t-checkbox> |
|
|
|
</t-form-item> |
|
|
|
|
|
|
|
<div v-if="registerError" :class="s.error">{{ registerError }}</div> |
|
|
|
|
|
|
|
<t-form-item> |
|
|
|
<t-button |
|
|
|
block |
|
|
|
size="large" |
|
|
|
type="submit" |
|
|
|
theme="primary" |
|
|
|
:loading="loading" |
|
|
|
> |
|
|
|
提交注册 |
|
|
|
</t-button> |
|
|
|
</t-form-item> |
|
|
|
</t-form> |
|
|
|
|
|
|
|
<p :class="s.help"> |
|
|
|
已有企业账号? |
|
|
|
<t-button theme="primary" variant="text" @click="mode = 'login'"> |
|
|
|
返回登录 |
|
|
|
</t-button> |
|
|
|
</p> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
|
|
|
|
<t-dialog |
|
|
|
v-model:visible="forgotVisible" |
|
|
|
header="找回登录密码" |
|
|
|
width="420px" |
|
|
|
:confirm-btn="{ content: '确认重置', theme: 'primary' }" |
|
|
|
cancel-btn="取消" |
|
|
|
@confirm="submitForgot" |
|
|
|
> |
|
|
|
<t-form label-width="0"> |
|
|
|
<t-form-item> |
|
|
|
<t-input v-model="forgot.account" size="large" clearable placeholder="请输入登录账号(手机号)" /> |
|
|
|
</t-form-item> |
|
|
|
<t-form-item> |
|
|
|
<t-input |
|
|
|
v-model="forgot.phone" |
|
|
|
size="large" |
|
|
|
clearable |
|
|
|
type="tel" |
|
|
|
placeholder="请输入接收验证码的手机号" |
|
|
|
/> |
|
|
|
</t-form-item> |
|
|
|
<t-form-item> |
|
|
|
<div :class="s.codeRow"> |
|
|
|
<t-input |
|
|
|
v-model="forgot.code" |
|
|
|
size="large" |
|
|
|
clearable |
|
|
|
:maxlength="6" |
|
|
|
placeholder="请输入短信验证码" |
|
|
|
/> |
|
|
|
<t-button theme="default" variant="outline" size="large" @click="sendForgotCode"> |
|
|
|
获取验证码 |
|
|
|
</t-button> |
|
|
|
</div> |
|
|
|
</t-form-item> |
|
|
|
<t-form-item> |
|
|
|
<t-input |
|
|
|
v-model="forgot.password" |
|
|
|
size="large" |
|
|
|
type="password" |
|
|
|
clearable |
|
|
|
placeholder="设置新密码(至少 6 位)" |
|
|
|
/> |
|
|
|
</t-form-item> |
|
|
|
<div v-if="forgotError" :class="s.error">{{ forgotError }}</div> |
|
|
|
</t-form> |
|
|
|
</t-dialog> |
|
|
|
</section> |
|
|
|
</template> |
|
|
|
|
|
|
|
<style lang="less" module="s"> |
|
|
|
.page { |
|
|
|
position: relative; |
|
|
|
display: block; |
|
|
|
min-height: 100vh; |
|
|
|
overflow: hidden; |
|
|
|
background: #102a35; |
|
|
|
} |
|
|
|
|
|
|
|
.visual { |
|
|
|
position: absolute; |
|
|
|
z-index: 0; |
|
|
|
inset: 0; |
|
|
|
width: 100%; |
|
|
|
min-height: 100vh; |
|
|
|
color: #fff; |
|
|
|
background: #1b323c; |
|
|
|
overflow: hidden; |
|
|
|
} |
|
|
|
|
|
|
|
.visualMap { |
|
|
|
position: absolute; |
|
|
|
inset: 0; |
|
|
|
background-image: url('/assets/login-hero-dock-drone.png'); |
|
|
|
background-size: cover; |
|
|
|
background-position: center; |
|
|
|
filter: saturate(0.82) contrast(1.04); |
|
|
|
} |
|
|
|
|
|
|
|
.visualShade { |
|
|
|
position: absolute; |
|
|
|
inset: 0; |
|
|
|
background: |
|
|
|
linear-gradient(90deg, rgba(10, 31, 40, 0.78), rgba(12, 38, 48, 0.18) 62%, rgba(9, 26, 34, 0.34)), |
|
|
|
linear-gradient(0deg, rgba(9, 26, 34, 0.72), transparent 42%); |
|
|
|
} |
|
|
|
|
|
|
|
.brand { |
|
|
|
position: absolute; |
|
|
|
z-index: 1; |
|
|
|
top: 36px; |
|
|
|
left: 40px; |
|
|
|
display: flex; |
|
|
|
align-items: center; |
|
|
|
gap: 12px; |
|
|
|
|
|
|
|
strong { |
|
|
|
display: block; |
|
|
|
font-size: 18px; |
|
|
|
font-weight: 600; |
|
|
|
} |
|
|
|
|
|
|
|
small { |
|
|
|
display: block; |
|
|
|
margin-top: 2px; |
|
|
|
opacity: 0.78; |
|
|
|
font-size: 12px; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
.brandMark { |
|
|
|
display: grid; |
|
|
|
grid-template-columns: repeat(3, 6px); |
|
|
|
gap: 4px; |
|
|
|
|
|
|
|
span { |
|
|
|
width: 6px; |
|
|
|
height: 18px; |
|
|
|
border-radius: 2px; |
|
|
|
background: #5ec8f0; |
|
|
|
|
|
|
|
&:nth-child(2) { |
|
|
|
opacity: 0.7; |
|
|
|
height: 12px; |
|
|
|
align-self: end; |
|
|
|
} |
|
|
|
|
|
|
|
&:nth-child(3) { |
|
|
|
opacity: 0.45; |
|
|
|
height: 8px; |
|
|
|
align-self: end; |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
.visualCopy { |
|
|
|
position: absolute; |
|
|
|
z-index: 1; |
|
|
|
left: 40px; |
|
|
|
bottom: 96px; |
|
|
|
max-width: 420px; |
|
|
|
|
|
|
|
> span { |
|
|
|
letter-spacing: 0.12em; |
|
|
|
font-size: 11px; |
|
|
|
opacity: 0.72; |
|
|
|
} |
|
|
|
|
|
|
|
h1 { |
|
|
|
margin: 14px 0 12px; |
|
|
|
font-size: 34px; |
|
|
|
line-height: 1.25; |
|
|
|
font-weight: 600; |
|
|
|
} |
|
|
|
|
|
|
|
p { |
|
|
|
margin: 0; |
|
|
|
opacity: 0.82; |
|
|
|
font-size: 14px; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
.visualFooter { |
|
|
|
position: absolute; |
|
|
|
z-index: 1; |
|
|
|
left: 40px; |
|
|
|
right: 40px; |
|
|
|
bottom: 28px; |
|
|
|
display: flex; |
|
|
|
justify-content: space-between; |
|
|
|
opacity: 0.65; |
|
|
|
font-size: 12px; |
|
|
|
} |
|
|
|
|
|
|
|
.formSide { |
|
|
|
position: relative; |
|
|
|
z-index: 2; |
|
|
|
width: 100%; |
|
|
|
min-height: 100vh; |
|
|
|
padding: 40px 24px; |
|
|
|
display: flex; |
|
|
|
align-items: center; |
|
|
|
justify-content: center; |
|
|
|
} |
|
|
|
|
|
|
|
.formWrap { |
|
|
|
width: min(400px, 100%); |
|
|
|
padding: 36px 32px 28px; |
|
|
|
border: 1px solid rgba(225, 231, 236, 0.9); |
|
|
|
border-radius: 10px; |
|
|
|
background: rgba(255, 255, 255, 0.96); |
|
|
|
box-shadow: 0 20px 55px rgba(10, 31, 43, 0.25); |
|
|
|
} |
|
|
|
|
|
|
|
.panel { |
|
|
|
width: 100%; |
|
|
|
} |
|
|
|
|
|
|
|
.methodTabs { |
|
|
|
height: 38px; |
|
|
|
margin: 0 0 22px; |
|
|
|
display: grid; |
|
|
|
grid-template-columns: 1fr 1fr; |
|
|
|
border-bottom: 1px solid #dce4e9; |
|
|
|
} |
|
|
|
|
|
|
|
.tab { |
|
|
|
position: relative; |
|
|
|
border: 0; |
|
|
|
color: #7d8b94; |
|
|
|
background: transparent; |
|
|
|
font-size: 13px; |
|
|
|
cursor: pointer; |
|
|
|
} |
|
|
|
|
|
|
|
.tabActive { |
|
|
|
color: #1f6feb; |
|
|
|
font-weight: 600; |
|
|
|
|
|
|
|
&::after { |
|
|
|
content: ''; |
|
|
|
position: absolute; |
|
|
|
right: 0; |
|
|
|
bottom: -1px; |
|
|
|
left: 0; |
|
|
|
height: 2px; |
|
|
|
background: #1f6feb; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
.codeRow { |
|
|
|
width: 100%; |
|
|
|
display: grid; |
|
|
|
grid-template-columns: 1fr auto; |
|
|
|
gap: 10px; |
|
|
|
align-items: center; |
|
|
|
} |
|
|
|
|
|
|
|
.captchaBtn { |
|
|
|
min-width: 96px; |
|
|
|
letter-spacing: 0.18em; |
|
|
|
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace; |
|
|
|
} |
|
|
|
|
|
|
|
.options { |
|
|
|
display: flex; |
|
|
|
align-items: center; |
|
|
|
justify-content: space-between; |
|
|
|
margin: 4px 0 14px; |
|
|
|
} |
|
|
|
|
|
|
|
.error { |
|
|
|
margin: 0 0 12px; |
|
|
|
color: #d54941; |
|
|
|
font-size: 13px; |
|
|
|
} |
|
|
|
|
|
|
|
.help { |
|
|
|
margin: 8px 0 0; |
|
|
|
color: #6b7b86; |
|
|
|
font-size: 13px; |
|
|
|
text-align: center; |
|
|
|
} |
|
|
|
|
|
|
|
.heading { |
|
|
|
margin-bottom: 22px; |
|
|
|
|
|
|
|
> span { |
|
|
|
color: #1f6feb; |
|
|
|
font-size: 12px; |
|
|
|
} |
|
|
|
|
|
|
|
h2 { |
|
|
|
margin: 10px 0 8px; |
|
|
|
color: #263945; |
|
|
|
font-size: 28px; |
|
|
|
font-weight: 600; |
|
|
|
} |
|
|
|
|
|
|
|
p { |
|
|
|
margin: 0; |
|
|
|
color: #6b7b86; |
|
|
|
font-size: 13px; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
.row { |
|
|
|
display: grid; |
|
|
|
grid-template-columns: 1fr 1fr; |
|
|
|
gap: 10px; |
|
|
|
} |
|
|
|
|
|
|
|
.iconClick { |
|
|
|
cursor: pointer; |
|
|
|
} |
|
|
|
|
|
|
|
@media (max-width: 820px) { |
|
|
|
.visual { |
|
|
|
display: none; |
|
|
|
} |
|
|
|
|
|
|
|
.page { |
|
|
|
background: #f3f6f9; |
|
|
|
} |
|
|
|
|
|
|
|
.formSide { |
|
|
|
padding: 28px 22px; |
|
|
|
} |
|
|
|
|
|
|
|
.formWrap { |
|
|
|
box-shadow: 0 12px 28px rgba(28, 53, 70, 0.08); |
|
|
|
} |
|
|
|
|
|
|
|
.row { |
|
|
|
grid-template-columns: 1fr; |
|
|
|
} |
|
|
|
} |
|
|
|
</style> |
|
|
|
|