227 lines
5.0 KiB
Vue
227 lines
5.0 KiB
Vue
<template>
|
||
<div class="login-container">
|
||
<div class="login-content">
|
||
<div class="login-brand">
|
||
<h1 class="brand-title">CTMS</h1>
|
||
<p class="brand-subtitle">临床试验管理系统</p>
|
||
</div>
|
||
|
||
<el-card class="login-card">
|
||
<div class="login-header">
|
||
<h2 class="form-title">用户登录</h2>
|
||
<p class="form-desc">请输入邮箱与密码登录,未激活账号将无法登录</p>
|
||
</div>
|
||
|
||
<el-form
|
||
:model="form"
|
||
ref="formRef"
|
||
:rules="rules"
|
||
label-position="top"
|
||
@keyup.enter.native="onSubmit"
|
||
>
|
||
<el-form-item label="邮箱" prop="email">
|
||
<el-input
|
||
v-model="form.email"
|
||
placeholder="请输入邮箱"
|
||
autocomplete="email"
|
||
/>
|
||
</el-form-item>
|
||
<el-form-item label="密码" prop="password">
|
||
<el-input
|
||
v-model="form.password"
|
||
type="password"
|
||
placeholder="请输入密码"
|
||
autocomplete="current-password"
|
||
show-password
|
||
/>
|
||
</el-form-item>
|
||
|
||
<div class="form-footer">
|
||
<el-checkbox v-model="form.remember">记住账号密码</el-checkbox>
|
||
<el-button
|
||
type="primary"
|
||
:loading="loading"
|
||
class="submit-btn"
|
||
@click="onSubmit"
|
||
>
|
||
登 录
|
||
</el-button>
|
||
<div class="link-row">
|
||
<RouterLink to="/register">没有账号?注册</RouterLink>
|
||
</div>
|
||
</div>
|
||
</el-form>
|
||
</el-card>
|
||
|
||
<div class="login-footer">
|
||
<p>© 2025 Clinical Trial Management System. All Rights Reserved.</p>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</template>
|
||
|
||
<script setup lang="ts">
|
||
import { reactive, ref, onMounted } from "vue";
|
||
import { useRouter } from "vue-router";
|
||
import { ElCheckbox, ElMessage, type FormInstance, type FormRules } from "element-plus";
|
||
import { useAuthStore } from "../store/auth";
|
||
import { getCachedCredential, setCachedCredential, clearCachedCredential } from "../utils/auth";
|
||
|
||
const auth = useAuthStore();
|
||
const router = useRouter();
|
||
|
||
const formRef = ref<FormInstance>();
|
||
const form = reactive({
|
||
email: "",
|
||
password: "",
|
||
remember: false,
|
||
});
|
||
|
||
const rules: FormRules<typeof form> = {
|
||
email: [
|
||
{ required: true, message: "请输入邮箱", trigger: "blur" },
|
||
{ type: "email", message: "邮箱格式不正确", trigger: "blur" },
|
||
],
|
||
password: [{ required: true, message: "请输入密码", trigger: "blur" }],
|
||
};
|
||
|
||
const loading = ref(false);
|
||
|
||
onMounted(() => {
|
||
const cached = getCachedCredential();
|
||
if (cached) {
|
||
form.email = cached.email;
|
||
form.password = cached.password;
|
||
form.remember = true;
|
||
}
|
||
});
|
||
|
||
const onSubmit = async () => {
|
||
if (!formRef.value) return;
|
||
const valid = await formRef.value.validate();
|
||
if (!valid) return;
|
||
loading.value = true;
|
||
try {
|
||
await auth.login(form.email, form.password);
|
||
if (form.remember) {
|
||
setCachedCredential(form.email, form.password);
|
||
} else {
|
||
clearCachedCredential();
|
||
}
|
||
router.push("/");
|
||
} catch (error: any) {
|
||
const detail = error?.response?.data?.detail || error?.response?.data?.message;
|
||
ElMessage.error(detail || "邮箱或密码错误");
|
||
} finally {
|
||
loading.value = false;
|
||
}
|
||
};
|
||
</script>
|
||
|
||
<style scoped>
|
||
.login-container {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
min-height: 100vh;
|
||
background: radial-gradient(circle at 50% 50%, #f0f2f5 0%, #e6e9f0 100%);
|
||
position: relative;
|
||
overflow: hidden;
|
||
}
|
||
|
||
/* 装饰性背景元素,不使用外部资源 */
|
||
.login-container::before {
|
||
content: "";
|
||
position: absolute;
|
||
width: 600px;
|
||
height: 600px;
|
||
background: var(--ctms-primary);
|
||
opacity: 0.03;
|
||
border-radius: 50%;
|
||
top: -200px;
|
||
right: -200px;
|
||
}
|
||
|
||
.login-content {
|
||
width: 400px;
|
||
z-index: 1;
|
||
}
|
||
|
||
.login-brand {
|
||
text-align: center;
|
||
margin-bottom: 40px;
|
||
}
|
||
|
||
.brand-title {
|
||
font-size: 36px;
|
||
font-weight: 700;
|
||
color: var(--ctms-primary);
|
||
letter-spacing: 2px;
|
||
margin: 0;
|
||
}
|
||
|
||
.brand-subtitle {
|
||
font-size: 16px;
|
||
color: var(--ctms-text-secondary);
|
||
margin-top: 8px;
|
||
}
|
||
|
||
.login-card {
|
||
padding: 8px;
|
||
}
|
||
|
||
.login-header {
|
||
margin-bottom: 32px;
|
||
}
|
||
|
||
.form-title {
|
||
font-size: 20px;
|
||
font-weight: 600;
|
||
color: var(--ctms-text-main);
|
||
margin: 0;
|
||
}
|
||
|
||
.form-desc {
|
||
font-size: 14px;
|
||
color: var(--ctms-text-secondary);
|
||
margin-top: 8px;
|
||
}
|
||
|
||
.submit-btn {
|
||
width: 100%;
|
||
height: 44px;
|
||
font-size: 16px;
|
||
margin-top: 16px;
|
||
}
|
||
.link-row {
|
||
margin-top: 12px;
|
||
text-align: center;
|
||
}
|
||
.link-row a {
|
||
color: var(--ctms-primary);
|
||
font-size: 14px;
|
||
}
|
||
|
||
.login-footer {
|
||
text-align: center;
|
||
margin-top: 48px;
|
||
}
|
||
|
||
.login-footer p {
|
||
font-size: 12px;
|
||
color: var(--ctms-text-disabled);
|
||
}
|
||
|
||
:deep(.el-form-item__label) {
|
||
padding-bottom: 4px;
|
||
}
|
||
|
||
:deep(.el-input__wrapper) {
|
||
box-shadow: 0 0 0 1px #d9d9d9 inset;
|
||
}
|
||
|
||
:deep(.el-input__wrapper.is-focus) {
|
||
box-shadow: 0 0 0 1px var(--ctms-primary) inset !important;
|
||
}
|
||
</style>
|