Files
client-web/src/modules/auth/views/LoginView.vue
T

126 lines
4.8 KiB
Vue
Raw Normal View History

2026-07-30 17:59:42 +08:00
<script setup lang="ts">
2026-08-04 15:31:08 +08:00
import { nextTick, reactive, ref } from 'vue'
2026-07-30 17:59:42 +08:00
import { useRoute, useRouter } from 'vue-router'
import { ElMessage, type FormInstance, type FormRules } from 'element-plus'
import { ArrowRight, LockKeyhole, UserRound } from '@lucide/vue'
2026-08-03 18:52:30 +08:00
import { useAuthStore } from '@/modules/auth/store/auth'
2026-08-04 15:31:08 +08:00
import AuthShell from '@/modules/auth/components/AuthShell.vue'
2026-09-03 18:11:07 +08:00
import { getRememberedLoginId } from '@/api/session'
2026-07-30 17:59:42 +08:00
interface LoginForm {
account: string
password: string
remember: boolean
}
const route = useRoute()
const router = useRouter()
const auth = useAuthStore()
const formRef = ref<FormInstance>()
const loading = ref(false)
2026-08-13 12:22:39 +08:00
const impersonationFailed = route.query.impersonation === 'failed'
const loginIdentifiersUpdated = route.query.reason === 'identifiers-updated'
2026-09-03 18:11:07 +08:00
const rememberedLoginId = getRememberedLoginId()
2026-07-30 17:59:42 +08:00
const form = reactive<LoginForm>({
account: typeof route.query.account === 'string'
? route.query.account
2026-09-03 18:11:07 +08:00
: rememberedLoginId,
2026-07-30 17:59:42 +08:00
password: '',
2026-09-03 18:11:07 +08:00
remember: Boolean(rememberedLoginId),
2026-07-30 17:59:42 +08:00
})
const usernamePattern = /^[A-Za-z][A-Za-z0-9_.@-]{3,63}$/
const mainlandMobilePattern = /^1[3-9]\d{9}$/
function validLoginId(value: string) {
return usernamePattern.test(value) || mainlandMobilePattern.test(value)
}
2026-07-30 17:59:42 +08:00
const rules: FormRules<LoginForm> = {
account: [
{ required: true, message: '请输入用户名或手机号', trigger: 'blur' },
{
validator: (_rule, value: string, callback) => {
if (!value || validLoginId(value.trim())) callback()
else callback(new Error('请输入有效用户名或中国大陆手机号'))
},
trigger: 'blur',
},
2026-07-30 17:59:42 +08:00
],
password: [
{ required: true, message: '请输入密码', trigger: 'blur' },
{ min: 8, max: 72, message: '密码长度应为 8–72 位', trigger: 'blur' },
],
}
async function submit() {
if (!formRef.value || loading.value) return
const valid = await formRef.value.validate().catch(() => false)
2026-08-04 15:31:08 +08:00
if (!valid) {
await nextTick()
document.querySelector<HTMLElement>('.auth-form-panel .el-form-item.is-error input')?.focus()
return
}
2026-07-30 17:59:42 +08:00
loading.value = true
try {
await auth.login(form.account.trim(), form.password, form.remember)
ElMessage.success('登录成功')
await router.replace(String(route.query.redirect || '/'))
} catch (error) {
ElMessage.error(error instanceof Error ? error.message : '登录失败,请检查账号后重试')
} finally {
loading.value = false
}
}
function showResetHint() {
ElMessage.info('请联系平台客服重置密码')
}
</script>
<template>
2026-08-04 15:31:08 +08:00
<AuthShell
switch-text="还没有企业账号?"
switch-label="申请入驻"
switch-to="/register"
visual-title="跨境物流业务,一个工作台完成"
visual-description="从企业协作到头程订单跟进,让业务信息与操作权限始终清晰。"
:features="['企业级角色与权限', '头程订单全程跟进', '多成员业务协同']"
>
<div class="auth-form-content auth-form-content--login">
<div class="auth-form-heading">
2026-07-30 17:59:42 +08:00
<h2 id="login-title">登录客户工作台</h2>
<p>使用用户名或中国大陆手机号登录,继续管理您的跨境物流业务。</p>
2026-08-04 15:31:08 +08:00
</div>
2026-08-13 12:22:39 +08:00
<el-alert
v-if="impersonationFailed"
title="代登录失败或授权已失效,请返回管理后台重新发起"
type="error"
:closable="false"
show-icon
/>
<el-alert
v-if="loginIdentifiersUpdated"
title="用户名或手机号已更新,所有会话均已退出,请重新登录"
type="success"
:closable="false"
show-icon
/>
2026-08-04 15:31:08 +08:00
<div class="auth-account-note">企业注册成功后即可登录;提交订单前需完成企业资料审核。</div>
2026-07-30 17:59:42 +08:00
<el-form ref="formRef" :model="form" :rules="rules" label-position="top" size="large" @submit.prevent="submit">
<el-form-item label="用户名或手机号" prop="account">
<el-input v-model="form.account" autocomplete="username" maxlength="64" placeholder="请输入用户名或中国大陆手机号" :prefix-icon="UserRound" />
2026-07-30 17:59:42 +08:00
</el-form-item>
<el-form-item label="密码" prop="password">
<el-input v-model="form.password" autocomplete="current-password" maxlength="72" type="password" show-password placeholder="请输入 8–72 位密码" :prefix-icon="LockKeyhole" @keyup.enter="submit" />
</el-form-item>
<div class="auth-form-options">
2026-09-03 18:11:07 +08:00
<el-checkbox v-model="form.remember">记住账号</el-checkbox>
2026-07-30 17:59:42 +08:00
<button type="button" @click="showResetHint">忘记密码?</button>
</div>
<el-button native-type="submit" class="auth-submit" type="primary" :loading="loading">登录 <ArrowRight :size="17" /></el-button>
</el-form>
2026-08-04 15:31:08 +08:00
</div>
</AuthShell>
2026-07-30 17:59:42 +08:00
</template>