344 lines
6.9 KiB
Vue
344 lines
6.9 KiB
Vue
<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>
|