优化登录页

This commit is contained in:
2026-08-18 11:33:28 +08:00
parent 7d8004a7ba
commit 83f36b62b4
+261 -110
View File
@@ -1,62 +1,87 @@
<template> <template>
<div class="login-wrapper"> <div class="login-page">
<div class="login-bg" /> <section class="hero" aria-hidden="false">
<div class="login-mask" /> <div class="hero-photo" />
<el-card class="login-card" shadow="always"> <div class="hero-shade" />
<div class="header"> <div class="hero-body">
<div class="logo"> <div class="mark">
<el-icon :size="28"><OfficeBuilding /></el-icon> <div class="mark-name">智慧医院</div>
<div class="mark-sub">医院信息管理系统</div>
</div> </div>
<div> <div class="hero-main" />
<h2 class="title">智慧医院管理系统</h2> <div class="hero-meta">
<p class="subtitle">患者 · 影像 · 病历 · 预约一体化管理</p> <span>{{ nowText }}</span>
<span>内部系统 · 请勿转借账号</span>
</div> </div>
</div> </div>
</section>
<el-form ref="formRef" :model="form" :rules="rules" size="large" @submit.prevent="onSubmit"> <section class="panel">
<el-form-item prop="username"> <div class="panel-inner">
<el-input v-model="form.username" placeholder="用户名" clearable> <h2>登录</h2>
<template #prefix><el-icon><User /></el-icon></template> <p class="panel-hint">输入用户名和密码进入系统</p>
</el-input>
</el-form-item> <el-form
<el-form-item prop="password"> ref="formRef"
<el-input class="login-form"
v-model="form.password" :model="form"
type="password" :rules="rules"
placeholder="密码" size="large"
show-password label-position="top"
@keyup.enter="onSubmit" hide-required-asterisk
@submit.prevent="onSubmit"
>
<el-form-item label="用户名" prop="username">
<el-input
v-model="form.username"
placeholder="请输入用户名"
maxlength="32"
clearable
autocomplete="username"
/>
</el-form-item>
<el-form-item label="密码" prop="password">
<el-input
v-model="form.password"
type="password"
placeholder="请输入密码"
maxlength="64"
show-password
autocomplete="current-password"
@keyup.enter="onSubmit"
/>
</el-form-item>
<el-button
native-type="submit"
type="primary"
class="submit"
:loading="loading"
@click="onSubmit"
> >
<template #prefix><el-icon><Lock /></el-icon></template> 登录
</el-input> </el-button>
</el-form-item> </el-form>
<el-button type="primary" class="submit" :loading="loading" @click="onSubmit">
登 录
</el-button>
</el-form>
<div class="accounts"> <div class="demo">
<div class="accounts-title">测试账号</div> <button type="button" class="demo-toggle" @click="showDemo = !showDemo">
<div class="account-row"> {{ showDemo ? '收起演示账号' : '实训演示账号' }}
<el-tag type="danger" effect="plain">管理员</el-tag> </button>
<code>admin / admin123</code> <div v-if="showDemo" class="demo-list">
<el-button link type="primary" @click="fill('admin', 'admin123')">填入</el-button> <button type="button" @click="fill('admin', 'admin123')">管理员 admin / admin123</button>
</div> <button type="button" @click="fill('doctor1', 'pass123')">医生 doctor1 / pass123</button>
<div class="account-row"> <button type="button" @click="fill('radio1', 'radio123')">放射科 radio1 / radio123</button>
<el-tag type="success" effect="plain">医生</el-tag> </div>
<code>doctor1 / pass123</code>
<el-button link type="primary" @click="fill('doctor1', 'pass123')">填入</el-button>
</div> </div>
</div> </div>
</el-card> <p class="copyright">信息科 HIS v1.0</p>
</section>
</div> </div>
</template> </template>
<script setup> <script setup>
import { ref, reactive } from 'vue' import { onBeforeUnmount, onMounted, reactive, ref } from 'vue'
import { useRouter, useRoute } from 'vue-router' import { useRouter, useRoute } from 'vue-router'
import { ElMessage } from 'element-plus' import { ElMessage } from 'element-plus'
import { User, Lock, OfficeBuilding } from '@element-plus/icons-vue'
import { useUserStore } from '@/stores/user' import { useUserStore } from '@/stores/user'
const router = useRouter() const router = useRouter()
@@ -65,12 +90,32 @@ const userStore = useUserStore()
const formRef = ref() const formRef = ref()
const loading = ref(false) const loading = ref(false)
const showDemo = ref(false)
const nowText = ref('')
const form = reactive({ username: '', password: '' }) const form = reactive({ username: '', password: '' })
const rules = { const rules = {
username: [{ required: true, message: '请输入用户名', trigger: 'blur' }], username: [{ required: true, message: '请输入用户名', trigger: 'blur' }],
password: [{ required: true, message: '请输入密码', trigger: 'blur' }] password: [{ required: true, message: '请输入密码', trigger: 'blur' }]
} }
let clockTimer = 0
const pad = (n) => String(n).padStart(2, '0')
const tick = () => {
const d = new Date()
nowText.value = `${d.getFullYear()}年${pad(d.getMonth() + 1)}月${pad(d.getDate())}日 ${pad(d.getHours())}:${pad(d.getMinutes())}:${pad(d.getSeconds())}`
}
onMounted(() => {
tick()
clockTimer = window.setInterval(tick, 1000)
})
onBeforeUnmount(() => {
window.clearInterval(clockTimer)
})
const fill = (u, p) => { const fill = (u, p) => {
form.username = u form.username = u
form.password = p form.password = p
@@ -93,100 +138,206 @@ const onSubmit = async () => {
</script> </script>
<style scoped> <style scoped>
.login-wrapper { .login-page {
position: relative;
min-height: 100vh;
height: 100vh; height: 100vh;
overflow: hidden;
display: flex; display: flex;
align-items: center; background: #f3f6f9;
justify-content: center;
padding: 24px;
} }
.login-bg { .hero {
position: relative;
flex: 1.15;
min-width: 0;
overflow: hidden;
color: #fff;
}
.hero-photo {
position: absolute; position: absolute;
inset: 0; inset: 0;
background: url('/images/login-bg.jpg') center / cover no-repeat; background: url('/images/login-bg.jpg') center / cover no-repeat;
transform: scale(1.02);
z-index: 0;
} }
.login-mask { .hero-shade {
position: absolute; position: absolute;
inset: 0; inset: 0;
background: background:
linear-gradient(120deg, rgba(15, 61, 102, 0.72) 0%, rgba(30, 96, 145, 0.55) 45%, rgba(56, 189, 248, 0.35) 100%); linear-gradient(180deg, rgba(8, 32, 54, 0.55) 0%, rgba(8, 32, 54, 0.18) 42%, rgba(8, 32, 54, 0.45) 100%);
z-index: 1;
} }
.login-card { .hero-body {
width: 440px;
max-width: 100%;
border-radius: 18px;
border: none;
position: relative; position: relative;
z-index: 2; z-index: 1;
background: rgba(255, 255, 255, 0.94); height: 100%;
backdrop-filter: blur(10px); display: flex;
box-shadow: 0 20px 50px rgba(15, 61, 102, 0.35); flex-direction: column;
padding: 40px 48px 28px;
} }
.header { .mark-name {
display: flex; font-size: 20px;
align-items: center; font-weight: 700;
gap: 14px; letter-spacing: 1px;
margin-bottom: 24px; line-height: 1.2;
} }
.logo {
width: 52px; .mark-sub {
height: 52px; margin-top: 3px;
border-radius: 14px;
background: linear-gradient(135deg, #1e6091, #38bdf8);
color: #fff;
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
}
.title {
margin: 0;
font-size: 18px;
color: #0f172a;
}
.subtitle {
margin: 6px 0 0;
font-size: 12px; font-size: 12px;
color: #64748b; color: rgba(255, 255, 255, 0.78);
} }
.hero-main {
flex: 1;
}
.hero-meta {
display: flex;
justify-content: space-between;
gap: 16px;
font-size: 12px;
color: rgba(255, 255, 255, 0.7);
}
.panel {
width: 440px;
flex-shrink: 0;
background: #fff;
display: flex;
flex-direction: column;
border-left: 1px solid #d7e1ea;
}
.panel-inner {
flex: 1;
display: flex;
flex-direction: column;
justify-content: center;
padding: 0 48px;
}
.panel-inner h2 {
margin: 0;
font-size: 22px;
font-weight: 650;
color: #16324a;
}
.panel-hint {
margin: 8px 0 28px;
font-size: 13px;
color: #6b7c8d;
}
.login-form :deep(.el-form-item) {
margin-bottom: 18px;
}
.login-form :deep(.el-form-item__label) {
color: #3d5163;
font-weight: 500;
padding-bottom: 4px;
}
.login-form :deep(.el-input__wrapper) {
border-radius: 2px;
box-shadow: 0 0 0 1px #c5d0db inset;
background: #fff;
}
.login-form :deep(.el-input__wrapper.is-focus) {
box-shadow: 0 0 0 1px #1464a5 inset;
}
.submit { .submit {
width: 100%; width: 100%;
height: 44px; height: 42px;
margin-top: 8px;
border: none;
border-radius: 2px;
background: #1464a5;
font-size: 15px; font-size: 15px;
letter-spacing: 2px;
} }
.accounts {
margin-top: 20px; .submit:hover,
padding-top: 16px; .submit:focus {
border-top: 1px dashed #e2e8f0; background: #0f548c;
} }
.accounts-title {
.demo {
margin-top: 28px;
}
.demo-toggle {
border: 0;
padding: 0;
background: none;
color: #6b7c8d;
font-size: 12px; font-size: 12px;
color: #94a3b8; cursor: pointer;
margin-bottom: 10px;
} }
.account-row {
.demo-toggle:hover {
color: #1464a5;
}
.demo-list {
margin-top: 8px;
display: flex; display: flex;
align-items: center; flex-direction: column;
gap: 10px; gap: 2px;
margin-bottom: 8px;
font-size: 13px;
} }
.account-row code {
flex: 1; .demo-list button {
background: #f8fafc; border: 0;
padding: 4px 8px; background: none;
border-radius: 6px; text-align: left;
color: #334155; padding: 6px 0;
color: #3d5163;
font-size: 12px;
font-family: Consolas, 'Courier New', monospace;
cursor: pointer;
}
.demo-list button:hover {
color: #1464a5;
}
.copyright {
margin: 0;
padding: 0 48px 24px;
font-size: 12px;
color: #9aa8b5;
}
@media (max-width: 900px) {
.login-page {
flex-direction: column;
}
.hero {
flex: none;
height: 210px;
}
.hero-body {
padding: 20px 24px 16px;
}
.hero-meta span:last-child {
display: none;
}
.panel {
width: 100%;
flex: 1;
border-left: none;
}
.panel-inner,
.copyright {
padding-left: 24px;
padding-right: 24px;
}
} }
</style> </style>