fjrcloud-community-ui/src/views/Login/Login.vue

475 lines
11 KiB
Vue

<template>
<div :class="prefixCls" class="login-container">
<div class="login-content">
<!-- 登录卡片 -->
<div class="login-card">
<!-- Logo 图标 -->
<div class="login-logo">
<div class="logo-icon">
<svg viewBox="0 0 1024 1024" width="60" height="60">
<path
d="M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm0 820c-205.4 0-372-166.6-372-372S306.6 140 512 140s372 166.6 372 372-166.6 372-372 372z"
fill="#1677ff"
/>
<path
d="M686.7 638.6L544.1 535.5V288c0-4.4-3.6-8-8-8H488c-4.4 0-8 3.6-8 8v275.4c0 2.6 1.2 5 3.3 6.5l165.4 120.6c3.6 2.6 8.6 1.8 11.2-1.7l28.6-39c2.6-3.7 1.8-8.7-1.8-11.2z"
fill="#1677ff"
/>
</svg>
</div>
</div>
<!-- 标题 -->
<h2 class="login-title">智慧小区云平台</h2>
<!-- 登录表单 -->
<el-form
ref="formLogin"
:model="loginData.loginForm"
:rules="LoginRules"
class="login-form"
size="large"
>
<el-form-item prop="username">
<el-input
v-model="loginData.loginForm.username"
placeholder="请输入用户名称"
:prefix-icon="iconAvatar"
@blur="handleUsernameBlur"
/>
</el-form-item>
<el-form-item prop="password">
<el-input
v-model="loginData.loginForm.password"
placeholder="请输入登录密码"
:prefix-icon="iconLock"
show-password
type="password"
@keyup.enter="getCode()"
@blur="handlePasswordBlur"
/>
</el-form-item>
<!-- 租户选择 -->
<el-form-item v-if="loginData.tenantEnable === 'true'" prop="tenantId">
<el-select
ref="tenantSelectRef"
v-model="loginData.loginForm.tenantId"
placeholder="请选择租户"
:prefix-icon="iconHouse"
style="width: 100%"
:disabled="tenantList.length === 0"
>
<el-option
v-for="tenant in tenantList"
:key="tenant.tenantId"
:label="tenant.tenantName"
:value="tenant.tenantId"
/>
</el-select>
</el-form-item>
<!-- 记住密码 -->
<div class="login-options">
<el-checkbox v-model="loginData.loginForm.rememberMe">
记住密码
</el-checkbox>
</div>
<!-- 登录按钮 -->
<el-form-item>
<el-button
:loading="loginLoading"
type="primary"
class="login-button"
@click="getCode()"
>
登录
</el-button>
</el-form-item>
</el-form>
</div>
<!-- 右侧插图 -->
<div class="login-illustration">
<img src="@/assets/svgs/login-box-bg.svg" alt="illustration" />
</div>
</div>
<!-- 主题和语言切换 -->
<div class="login-tools">
<ThemeSwitch />
<LocaleDropdown />
</div>
</div>
</template>
<script lang="ts" setup>
import { underlineToHump } from '@/utils'
import { useDesign } from '@/hooks/web/useDesign'
import { useAppStore } from '@/store/modules/app'
import { ThemeSwitch } from '@/layout/components/ThemeSwitch'
import { LocaleDropdown } from '@/layout/components/LocaleDropdown'
import { useIcon } from '@/hooks/web/useIcon'
import * as authUtil from '@/utils/auth'
import { usePermissionStore } from '@/store/modules/permission'
import * as LoginApi from '@/api/login'
import { ElLoading } from 'element-plus'
defineOptions({ name: 'Login' })
const { t } = useI18n()
const message = useMessage()
const appStore = useAppStore()
const { getPrefixCls } = useDesign()
const prefixCls = getPrefixCls('login')
const iconHouse = useIcon({ icon: 'ep:house' })
const iconAvatar = useIcon({ icon: 'ep:avatar' })
const iconLock = useIcon({ icon: 'ep:lock' })
const formLogin = ref()
const loginLoading = ref(false)
const captchaType = ref('blockPuzzle')
const tenantList = ref<LoginApi.TenantInfo[]>([])
const tenantSelectRef = ref()
const isFetchingTenants = ref(false)
const LoginRules = {
username: [required],
password: [required],
tenantId: [
{
required: true,
message: '请选择租户',
trigger: 'change'
}
]
}
const loginData = reactive({
captchaEnable: import.meta.env.VITE_APP_CAPTCHA_ENABLE,
tenantEnable: import.meta.env.VITE_APP_TENANT_ENABLE,
loginForm: {
tenantId: undefined as number | undefined,
username: import.meta.env.VITE_APP_DEFAULT_LOGIN_USERNAME || '',
password: import.meta.env.VITE_APP_DEFAULT_LOGIN_PASSWORD || '',
captchaVerification: '',
rememberMe: true
}
})
const verify = ref()
const loading = ref()
const { push } = useRouter()
const permissionStore = usePermissionStore()
// 用户名失焦时获取租户列表
const handleUsernameBlur = async () => {
if (loginData.tenantEnable !== 'true') {
return
}
await fetchTenantList()
}
// 密码失焦时获取租户列表
const handlePasswordBlur = async () => {
if (loginData.tenantEnable !== 'true') {
return
}
await fetchTenantList()
}
// 获取租户列表
const fetchTenantList = async () => {
const username = loginData.loginForm.username?.trim()
const password = loginData.loginForm.password?.trim()
if (!username || !password) {
return
}
if (isFetchingTenants.value) {
return
}
isFetchingTenants.value = true
try {
const res = await LoginApi.getUserTenantList({
username,
password
})
if (!res || !res.tenants || res.tenants.length === 0) {
message.error('无权限访问该系统')
tenantList.value = []
loginData.loginForm.tenantId = undefined
return
}
tenantList.value = res.tenants
if (res.tenants.length === 1) {
loginData.loginForm.tenantId = res.tenants[0].tenantId
} else {
loginData.loginForm.tenantId = undefined
}
} catch (error) {
console.error('获取租户列表失败:', error)
tenantList.value = []
loginData.loginForm.tenantId = undefined
} finally {
isFetchingTenants.value = false
}
}
// 获取验证码
const getCode = async () => {
if (loginData.tenantEnable === 'true' && tenantList.value.length === 0) {
const username = loginData.loginForm.username?.trim()
const password = loginData.loginForm.password?.trim()
if (!username || !password) {
message.error('请输入用户名和密码')
return
}
await fetchTenantList()
if (tenantList.value.length === 0) {
return
}
if (tenantList.value.length === 1) {
loginData.loginForm.tenantId = tenantList.value[0].tenantId
continueLogin()
} else {
if (tenantSelectRef.value) {
tenantSelectRef.value.visible = true
}
message.info('请选择要登录的租户')
}
} else if (loginData.tenantEnable === 'true' && tenantList.value.length > 0) {
if (!loginData.loginForm.tenantId) {
message.error('请选择租户')
return
}
continueLogin()
} else {
continueLogin()
}
}
const continueLogin = () => {
if (loginData.captchaEnable === 'false') {
handleLogin({})
} else {
verify.value?.show()
}
}
const setTenantId = () => {
if (loginData.loginForm.tenantId) {
authUtil.setTenantId(loginData.loginForm.tenantId)
}
}
const getLoginFormCache = () => {
const loginForm = authUtil.getLoginForm()
if (loginForm) {
loginData.loginForm = {
...loginData.loginForm,
username: loginForm.username ? loginForm.username : loginData.loginForm.username,
password: loginForm.password ? loginForm.password : loginData.loginForm.password,
rememberMe: loginForm.rememberMe
}
}
}
const getTenantByWebsite = async () => {
if (loginData.tenantEnable === 'true') {
const website = location.host
const res = await LoginApi.getTenantByWebsite(website)
if (res) {
authUtil.setTenantId(res.id)
}
}
}
const handleLogin = async (params: any) => {
loginLoading.value = true
try {
setTenantId()
await formLogin.value.validate()
const loginDataLoginForm = { ...loginData.loginForm }
loginDataLoginForm.captchaVerification = params.captchaVerification
const res = await LoginApi.login(loginDataLoginForm)
if (!res) {
return
}
loading.value = ElLoading.service({
lock: true,
text: '正在加载系统中...',
background: 'rgba(0, 0, 0, 0.7)'
})
if (loginDataLoginForm.rememberMe) {
authUtil.setLoginForm(loginDataLoginForm)
} else {
authUtil.removeLoginForm()
}
authUtil.setToken(res)
await push({ path: '/' })
} finally {
loginLoading.value = false
loading.value?.close()
}
}
onMounted(() => {
getLoginFormCache()
getTenantByWebsite()
})
</script>
<style lang="scss" scoped>
.login-container {
position: relative;
width: 100%;
height: 100vh;
background: #1677ff;
display: flex;
align-items: center;
justify-content: center;
overflow: hidden;
}
.login-content {
display: flex;
align-items: center;
justify-content: center;
gap: 100px;
max-width: 1200px;
width: 100%;
padding: 0 40px;
}
.login-card {
background: #ffffff;
border-radius: 8px;
padding: 40px;
width: 380px;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.1);
}
.login-logo {
display: flex;
justify-content: center;
margin-bottom: 16px;
.logo-icon {
width: 60px;
height: 60px;
display: flex;
align-items: center;
justify-content: center;
}
}
.login-title {
text-align: center;
font-size: 24px;
font-weight: 600;
color: #1677ff;
margin: 0 0 30px 0;
}
.login-form {
:deep(.el-input__wrapper) {
border-radius: 4px;
}
:deep(.el-select .el-input__wrapper) {
border-radius: 4px;
}
}
.login-options {
margin-bottom: 20px;
:deep(.el-checkbox__label) {
color: #1677ff;
font-size: 14px;
}
}
.login-button {
width: 100%;
height: 44px;
font-size: 16px;
background: #1677ff;
border: none;
border-radius: 4px;
&:hover {
background: #4096ff;
}
}
.login-illustration {
flex-shrink: 0;
img {
width: 500px;
height: auto;
}
}
.login-tools {
position: absolute;
top: 20px;
right: 20px;
display: flex;
gap: 12px;
align-items: center;
:deep(svg) {
fill: #ffffff;
}
}
@media (max-width: 1200px) {
.login-illustration {
display: none;
}
.login-content {
justify-content: center;
}
}
@media (max-width: 480px) {
.login-card {
width: 100%;
padding: 30px 20px;
}
.login-content {
padding: 0 20px;
}
}
</style>
<style lang="scss">
.dark .login-form {
.el-divider__text {
background-color: var(--login-bg-color);
}
.el-card {
background-color: var(--login-bg-color);
}
}
</style>