lis8.0-vue3/src/views/login.vue
2025-11-26 20:01:51 +08:00

422 lines
12 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<div class="login">
<el-form ref="loginRef" class="login-form">
<div class="login-header">
<div class="login-logo">
<img src="@/assets/images/logo_new.png" alt="系统Logo" />
</div>
<div class="login-split"></div>
<div class="login-title">
<h1>{{ oem.appname}}</h1>
</div>
</div>
<!-- Tab切换容器 -->
<el-tabs v-model="activeName" type="card" @tab-click="handleClick" class="login-tabs">
<!-- 工号登录 Tab -->
<el-tab-pane label="账号密码登录" name="first">
<el-form-item label="医疗机构:" prop="loginYLJG"class="custom-select">
<el-select
v-model="loginForm.loginParam.loginYLJG"
placeholder="请选择医疗机构"
style="width: 100%"
>
<template #prefix>
<svg-icon icon-class="international" class="el-input__icon input-icon" />
</template>
<el-option
v-for="item in selectOptions"
:key="item.id"
:label="item.name"
:value="item.id"
></el-option>
</el-select>
</el-form-item>
<el-form-item label="用户代号:" prop="username">
<el-input v-model="loginForm.username" type="text" size="large" auto-complete="off" placeholder="账号">
<template #prefix>
<svg-icon icon-class="user" class="el-input__icon input-icon" />
</template>
</el-input>
</el-form-item>
<el-form-item label="登录密码:" prop="password">
<el-input v-model="loginForm.password" type="password" size="large" auto-complete="off" placeholder="密码"
@keyup.enter="handleLogin">
<template #prefix>
<svg-icon icon-class="password" class="el-input__icon input-icon" />
</template>
</el-input>
</el-form-item>
<!-- 修复:移除空的el-form-item,验证码关闭时不渲染任何内容 -->
<el-form-item prop="code" v-if="!captchaEnabled" size="large">
</el-form-item>
<el-form-item prop="code" label="验 证 码 :" v-if="captchaEnabled">
<el-input
v-model="loginForm.code"
size="large"
auto-complete="off"
placeholder="验证码"
style="width: 53%"
@keyup.enter="handleLogin"
>
<template #prefix>
<svg-icon icon-class="validCode" class="el-input__icon input-icon" />
</template>
</el-input>
<div class="login-code">
<img :src="codeUrl" @click="getCode" class="login-code-img" />
</div>
</el-form-item>
<el-form-item style="width: 100%;">
<el-button :loading="loading" size="large" type="primary" class="login-btn" style="width: 100%;" @click.prevent="handleLogin">
<span v-if="!loading">登 录</span>
<span v-else>登 录 中...</span>
</el-button>
<div style="float: right;" v-if="register">
<router-link class="link-type" :to="'/register'">立即注册</router-link>
</div>
</el-form-item>
</el-tab-pane>
<el-tab-pane label="扫码登录" name="second">
<div class="tab-placeholder">
<el-icon class="placeholder-icon"><Sms /></el-icon>
<p>正在加载二维码...</p>
<el-button type="success" icon="wechat" class="wechat-login-btn">刷新二维码</el-button>
</div>
</el-tab-pane>
<el-tab-pane label="UKEY登录" name="third">
<div class="tab-placeholder">
<el-icon class="placeholder-icon"><Wechat /></el-icon>
<p>UKEY登录功能开发中...</p>
</div>
</el-tab-pane>
</el-tabs>
</el-form>
<div class="el-login-footer">
<span>版权所有(R)2022-2026 {{ oem.compay}} 电话:{{oem.phone}} E-mail:{{oem.Email}} version:{{oem.version}}</span>
</div>
</div>
</template>
<script setup>
import { ref, watch, getCurrentInstance, onMounted } from 'vue';
import { useRoute, useRouter } from 'vue-router';
import { useFingerprint } from '@/utils/useFingerprint';
import { getCodeImg, getInfo, getLocalconfig } from "@/api/login";
import Cookies from "js-cookie";
import { encrypt, decrypt } from "@/utils/jsencrypt";
import useUserStore from '@/store/modules/user';
// 初始化响应式变量
const activeName = ref('first');
const userStore = useUserStore();
const route = useRoute();
const router = useRouter();
const oem = ref({
appname: "实验室信息管理系统",
phone: "",
compay: "",
version: "",
Email: ""
});
const loginRef = ref(null); // 修复:直接用ref获取表单引用
const loginForm = ref({
username: "admin",
password: "",
rememberMe: false,
code: "",
uuid: "",
loginParam:{loginYLJG:"",localid:""}
});
const selectOptions = ref([{ id: '1', name: '总院' }]);
const loginRules = ref({ // 修复:改为ref响应式,方便动态修改
username: [{ required: true, trigger: "blur", message: "请输入您的账号" }],
// password: [{ required: true, trigger: "blur", message: "请输入您的密码" }],
hospid: [{ required: true, message: '请选择医疗机构', trigger: 'change' }],
code: [{ required: true, trigger: "change", message: "请输入验证码" }]
});
const codeUrl = ref("");
const loading = ref(false);
const captchaEnabled = ref(true);
const register = ref(false);
const redirect = ref(undefined);
// 路由监听(优化版)
watch(
() => route.query.redirect,
(newRedirect) => {
if (redirect.value !== newRedirect) {
redirect.value = newRedirect || '';
}
},
{ immediate: true, deep: false }
);
// 登录方法(修复proxy.$refs问题)
const handleLogin = async () => {
loginForm.value.loginParam.loginYLJG
loading.value = true;
Cookies.set("czlis_username", loginForm.value.username, { expires: 30 });
Cookies.set("czlis_loginYLJG", loginForm.value.loginParam.loginYLJG, {expires: 30});
// 调用action的登录方法
userStore.login(loginForm.value).then(() => {
const query = route.query;
const otherQueryParams = Object.keys(query).reduce((acc, cur) => {
if (cur !== "redirect") {
acc[cur] = query[cur];
}
return acc;
}, {});
const targetPath = redirect.value || "/";
if (route.path !== targetPath) {
router.push({ path: targetPath, query: otherQueryParams });
}
}).catch(() => {
loading.value = false;
// 重新获取验证码
if (captchaEnabled.value) {
getCode();
}
});
};
// 获取验证码
const getCode = () => {
getCodeImg().then(res => {
captchaEnabled.value = res.captchaEnabled ?? true;
if (captchaEnabled.value) {
codeUrl.value = "data:image/gif;base64," + res.img;
loginForm.value.uuid = res.uuid;
}
// 验证码关闭时移除校验规则,避免报错
if (!captchaEnabled.value) {
loginRules.value.code = [];
} else {
loginRules.value.code = [{required: true, trigger: "change", message: "请输入验证码"}];
}
});
};
// 从Cookie获取账号密码
const getCookie = () => {
const cookieUsername = Cookies.get("czlis_username");
const cookieloginYLJG = Cookies.get("czlis_loginYLJG");
//console.log('cookieloginYLJG', cookieloginYLJG);
if (cookieUsername !== undefined && cookieUsername !== null&& cookieUsername !=="") {
loginForm.value.username = cookieUsername;
}
if (cookieloginYLJG !== undefined && cookieloginYLJG !== null&& cookieloginYLJG !=="") {
loginForm.value.loginParam.loginYLJG = cookieloginYLJG;
}
//console.log('loginForm', loginForm);
};
// 获取本地配置
const Localconfig = (localid) => {
getLocalconfig({localid}).then(res => {
if (res) {
const {localconfig, hosplist, oem: oemData} = res;
// 修复:赋值oem数据,显示动态标题
if (oemData) oem.value = oemData;
// console.log('oemData', oemData);
// console.log('oem', oem.value);
// 修复:判重后更新下拉选项,避免递归
if (JSON.stringify(selectOptions.value) !== JSON.stringify(hosplist)) {
selectOptions.value = hosplist || [{id: '1', name: '总院'}];
// 判重后赋值,避免重复修改触发更新
if (!selectOptions.value.some(item => item.id === loginForm.value.hospid)) {
loginForm.value.loginParam.loginYLJG = selectOptions.value[0]?.id || '1';
}
getCookie();
}
}
}).catch(err => {
console.error("获取本地配置失败:", err);
});
};
// 获取指纹并加载配置
const {getFingerprint} = useFingerprint();
const refreshFingerprint = async () => {
try {
const res = await getFingerprint();
loginForm.value.loginParam.localid=res.visitorId
Localconfig(res.visitorId);
} catch (err) {
console.error("获取指纹失败:", err);
Localconfig("default"); // 兜底使用默认ID
}
};
// Tab切换事件(移除activeName手动赋值,避免递归)
const handleClick = (tab) => {
console.log('当前Tab:', tab.name);
// 仅在切回账号密码登录时刷新验证码
if (tab.name === 'first' && captchaEnabled.value) {
getCode();
}
};
// 页面挂载时初始化
onMounted(() => {
refreshFingerprint();
getCode();
});
</script>
<style lang='scss' scoped>
.login {
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
background-image: url("../assets/images/login-background.jpg");
background-size: cover;
}
.login-form {
background: rgba(255, 255, 255, 0.4);
backdrop-filter: blur(10px);
-webkit-backdrop-filter: blur(10px);
border: 1px solid rgba(255, 255, 255, 0.9);
border-radius: 12px;
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1);
width: 480px;
padding: 35px 35px 5px 35px;
.el-input {
height: 40px;
input {
height: 40px;
}
}
.input-icon {
height: 39px;
width: 14px;
margin-left: 0px;
}
}
.login-code {
width: 33%;
height: 40px;
float: right;
img {
cursor: pointer;
vertical-align: middle;
height: 100%;
width: 100%;
object-fit: cover;
}
}
.el-login-footer {
height: 40px;
line-height: 40px;
position: fixed;
bottom: 0;
width: 100%;
text-align: center;
color: #fff;
font-family: Arial;
font-size: 12px;
letter-spacing: 1px;
}
/* Logo和标题样式 */
.login-header {
display: flex;
align-items: center;
margin-bottom: 30px;
}
.login-logo img {
height: 40px;
width: auto;
}
.login-split {
width: 1px;
height: 30px;
background-color: #ccc;
margin: 0 15px;
}
.login-title h1 {
margin: 0;
font-size: 24px;
color: #1989fa;
font-weight: 500;
}
/* Tab容器样式(修复冲突) */
.login-tabs {
width: 100%;
height: 350px;
overflow: hidden;
border-radius: 8px;
backdrop-filter: blur(10px);
-webkit-backdrop-filter: blur(10px);
border: 1px solid rgba(255, 255, 255, 0.1);
padding: 1px 10px 10px;
background: transparent;
:deep(.el-tabs__header) {
background: transparent;
margin: 0;
}
:deep(.el-tabs__nav-prev),
:deep(.el-tabs__nav-next) {
display: none !important; // 隐藏Tab左右箭头
}
:deep(.el-tabs__nav-wrap) {
overflow: visible !important;
}
}
.custom-select {
margin-top: 20px; /* 可根据需求调整数值 */
}
/* Tab占位样式 */
.tab-placeholder {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 40px 0;
color: #666;
height: calc(100% - 40px); // 适配Tab容器高度
.placeholder-icon {
font-size: 48px;
margin-bottom: 20px;
color: #ccc;
}
.wechat-login-btn {
margin-top: 20px;
width: 200px;
}
}
/* 隐藏必填项星号 */
:deep(.el-form-item.is-required:not(.is-no-asterisk) .el-form-item__label-wrap .el-form-item__asterisk) {
display: none !important;
}
</style>