优化登录页

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>
<div class="login-wrapper">
<div class="login-bg" />
<div class="login-mask" />
<el-card class="login-card" shadow="always">
<div class="header">
<div class="logo">
<el-icon :size="28"><OfficeBuilding /></el-icon>
<div class="login-page">
<section class="hero" aria-hidden="false">
<div class="hero-photo" />
<div class="hero-shade" />
<div class="hero-body">
<div class="mark">
<div class="mark-name">智慧医院</div>
<div class="mark-sub">医院信息管理系统</div>
</div>
<div>
<h2 class="title">智慧医院管理系统</h2>
<p class="subtitle">患者 · 影像 · 病历 · 预约一体化管理</p>
<div class="hero-main" />
<div class="hero-meta">
<span>{{ nowText }}</span>
<span>内部系统 · 请勿转借账号</span>
</div>
</div>
</section>
<el-form ref="formRef" :model="form" :rules="rules" size="large" @submit.prevent="onSubmit">
<el-form-item prop="username">
<el-input v-model="form.username" placeholder="用户名" clearable>
<template #prefix><el-icon><User /></el-icon></template>
</el-input>
</el-form-item>
<el-form-item prop="password">
<el-input
v-model="form.password"
type="password"
placeholder="密码"
show-password
@keyup.enter="onSubmit"
<section class="panel">
<div class="panel-inner">
<h2>登录</h2>
<p class="panel-hint">输入用户名和密码进入系统</p>
<el-form
ref="formRef"
class="login-form"
:model="form"
:rules="rules"
size="large"
label-position="top"
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-form-item>
<el-button type="primary" class="submit" :loading="loading" @click="onSubmit">
登 录
</el-button>
</el-form>
登录
</el-button>
</el-form>
<div class="accounts">
<div class="accounts-title">测试账号</div>
<div class="account-row">
<el-tag type="danger" effect="plain">管理员</el-tag>
<code>admin / admin123</code>
<el-button link type="primary" @click="fill('admin', 'admin123')">填入</el-button>
</div>
<div class="account-row">
<el-tag type="success" effect="plain">医生</el-tag>
<code>doctor1 / pass123</code>
<el-button link type="primary" @click="fill('doctor1', 'pass123')">填入</el-button>
<div class="demo">
<button type="button" class="demo-toggle" @click="showDemo = !showDemo">
{{ showDemo ? '收起演示账号' : '实训演示账号' }}
</button>
<div v-if="showDemo" class="demo-list">
<button type="button" @click="fill('admin', 'admin123')">管理员 admin / admin123</button>
<button type="button" @click="fill('doctor1', 'pass123')">医生 doctor1 / pass123</button>
<button type="button" @click="fill('radio1', 'radio123')">放射科 radio1 / radio123</button>
</div>
</div>
</div>
</el-card>
<p class="copyright">信息科 HIS v1.0</p>
</section>
</div>
</template>
<script setup>
import { ref, reactive } from 'vue'
import { onBeforeUnmount, onMounted, reactive, ref } from 'vue'
import { useRouter, useRoute } from 'vue-router'
import { ElMessage } from 'element-plus'
import { User, Lock, OfficeBuilding } from '@element-plus/icons-vue'
import { useUserStore } from '@/stores/user'
const router = useRouter()
@@ -65,12 +90,32 @@ const userStore = useUserStore()
const formRef = ref()
const loading = ref(false)
const showDemo = ref(false)
const nowText = ref('')
const form = reactive({ username: '', password: '' })
const rules = {
username: [{ 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) => {
form.username = u
form.password = p
@@ -93,100 +138,206 @@ const onSubmit = async () => {
</script>
<style scoped>
.login-wrapper {
position: relative;
min-height: 100vh;
.login-page {
height: 100vh;
overflow: hidden;
display: flex;
align-items: center;
justify-content: center;
padding: 24px;
background: #f3f6f9;
}
.login-bg {
.hero {
position: relative;
flex: 1.15;
min-width: 0;
overflow: hidden;
color: #fff;
}
.hero-photo {
position: absolute;
inset: 0;
background: url('/images/login-bg.jpg') center / cover no-repeat;
transform: scale(1.02);
z-index: 0;
}
.login-mask {
.hero-shade {
position: absolute;
inset: 0;
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%);
z-index: 1;
linear-gradient(180deg, rgba(8, 32, 54, 0.55) 0%, rgba(8, 32, 54, 0.18) 42%, rgba(8, 32, 54, 0.45) 100%);
}
.login-card {
width: 440px;
max-width: 100%;
border-radius: 18px;
border: none;
.hero-body {
position: relative;
z-index: 2;
background: rgba(255, 255, 255, 0.94);
backdrop-filter: blur(10px);
box-shadow: 0 20px 50px rgba(15, 61, 102, 0.35);
z-index: 1;
height: 100%;
display: flex;
flex-direction: column;
padding: 40px 48px 28px;
}
.header {
display: flex;
align-items: center;
gap: 14px;
margin-bottom: 24px;
.mark-name {
font-size: 20px;
font-weight: 700;
letter-spacing: 1px;
line-height: 1.2;
}
.logo {
width: 52px;
height: 52px;
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;
.mark-sub {
margin-top: 3px;
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 {
width: 100%;
height: 44px;
height: 42px;
margin-top: 8px;
border: none;
border-radius: 2px;
background: #1464a5;
font-size: 15px;
letter-spacing: 2px;
}
.accounts {
margin-top: 20px;
padding-top: 16px;
border-top: 1px dashed #e2e8f0;
.submit:hover,
.submit:focus {
background: #0f548c;
}
.accounts-title {
.demo {
margin-top: 28px;
}
.demo-toggle {
border: 0;
padding: 0;
background: none;
color: #6b7c8d;
font-size: 12px;
color: #94a3b8;
margin-bottom: 10px;
cursor: pointer;
}
.account-row {
.demo-toggle:hover {
color: #1464a5;
}
.demo-list {
margin-top: 8px;
display: flex;
align-items: center;
gap: 10px;
margin-bottom: 8px;
font-size: 13px;
flex-direction: column;
gap: 2px;
}
.account-row code {
flex: 1;
background: #f8fafc;
padding: 4px 8px;
border-radius: 6px;
color: #334155;
.demo-list button {
border: 0;
background: none;
text-align: left;
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>