Files
Shixun/frontend/src/views/Login.vue
T
2026-08-18 11:33:28 +08:00

344 lines
6.9 KiB
Vue
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<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 class="hero-main" />
<div class="hero-meta">
<span>{{ nowText }}</span>
<span>内部系统 · 请勿转借账号</span>
</div>
</div>
</section>
<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"
>
登录
</el-button>
</el-form>
<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>
<p class="copyright">信息科 HIS v1.0</p>
</section>
</div>
</template>
<script setup>
import { onBeforeUnmount, onMounted, reactive, ref } from 'vue'
import { useRouter, useRoute } from 'vue-router'
import { ElMessage } from 'element-plus'
import { useUserStore } from '@/stores/user'
const router = useRouter()
const route = useRoute()
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
}
const onSubmit = async () => {
await formRef.value.validate()
loading.value = true
try {
await userStore.login(form)
ElMessage.success(`欢迎回来,${userStore.displayName}`)
const redirect = route.query.redirect || '/dashboard'
await router.replace(String(redirect))
} catch {
/* 拦截器已提示 */
} finally {
loading.value = false
}
}
</script>
<style scoped>
.login-page {
height: 100vh;
display: flex;
background: #f3f6f9;
}
.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;
}
.hero-shade {
position: absolute;
inset: 0;
background:
linear-gradient(180deg, rgba(8, 32, 54, 0.55) 0%, rgba(8, 32, 54, 0.18) 42%, rgba(8, 32, 54, 0.45) 100%);
}
.hero-body {
position: relative;
z-index: 1;
height: 100%;
display: flex;
flex-direction: column;
padding: 40px 48px 28px;
}
.mark-name {
font-size: 20px;
font-weight: 700;
letter-spacing: 1px;
line-height: 1.2;
}
.mark-sub {
margin-top: 3px;
font-size: 12px;
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: 42px;
margin-top: 8px;
border: none;
border-radius: 2px;
background: #1464a5;
font-size: 15px;
}
.submit:hover,
.submit:focus {
background: #0f548c;
}
.demo {
margin-top: 28px;
}
.demo-toggle {
border: 0;
padding: 0;
background: none;
color: #6b7c8d;
font-size: 12px;
cursor: pointer;
}
.demo-toggle:hover {
color: #1464a5;
}
.demo-list {
margin-top: 8px;
display: flex;
flex-direction: column;
gap: 2px;
}
.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>