204 lines
4.7 KiB
Vue
204 lines
4.7 KiB
Vue
<script setup lang="ts">
|
|
import { computed, ref } from 'vue'
|
|
import { useRoute, useRouter } from 'vue-router'
|
|
import { showToast } from 'vant'
|
|
import { BadgeCheck, Phone, ShieldCheck } from 'lucide-vue-next'
|
|
import { sendMobileCode } from '@/api/auth'
|
|
import { useAuthStore } from '@/stores/auth'
|
|
|
|
const router = useRouter()
|
|
const route = useRoute()
|
|
const auth = useAuthStore()
|
|
|
|
const phone = ref('')
|
|
const code = ref('')
|
|
const loading = ref(false)
|
|
const sending = ref(false)
|
|
const seconds = ref(0)
|
|
|
|
let timer: number | undefined
|
|
|
|
const canSend = computed(() => /^1\d{10}$/.test(phone.value) && seconds.value === 0)
|
|
|
|
const startCountdown = () => {
|
|
seconds.value = 60
|
|
window.clearInterval(timer)
|
|
timer = window.setInterval(() => {
|
|
seconds.value -= 1
|
|
if (seconds.value <= 0) {
|
|
window.clearInterval(timer)
|
|
}
|
|
}, 1000)
|
|
}
|
|
|
|
const handleSend = async () => {
|
|
if (!canSend.value) {
|
|
showToast('请输入正确手机号')
|
|
return
|
|
}
|
|
sending.value = true
|
|
try {
|
|
const result = await sendMobileCode(phone.value)
|
|
const message = typeof result === 'object' && result && 'msg' in result ? result.msg : ''
|
|
if (message) {
|
|
code.value = String(message)
|
|
showToast(`验证码:${message}`)
|
|
} else {
|
|
showToast('验证码已发送')
|
|
}
|
|
startCountdown()
|
|
} finally {
|
|
sending.value = false
|
|
}
|
|
}
|
|
|
|
const handleLogin = async () => {
|
|
if (!/^1\d{10}$/.test(phone.value)) {
|
|
showToast('请输入正确手机号')
|
|
return
|
|
}
|
|
if (!code.value) {
|
|
showToast('请输入验证码')
|
|
return
|
|
}
|
|
loading.value = true
|
|
try {
|
|
await auth.login(phone.value, code.value)
|
|
const redirect = typeof route.query.redirect === 'string' ? route.query.redirect : '/home'
|
|
await router.replace(redirect)
|
|
} catch (error) {
|
|
showToast(error instanceof Error ? error.message : '登录失败')
|
|
} finally {
|
|
loading.value = false
|
|
}
|
|
}
|
|
</script>
|
|
|
|
<template>
|
|
<main class="login-page">
|
|
<section class="login-card">
|
|
<div class="login-brand">
|
|
<span class="login-logo"><ShieldCheck :size="30" /></span>
|
|
<div>
|
|
<p>员工端</p>
|
|
<h1>手机号登录</h1>
|
|
</div>
|
|
</div>
|
|
|
|
<van-form class="login-form" @submit="handleLogin">
|
|
<van-field
|
|
v-model="phone"
|
|
type="tel"
|
|
name="phone"
|
|
maxlength="11"
|
|
placeholder="请输入手机号"
|
|
autocomplete="tel"
|
|
clearable
|
|
>
|
|
<template #left-icon><Phone :size="18" /></template>
|
|
</van-field>
|
|
<van-field
|
|
v-model="code"
|
|
type="text"
|
|
name="code"
|
|
maxlength="12"
|
|
placeholder="请输入验证码"
|
|
clearable
|
|
>
|
|
<template #left-icon><BadgeCheck :size="18" /></template>
|
|
<template #button>
|
|
<van-button
|
|
size="small"
|
|
type="primary"
|
|
plain
|
|
native-type="button"
|
|
:disabled="!canSend || sending"
|
|
:loading="sending"
|
|
@click="handleSend"
|
|
>
|
|
{{ seconds > 0 ? `${seconds}s` : '获取验证码' }}
|
|
</van-button>
|
|
</template>
|
|
</van-field>
|
|
|
|
<van-button block type="primary" native-type="submit" :loading="loading">
|
|
登录
|
|
</van-button>
|
|
</van-form>
|
|
|
|
<p class="login-note">
|
|
{{ auth.isMockMode ? '当前为 mock 模式,可直接获取验证码体验。' : '登录后将进入员工绑定组织下的订单与事件工作区。' }}
|
|
</p>
|
|
</section>
|
|
</main>
|
|
</template>
|
|
|
|
<style scoped>
|
|
.login-page {
|
|
display: grid;
|
|
min-height: 100vh;
|
|
padding: 18px;
|
|
place-items: center;
|
|
background:
|
|
linear-gradient(180deg, #dff4ec 0, rgba(246, 248, 247, 0) 44%),
|
|
var(--app-bg);
|
|
}
|
|
|
|
.login-card {
|
|
width: min(100%, 430px);
|
|
padding: 22px 16px 16px;
|
|
border: 1px solid var(--line);
|
|
border-radius: var(--radius);
|
|
background: var(--surface);
|
|
box-shadow: var(--shadow);
|
|
}
|
|
|
|
.login-brand {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 12px;
|
|
margin-bottom: 24px;
|
|
}
|
|
|
|
.login-logo {
|
|
display: inline-grid;
|
|
width: 54px;
|
|
height: 54px;
|
|
border-radius: var(--radius);
|
|
background: var(--primary-soft);
|
|
color: var(--primary-deep);
|
|
place-items: center;
|
|
}
|
|
|
|
.login-brand p {
|
|
margin: 0 0 4px;
|
|
color: var(--text-muted);
|
|
font-size: 13px;
|
|
}
|
|
|
|
.login-brand h1 {
|
|
margin: 0;
|
|
color: var(--text-strong);
|
|
font-size: 25px;
|
|
line-height: 1.2;
|
|
}
|
|
|
|
.login-form {
|
|
display: grid;
|
|
gap: 14px;
|
|
}
|
|
|
|
.login-form :deep(.van-cell) {
|
|
min-height: 50px;
|
|
border: 1px solid var(--line);
|
|
border-radius: var(--radius);
|
|
}
|
|
|
|
.login-note {
|
|
margin: 14px 0 0;
|
|
color: var(--text-muted);
|
|
font-size: 12px;
|
|
line-height: 1.5;
|
|
}
|
|
</style>
|