262 lines
7.1 KiB
Vue
262 lines
7.1 KiB
Vue
<template>
|
||
<div class="login">
|
||
<!-- 背景视频 -->
|
||
<div class="background-video-stack">
|
||
<video
|
||
class="background-video"
|
||
autoplay
|
||
muted
|
||
loop
|
||
playsinline
|
||
crossorigin="anonymous"
|
||
src="../../assets/images/loginBg.mp4"
|
||
></video>
|
||
</div>
|
||
|
||
<!-- 玻璃卡片表单 -->
|
||
<div class="form-card">
|
||
<div class="form-top">
|
||
<h2 class="form-title">欢迎来到元境</h2>
|
||
<span class="form-title"> 元启万象,境由心生 </span>
|
||
</div>
|
||
<el-form
|
||
ref="formRef"
|
||
style="max-width: 400px"
|
||
:model="form"
|
||
:rules="rules"
|
||
label-width="0px"
|
||
class="login-form"
|
||
@submit.prevent="submitForm"
|
||
>
|
||
<!-- 手机号 -->
|
||
<el-form-item prop="cellphone">
|
||
<el-input
|
||
v-model="form.cellphone"
|
||
placeholder="请输入手机号"
|
||
:prefix-icon="Iphone"
|
||
size="large"
|
||
@keyup.enter="submitForm"
|
||
/>
|
||
</el-form-item>
|
||
|
||
<!-- 验证码 + 获取按钮 -->
|
||
<el-form-item prop="captcha">
|
||
<div class="captcha-wrapper">
|
||
<el-input
|
||
v-model="form.captcha"
|
||
placeholder="请输入验证码"
|
||
:prefix-icon="Key"
|
||
size="large"
|
||
class="captcha-input"
|
||
@keyup.enter="submitForm"
|
||
/>
|
||
<el-button
|
||
:disabled="countingDown || !validPhone"
|
||
class="captcha-btn glass-btn"
|
||
@click="handleGetCaptcha"
|
||
>
|
||
{{ countingDown ? `${countdown}秒后重试` : '获取验证码' }}
|
||
</el-button>
|
||
</div>
|
||
</el-form-item>
|
||
|
||
<!-- 操作按钮 -->
|
||
<el-form-item>
|
||
<el-button
|
||
type="primary"
|
||
size="large"
|
||
class="submit-btn glass-btn"
|
||
@click="submitForm"
|
||
:loading="loading"
|
||
>
|
||
登 录 / 注 册
|
||
</el-button>
|
||
</el-form-item>
|
||
</el-form>
|
||
<div class="form-bottom">
|
||
登录即表示您同意遵守《用户协议》和《隐私政策》
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 账号选择弹窗 -->
|
||
<el-dialog
|
||
v-model="loginStore.showAccountSelect"
|
||
title="选择账号"
|
||
width="400px"
|
||
:close-on-click-modal="false"
|
||
@close="handleDialogClose"
|
||
>
|
||
<div class="account-list">
|
||
<div
|
||
v-for="account in loginStore.accountList"
|
||
:key="account.id"
|
||
class="account-item"
|
||
@click="selectAccount(account.id)"
|
||
>
|
||
<div class="account-info">
|
||
<div class="account-name">{{ account.username }}</div>
|
||
<!-- 显示手机号,优先使用 mobile 字段,其次 orgid -->
|
||
<div class="account-phone">{{ account.mobile || account.orgid }}</div>
|
||
</div>
|
||
<el-button type="primary" size="small">选择</el-button>
|
||
</div>
|
||
</div>
|
||
</el-dialog>
|
||
</div>
|
||
</template>
|
||
|
||
<script setup lang="ts">
|
||
import { reactive, ref, computed, onUnmounted } from 'vue';
|
||
import type { FormInstance, FormRules } from 'element-plus';
|
||
import { Iphone, Key } from '@element-plus/icons-vue';
|
||
import { ElMessage } from 'element-plus';
|
||
import { Code } from '@/apis/login';
|
||
import type { CodeResult, LoginParams, CaptchaParams } from '@/types/login';
|
||
import { useLoginStore } from '@/store/LoginStore';
|
||
import { debounce } from '@/utlis/debounce';
|
||
|
||
// 登录Store
|
||
const loginStore = useLoginStore();
|
||
|
||
// 表单数据类型
|
||
interface LoginForm {
|
||
cellphone: string;
|
||
captcha: string;
|
||
}
|
||
|
||
// 表单数据
|
||
const form = reactive<LoginForm>({
|
||
cellphone: '',
|
||
captcha: '',
|
||
});
|
||
|
||
// 表单实例引用
|
||
const formRef = ref<FormInstance>();
|
||
// 加载状态
|
||
const loading = ref(false);
|
||
// 登录参数(用于保存key)
|
||
const loginParams = ref<LoginParams>({
|
||
cellphone: '',
|
||
key: '',
|
||
sms_code: '',
|
||
});
|
||
|
||
// 验证规则
|
||
const rules = reactive<FormRules<LoginForm>>({
|
||
cellphone: [
|
||
{ required: true, message: '请输入手机号', trigger: 'blur' },
|
||
{ pattern: /^1[3-9]\d{9}$/, message: '手机号格式不正确', trigger: 'blur' },
|
||
],
|
||
captcha: [
|
||
{ required: true, message: '请输入验证码', trigger: 'blur' },
|
||
{ len: 6, message: '验证码应为6位数字', trigger: 'blur' },
|
||
],
|
||
});
|
||
|
||
// 验证码倒计时
|
||
const countdown = ref(60);
|
||
const countingDown = ref(false);
|
||
let timer: number | null = null;
|
||
|
||
// 手机号是否有效
|
||
const validPhone = computed(() => {
|
||
return /^1[3-9]\d{9}$/.test(form.cellphone);
|
||
});
|
||
|
||
// 获取验证码
|
||
const requestCaptcha = async () => {
|
||
if (countingDown.value) return;
|
||
|
||
try {
|
||
const res = await Code<CodeResult, CaptchaParams>({ cellphone: form.cellphone });
|
||
if (res.status === 'ok') {
|
||
ElMessage.success('发送成功');
|
||
// 保存key
|
||
loginParams.value.key = res.data?.key || '';
|
||
} else {
|
||
ElMessage.error('发送失败');
|
||
}
|
||
} catch (error) {
|
||
console.error('发送验证码失败:', error);
|
||
ElMessage.error('发送失败,请稍后重试');
|
||
}
|
||
|
||
// 开始倒计时
|
||
countingDown.value = true;
|
||
countdown.value = 60;
|
||
timer = setInterval(() => {
|
||
countdown.value--;
|
||
if (countdown.value <= 0) {
|
||
clearInterval(timer!);
|
||
timer = null;
|
||
countingDown.value = false;
|
||
}
|
||
}, 1000);
|
||
};
|
||
|
||
const handleGetCaptcha = debounce(requestCaptcha, 500);
|
||
|
||
// 提交表单(登录)
|
||
const doSubmitForm = async () => {
|
||
if (!formRef.value) return;
|
||
if (loading.value) return;
|
||
|
||
await formRef.value.validate(async (valid) => {
|
||
if (valid) {
|
||
loading.value = true;
|
||
try {
|
||
// 准备登录参数
|
||
loginParams.value.cellphone = form.cellphone;
|
||
loginParams.value.sms_code = form.captcha;
|
||
|
||
// 调用登录接口(通过store)
|
||
await loginStore.Login(loginParams.value);
|
||
} catch (error) {
|
||
console.error('登录失败:', error);
|
||
ElMessage.error('登录失败,请稍后重试');
|
||
} finally {
|
||
loading.value = false;
|
||
}
|
||
} else {
|
||
ElMessage.error('请正确填写表单');
|
||
}
|
||
});
|
||
};
|
||
|
||
const submitForm = debounce(doSubmitForm, 500);
|
||
|
||
// 选择账号
|
||
const handleSelectAccount = async (selectedId: string) => {
|
||
// 避免重复点击时多次请求
|
||
if (loading.value) return;
|
||
loading.value = true;
|
||
try {
|
||
await loginStore.loginWithSelectedAccount(selectedId, loginParams.value);
|
||
// 登录成功后会跳转,无需额外关闭弹窗(store 里已经关闭)
|
||
} catch (error) {
|
||
console.error('选择账号登录失败:', error);
|
||
ElMessage.error('登录失败,请稍后重试');
|
||
} finally {
|
||
loading.value = false;
|
||
}
|
||
};
|
||
|
||
const selectAccount = debounce(handleSelectAccount, 500);
|
||
|
||
// 弹窗关闭时的回调(如果用户直接关闭,清空账号列表,避免下次残留)
|
||
const handleDialogClose = () => {
|
||
loginStore.accountList = [];
|
||
};
|
||
|
||
// 组件卸载时清除定时器
|
||
onUnmounted(() => {
|
||
if (timer) clearInterval(timer);
|
||
handleGetCaptcha.cancel();
|
||
submitForm.cancel();
|
||
selectAccount.cancel();
|
||
});
|
||
</script>
|
||
|
||
<style scoped lang="less">
|
||
@import url('../../assets/less/Login/Login.less');
|
||
</style> |