登录
This commit is contained in:
parent
66c8dae03d
commit
0cdc3bdee2
@ -17,6 +17,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@element-plus/icons-vue": "2.3.2",
|
||||
"@fingerprintjs/fingerprintjs": "^5.1.0",
|
||||
"@vueup/vue-quill": "1.2.0",
|
||||
"@vueuse/core": "14.1.0",
|
||||
"axios": "1.13.2",
|
||||
@ -32,6 +33,7 @@
|
||||
"mitt": "^3.0.1",
|
||||
"nprogress": "0.2.0",
|
||||
"pinia": "3.0.4",
|
||||
"pinia-plugin-persistedstate": "^4.7.1",
|
||||
"pinyin-pro": "^3.28.0",
|
||||
"sortablejs": "^1.15.7",
|
||||
"splitpanes": "4.0.4",
|
||||
|
||||
@ -32,6 +32,18 @@ export function register(data: SysRegister): Promise<AjaxResult> {
|
||||
})
|
||||
}
|
||||
|
||||
export function getLocalconfig(data: object) {
|
||||
return request({
|
||||
url: '/getLocalconfig',
|
||||
headers: {
|
||||
isToken: false
|
||||
},
|
||||
method: 'get',
|
||||
params: data,
|
||||
timeout: 20000
|
||||
})
|
||||
}
|
||||
|
||||
// 获取用户详细信息
|
||||
export function getInfo(): Promise<UserInfoResult> {
|
||||
return request({
|
||||
|
||||
BIN
src/assets/images/logo_new.png
Normal file
BIN
src/assets/images/logo_new.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 12 KiB |
@ -1,3 +1,5 @@
|
||||
import piniaPluginPersistedstate from 'pinia-plugin-persistedstate'
|
||||
const store = createPinia()
|
||||
store.use(piniaPluginPersistedstate) // 注册持久化插件
|
||||
|
||||
export default store
|
||||
|
||||
37
src/store/modules/commonStore.ts
Normal file
37
src/store/modules/commonStore.ts
Normal file
@ -0,0 +1,37 @@
|
||||
//公共状态管理
|
||||
import { defineStore } from 'pinia'
|
||||
// 定义配置对象接口
|
||||
interface DefaultConfig {
|
||||
defaultinstr?: string //默认仪器
|
||||
lisgroupid?: string //默认分组id
|
||||
[key: string]: any // 允许其他属性
|
||||
}
|
||||
export const useCommonStore = defineStore('commonStore', {
|
||||
state: () => ({
|
||||
lxsrs: [] as string[], // 存储项目模板名称
|
||||
fingerprint: '',// 存储指纹信息
|
||||
defaultConfig: {} as DefaultConfig, // 存储默认配置
|
||||
}),
|
||||
actions: {
|
||||
setLxsrList(list: any[]) {
|
||||
this.lxsrs = list
|
||||
},
|
||||
|
||||
setFingerprint(fingerprint: string) {
|
||||
this.fingerprint = fingerprint
|
||||
},
|
||||
|
||||
setDefaultConfig(config: DefaultConfig) {
|
||||
this.defaultConfig = config
|
||||
},
|
||||
},
|
||||
// 持久化配置
|
||||
persist: {
|
||||
// 指定要持久化的状态字段
|
||||
pick: ['fingerprint', 'defaultConfig'],
|
||||
// 可选配置:自定义存储键名(默认是store的id)
|
||||
// key: 'common_store',
|
||||
// 可选配置:指定存储位置(默认是localStorage 也可以用sessionStorage)
|
||||
storage: localStorage,
|
||||
}
|
||||
})
|
||||
45
src/utils/useFingerprint.ts
Normal file
45
src/utils/useFingerprint.ts
Normal file
@ -0,0 +1,45 @@
|
||||
|
||||
import FingerprintJS from '@fingerprintjs/fingerprintjs';
|
||||
|
||||
export function useFingerprint() {
|
||||
const fingerprint = ref('');
|
||||
const loading = ref(true);
|
||||
const error = ref(null);
|
||||
// 初始化FingerprintJS并获取指纹
|
||||
const getFingerprint = async () => {
|
||||
try {
|
||||
loading.value = true;
|
||||
// 初始化代理
|
||||
const fp = await FingerprintJS.load({
|
||||
// 可选配置
|
||||
monitoring: false,
|
||||
// 扩展结果数据
|
||||
//@ts-ignore
|
||||
extendedResult: true
|
||||
});
|
||||
|
||||
// 获取指纹信息
|
||||
const result = await fp.get();
|
||||
|
||||
// 指纹标识符
|
||||
fingerprint.value = result.visitorId;
|
||||
|
||||
// 可以获取更多详细信息
|
||||
console.log('指纹详细信息:', result);
|
||||
return result;
|
||||
} catch (err) {
|
||||
error.value = err;
|
||||
console.error('获取指纹失败:', err);
|
||||
return null;
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
fingerprint,
|
||||
loading,
|
||||
error,
|
||||
getFingerprint
|
||||
};
|
||||
}
|
||||
@ -109,7 +109,6 @@ const lastJgbz = ref();
|
||||
watch(() => props.labPat, (newVal) => {
|
||||
lastValue.value = JSON.stringify(newVal);
|
||||
lastJgbz.value = newVal.zt;
|
||||
console.log('newVal==>', newVal);
|
||||
})
|
||||
|
||||
|
||||
|
||||
@ -1,117 +0,0 @@
|
||||
<!-- MultiConditionSearch.vue -->
|
||||
<template>
|
||||
<el-popover :visible="selectVisible" placement="bottom" :width="'25vw'" @after-enter="initClickOutside">
|
||||
<el-form :model="searchForm" label-width="100px">
|
||||
<el-form-item label="姓名:" prop="brxm">
|
||||
<el-input v-model="searchForm.brxm" clearable />
|
||||
</el-form-item>
|
||||
<el-form-item label="条码号:" prop="sqh">
|
||||
<el-input v-model="searchForm.sqh" clearable />
|
||||
</el-form-item>
|
||||
<el-form-item label="状态:" prop="zt">
|
||||
<SelectTable v-model:data="searchForm.zt" :tableData="props.ztData" clearable />
|
||||
</el-form-item>
|
||||
<el-form-item label="登记时间:" prop="begdate">
|
||||
<div class="radio_box">
|
||||
<el-radio-group v-model="searchForm.days" @change="handleSearch">
|
||||
<el-radio :value="7">近7天</el-radio>
|
||||
<el-radio :value="0">此日
|
||||
<el-icon>
|
||||
<Right />
|
||||
</el-icon>
|
||||
</el-radio>
|
||||
</el-radio-group>
|
||||
<el-date-picker v-model="searchForm.begdate" type="date" value-format="YYYY-MM-DD" style="width: 10rem;" />
|
||||
</div>
|
||||
</el-form-item>
|
||||
|
||||
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="handleSearch">查询</el-button>
|
||||
<el-button @click="handleReset">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<template #reference>
|
||||
<el-input v-model="selectShowValue" @clear="handleReset" @click="selectVisible = !selectVisible" readonly
|
||||
placeholder="查询条件" size="small" :style="{ width: props.width }" suffix-icon="ArrowDown"
|
||||
:clearable="props.clearable" />
|
||||
</template>
|
||||
</el-popover>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import dayjs from 'dayjs'
|
||||
|
||||
const props = defineProps({
|
||||
width: { type: String, default: '200px' },
|
||||
clearable: { type: Boolean, default: true },
|
||||
ztData: { type: Array, default: () => [] }
|
||||
})
|
||||
const selectShowValue = ref('')
|
||||
const selectVisible = ref(false)
|
||||
|
||||
// 查询表单数据
|
||||
const searchForm = reactive({
|
||||
brxm: '',
|
||||
sqh: '',
|
||||
zt: '',
|
||||
begdate: '',
|
||||
enddate: '',
|
||||
days: 0,
|
||||
})
|
||||
|
||||
// 查询事件
|
||||
const handleSearch = () => {
|
||||
searchForm.begdate = dayjs(searchForm.begdate).format('YYYY-MM-DD') + ' 00:00:00'
|
||||
searchForm.enddate = dayjs(searchForm.begdate).format('YYYY-MM-DD') + ' 23:59:59'
|
||||
emit('search', { ...searchForm })
|
||||
selectVisible.value = false
|
||||
}
|
||||
|
||||
// 重置事件
|
||||
const handleReset = () => {
|
||||
searchForm.brxm = ''
|
||||
searchForm.sqh = ''
|
||||
searchForm.zt = ''
|
||||
searchForm.begdate = dayjs().format('YYYY-MM-DD')
|
||||
searchForm.enddate = ''
|
||||
searchForm.days = 0
|
||||
handleSearch() // 重置后自动执行查询,刷新数据
|
||||
selectVisible.value = false
|
||||
}
|
||||
|
||||
// 定义事件
|
||||
const emit = defineEmits(['search',])
|
||||
|
||||
// 防止更多按钮的事件与之冲突
|
||||
const initClickOutside = () => {
|
||||
document.getElementById('app')!.addEventListener('click', () => {
|
||||
selectVisible.value = false
|
||||
}, { once: true })
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
searchForm.begdate = dayjs().format('YYYY-MM-DD')
|
||||
// queryParams.value.enddate = dayjs().format('YYYY-MM-DD')
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.multi-condition-search {
|
||||
padding: 16px;
|
||||
background: #f5f7fa;
|
||||
border-radius: 4px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.radio_box {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin: .125rem 0;
|
||||
|
||||
.el-radio {
|
||||
margin-right: .3125rem;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@ -55,7 +55,31 @@
|
||||
</el-col>
|
||||
<el-col :span="8" style="height: 100%;">
|
||||
<div class="box-shadow">
|
||||
<MultiConditionSearch @search="onSearch" :ztData="lis_req_type" />
|
||||
<el-form :model="searchForm" inline size="small">
|
||||
<el-form-item label="姓名:" prop="brxm">
|
||||
<el-input v-model="searchForm.brxm" clearable style="width: 9rem;" @change="handleQuery" />
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="状态:" prop="zt">
|
||||
<SelectTable v-model:data="searchForm.zt" :tableData="lis_req_type" clearable style="width: 9rem;"
|
||||
size="small" @getDataValue="handleQuery" />
|
||||
</el-form-item>
|
||||
<el-form-item label="" prop="begdate">
|
||||
<div class="radio_box">
|
||||
<el-radio-group v-model="searchForm.days" @change="handleQuery">
|
||||
<el-radio :value="3">近3天</el-radio>
|
||||
<el-radio :value="7">近7天</el-radio>
|
||||
<el-radio :value="0">此日
|
||||
<el-icon>
|
||||
<Right />
|
||||
</el-icon>
|
||||
</el-radio>
|
||||
</el-radio-group>
|
||||
<el-date-picker v-model="searchForm.begdate" type="date" value-format="YYYY-MM-DD" style="width: 10rem;"
|
||||
@change="handleQuery" />
|
||||
</div>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<div class="table-box">
|
||||
<CustomVxeTable :table-data="labPatList" :loading="loading" :columns="tableColumns"
|
||||
@current-change="handleRowClick" size="small"
|
||||
@ -97,7 +121,6 @@ import { queryXmInfoList, saveReqInfo, queryPatList, queryPatDetail } from '@/ap
|
||||
import dayjs from 'dayjs'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { getFirstLetter } from '@/utils/pinyin';
|
||||
import MultiConditionSearch from './components/multiConditionSearch.vue'
|
||||
|
||||
const { proxy } = getCurrentInstance();
|
||||
const { lis_req_type } = proxy.useDict("lis_req_type");
|
||||
@ -109,7 +132,7 @@ const searchKey = ref('')
|
||||
const loading = ref(false)
|
||||
const tableRef = useTemplateRef('tableRef')
|
||||
const labPatList = ref([])
|
||||
|
||||
const searchForm = ref({})
|
||||
const tableColumns = ref([
|
||||
{ field: 'brxm', title: '姓名', width: 40, align: 'center', resizable: true, },
|
||||
{ field: 'zt', title: '状态', width: 40, align: 'center', slotName: 'zt', resizable: true, },
|
||||
@ -133,8 +156,10 @@ const handleRowClick = (row) => {
|
||||
})
|
||||
}
|
||||
|
||||
const onSearch = (params) => {
|
||||
queryPatList(params).then(res => {
|
||||
const handleQuery = () => {
|
||||
searchForm.value.begdate = dayjs(searchForm.value.begdate).format('YYYY-MM-DD') + ' 00:00:00'
|
||||
searchForm.value.enddate = dayjs(searchForm.value.begdate).format('YYYY-MM-DD') + ' 23:59:59'
|
||||
queryPatList(searchForm.value).then(res => {
|
||||
labPatList.value = res.data
|
||||
if (!res.data.length) {
|
||||
labpat.value = {
|
||||
@ -189,10 +214,6 @@ const xmColumns = ref([
|
||||
{ field: 'yblx', title: '样本', align: 'center', resizable: true },
|
||||
])
|
||||
|
||||
const setbilRef = useTemplateRef('setbilRef')
|
||||
const BillingHandle = () => {
|
||||
setbilRef.value.open()
|
||||
}
|
||||
|
||||
const processedTableData = ref([])
|
||||
|
||||
@ -283,6 +304,7 @@ const formatDictzt = (value) => {
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
searchForm.value.begdate = dayjs().format('YYYY-MM-DD')
|
||||
getXmList()
|
||||
getDictData('PT', 'SX', 'AU', 'BT', 'DP', 'SRD', 'ZJLX')
|
||||
})
|
||||
@ -312,7 +334,7 @@ onMounted(() => {
|
||||
|
||||
.table-box {
|
||||
margin-top: 2px;
|
||||
height: calc(100% - 30px);
|
||||
height: calc(100% - 65px);
|
||||
}
|
||||
|
||||
.el-form-item {
|
||||
@ -352,4 +374,14 @@ onMounted(() => {
|
||||
color: #000;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.radio_box {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin: .125rem 0;
|
||||
|
||||
.el-radio {
|
||||
margin-right: .3125rem;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@ -1,219 +1,327 @@
|
||||
<template>
|
||||
<div class="login">
|
||||
<el-form ref="loginRef" :model="loginForm" :rules="loginRules" class="login-form">
|
||||
<h3 class="title">{{ title }}</h3>
|
||||
<el-form-item 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 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 prop="code" v-if="captchaEnabled">
|
||||
<el-input
|
||||
v-model="loginForm.code"
|
||||
size="large"
|
||||
auto-complete="off"
|
||||
placeholder="验证码"
|
||||
style="width: 63%"
|
||||
@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"/>
|
||||
<!-- <FloatingLines :enabled-waves="['top', 'middle', 'bottom']" :line-count="[10, 15, 20]" :line-distance="[8, 6, 4]"
|
||||
:bend-radius="5.0" :bend-strength="-0.5" :interactive="true" :parallax="true" /> -->
|
||||
<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>
|
||||
</el-form-item>
|
||||
<el-checkbox v-model="loginForm.rememberMe" style="margin:0px 0px 25px 0px;">记住密码</el-checkbox>
|
||||
<el-form-item style="width:100%;">
|
||||
<el-button
|
||||
:loading="loading"
|
||||
size="large"
|
||||
type="primary"
|
||||
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 class="login-split"></div>
|
||||
<div class="login-title">
|
||||
<h1>{{ oem.appname }}</h1>
|
||||
</div>
|
||||
</el-form-item>
|
||||
</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>{{ footerContent }}</span>
|
||||
<span>版权所有(R)2022-2026 {{ oem.compay }} 电话:{{ oem.phone }} E-mail:{{ oem.Email }} version:{{ oem.version }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { getCodeImg } from "@/api/login"
|
||||
import Cookies from "js-cookie"
|
||||
import { encrypt, decrypt } from "@/utils/jsencrypt"
|
||||
import useUserStore from '@/store/modules/user'
|
||||
import defaultSettings from '@/settings'
|
||||
import type { CaptchaInfoResult } from '@/types/api/login'
|
||||
import type { LoginForm } from '@/types/api/login'
|
||||
<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 title = import.meta.env.VITE_APP_TITLE
|
||||
const footerContent = defaultSettings.footerContent
|
||||
const userStore = useUserStore()
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
const { proxy } = getCurrentInstance()
|
||||
|
||||
const loginForm = ref<LoginForm>({
|
||||
import { useCommonStore } from '@/store/modules/commonStore'
|
||||
const mbStore = useCommonStore();
|
||||
// 初始化响应式变量
|
||||
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: "admin123",
|
||||
password: "",
|
||||
rememberMe: false,
|
||||
code: "",
|
||||
uuid: ""
|
||||
})
|
||||
|
||||
const loginRules = {
|
||||
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: "请输入您的密码" }],
|
||||
// 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);
|
||||
|
||||
const codeUrl = ref("")
|
||||
const loading = ref(false)
|
||||
// 验证码开关
|
||||
const captchaEnabled = ref(true)
|
||||
// 注册开关
|
||||
const register = ref(false)
|
||||
const redirect = ref<string | undefined>(undefined)
|
||||
// 路由监听(优化版)
|
||||
watch(
|
||||
() => route.query.redirect,
|
||||
(newRedirect) => {
|
||||
if (redirect.value !== newRedirect) {
|
||||
redirect.value = newRedirect || '';
|
||||
}
|
||||
},
|
||||
{ immediate: true, deep: false }
|
||||
);
|
||||
|
||||
watch(route, (newRoute: any) => {
|
||||
redirect.value = (newRoute.query && newRoute.query.redirect) as string | undefined
|
||||
}, { immediate: true })
|
||||
|
||||
function handleLogin(): void {
|
||||
proxy.$refs.loginRef.validate((valid: boolean) => {
|
||||
if (valid) {
|
||||
loading.value = true
|
||||
// 勾选了需要记住密码设置在 cookie 中设置记住用户名和密码
|
||||
if (loginForm.value.rememberMe) {
|
||||
Cookies.set("username", loginForm.value.username, { expires: 30 })
|
||||
Cookies.set("password", encrypt(loginForm.value.password), { expires: 30 })
|
||||
Cookies.set("rememberMe", loginForm.value.rememberMe, { expires: 30 })
|
||||
} else {
|
||||
// 否则移除
|
||||
Cookies.remove("username")
|
||||
Cookies.remove("password")
|
||||
Cookies.remove("rememberMe")
|
||||
// 登录方法(修复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];
|
||||
}
|
||||
// 调用action的登录方法
|
||||
userStore.login(loginForm.value).then(() => {
|
||||
const query = route.query
|
||||
const otherQueryParams = Object.keys(query).reduce((acc: Record<string, any>, cur) => {
|
||||
if (cur !== "redirect") {
|
||||
acc[cur] = query[cur]
|
||||
}
|
||||
return acc
|
||||
}, {})
|
||||
router.push({ path: redirect.value || "/", query: otherQueryParams })
|
||||
}).catch(() => {
|
||||
loading.value = false
|
||||
// 重新获取验证码
|
||||
if (captchaEnabled.value) {
|
||||
getCode()
|
||||
}
|
||||
})
|
||||
return acc;
|
||||
}, {});
|
||||
const targetPath = redirect.value || "/";
|
||||
if (route.path !== targetPath) {
|
||||
router.push({ path: targetPath, query: otherQueryParams });
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function getCode(): void {
|
||||
getCodeImg().then(res => {
|
||||
captchaEnabled.value = res.captchaEnabled === undefined ? true : res.captchaEnabled
|
||||
}).catch(() => {
|
||||
loading.value = false;
|
||||
// 重新获取验证码
|
||||
if (captchaEnabled.value) {
|
||||
codeUrl.value = "data:image/gif;base64," + res.img
|
||||
loginForm.value.uuid = res.uuid
|
||||
getCode();
|
||||
}
|
||||
})
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
function getCookie(): void {
|
||||
const username = Cookies.get("username")
|
||||
const password = Cookies.get("password")
|
||||
const rememberMe = Cookies.get("rememberMe")
|
||||
loginForm.value = {
|
||||
username: username === undefined ? loginForm.value.username : username,
|
||||
password: password === undefined ? loginForm.value.password : decrypt(password),
|
||||
rememberMe: rememberMe === undefined ? false : Boolean(rememberMe)
|
||||
// 获取验证码
|
||||
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);
|
||||
};
|
||||
|
||||
getCode()
|
||||
getCookie()
|
||||
// 获取本地配置
|
||||
const Localconfig = (localid) => {
|
||||
getLocalconfig({ localid }).then(res => {
|
||||
if (res) {
|
||||
const { localconfig, hosplist, oem: oemData } = res;
|
||||
mbStore.setDefaultConfig(localconfig)
|
||||
// 修复:赋值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: 100%;
|
||||
position: relative;
|
||||
height: 100vh;
|
||||
background-image: url("../assets/images/login-background.jpg");
|
||||
background-size: cover;
|
||||
}
|
||||
.title {
|
||||
margin: 0px auto 30px auto;
|
||||
text-align: center;
|
||||
color: #707070;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.login-form {
|
||||
border-radius: 6px;
|
||||
background: #ffffff;
|
||||
width: 400px;
|
||||
padding: 25px 25px 5px 25px;
|
||||
z-index: 1;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
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-tip {
|
||||
font-size: 13px;
|
||||
text-align: center;
|
||||
color: #bfbfbf;
|
||||
}
|
||||
|
||||
.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;
|
||||
@ -226,8 +334,89 @@ getCookie()
|
||||
font-size: 12px;
|
||||
letter-spacing: 1px;
|
||||
}
|
||||
.login-code-img {
|
||||
height: 40px;
|
||||
padding-left: 12px;
|
||||
|
||||
/* Logo和标题样式 */
|
||||
.login-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
</style>
|
||||
|
||||
.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>
|
||||
@ -48,7 +48,7 @@ export default defineConfig(({ mode, command }) => {
|
||||
proxy: {
|
||||
// https://cn.vitejs.dev/config/#server-proxy
|
||||
'/dev-api': {
|
||||
target: 'http://47.97.125.165:8905',
|
||||
target: 'http://47.97.125.165:8904',
|
||||
changeOrigin: true,
|
||||
rewrite: (p) => p.replace(/^\/dev-api/, '')
|
||||
},
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user