更新
This commit is contained in:
@@ -6,41 +6,79 @@
|
||||
<image-contain :src="config.login_image" :width="400" height="100%" />
|
||||
</div>
|
||||
<div
|
||||
class="login-form bg-body flex flex-col justify-center px-10 py-10 md:w-[400px] w-[375px] flex-none mx-auto"
|
||||
class="login-form bg-body flex flex-col justify-center px-10 py-10 md:w-[420px] w-[380px] flex-none mx-auto"
|
||||
>
|
||||
<div class="text-center text-3xl font-medium mb-8">{{ config.web_name }}</div>
|
||||
<el-form ref="formRef" :model="formData" size="large" :rules="rules">
|
||||
<el-form-item prop="account">
|
||||
<el-input
|
||||
v-model="formData.account"
|
||||
placeholder="请输入账号"
|
||||
@keyup.enter="handleEnter"
|
||||
>
|
||||
<template #prepend>
|
||||
<icon name="el-icon-User" size="16" />
|
||||
</template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
<el-form-item prop="password">
|
||||
<el-input
|
||||
ref="passwordRef"
|
||||
v-model="formData.password"
|
||||
show-password
|
||||
placeholder="请输入密码"
|
||||
@keyup.enter="handleLogin"
|
||||
>
|
||||
<template #prepend>
|
||||
<icon name="el-icon-Lock" size="16" />
|
||||
</template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<div class="mb-5">
|
||||
<el-checkbox v-model="remAccount" label="记住账号"></el-checkbox>
|
||||
|
||||
<!-- 企业微信自动授权中 -->
|
||||
<div v-if="wxWorkAutoLogin" class="text-center py-10">
|
||||
<el-icon class="is-loading mb-4" :size="40" color="var(--el-color-primary)">
|
||||
<Loading />
|
||||
</el-icon>
|
||||
<div class="text-gray-500">企业微信授权登录中...</div>
|
||||
</div>
|
||||
<el-button type="primary" size="large" :loading="isLock" @click="lockLogin">
|
||||
登录
|
||||
</el-button>
|
||||
|
||||
<template v-else>
|
||||
<!-- 登录方式切换标签 -->
|
||||
<div v-if="wxWorkEnabled" class="flex justify-center mb-6">
|
||||
<el-radio-group v-model="loginMode" size="large">
|
||||
<el-radio-button value="account">账号登录</el-radio-button>
|
||||
<el-radio-button value="wxwork">企业微信</el-radio-button>
|
||||
</el-radio-group>
|
||||
</div>
|
||||
|
||||
<!-- 账号密码登录 -->
|
||||
<template v-if="loginMode === 'account'">
|
||||
<el-form ref="formRef" :model="formData" size="large" :rules="rules">
|
||||
<el-form-item prop="account">
|
||||
<el-input
|
||||
v-model="formData.account"
|
||||
placeholder="请输入账号"
|
||||
@keyup.enter="handleEnter"
|
||||
>
|
||||
<template #prepend>
|
||||
<icon name="el-icon-User" size="16" />
|
||||
</template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
<el-form-item prop="password">
|
||||
<el-input
|
||||
ref="passwordRef"
|
||||
v-model="formData.password"
|
||||
show-password
|
||||
placeholder="请输入密码"
|
||||
@keyup.enter="handleLogin"
|
||||
>
|
||||
<template #prepend>
|
||||
<icon name="el-icon-Lock" size="16" />
|
||||
</template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<div class="mb-5">
|
||||
<el-checkbox v-model="remAccount" label="记住账号"></el-checkbox>
|
||||
</div>
|
||||
<el-button type="primary" size="large" :loading="isLock" @click="lockLogin">
|
||||
登录
|
||||
</el-button>
|
||||
</template>
|
||||
|
||||
<!-- 企业微信扫码登录(非企业微信内浏览器) -->
|
||||
<template v-if="loginMode === 'wxwork'">
|
||||
<div class="wxwork-qrcode-wrap">
|
||||
<div v-if="wxWorkLoading" class="text-center py-10">
|
||||
<el-icon class="is-loading" :size="32" color="var(--el-color-primary)">
|
||||
<Loading />
|
||||
</el-icon>
|
||||
<div class="mt-2 text-gray-400 text-sm">加载企业微信扫码...</div>
|
||||
</div>
|
||||
<div v-else id="wxwork_qrcode_container" class="wxwork-qrcode"></div>
|
||||
</div>
|
||||
<div class="text-center text-sm text-gray-400 mt-4">
|
||||
请使用企业微信扫描二维码登录
|
||||
</div>
|
||||
</template>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -50,8 +88,10 @@
|
||||
|
||||
<script lang="ts" setup>
|
||||
import type { FormInstance, InputInstance } from 'element-plus'
|
||||
import { computed, onMounted, reactive, ref, shallowRef } from 'vue'
|
||||
import { computed, nextTick, onMounted, ref, shallowRef, watch } from 'vue'
|
||||
import { Loading } from '@element-plus/icons-vue'
|
||||
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { ACCOUNT_KEY } from '@/enums/cacheEnums'
|
||||
import { PageEnum } from '@/enums/pageEnum'
|
||||
import { useLockFn } from '@/hooks/useLockFn'
|
||||
@@ -59,6 +99,7 @@ import LayoutFooter from '@/layout/components/footer.vue'
|
||||
import useAppStore from '@/stores/modules/app'
|
||||
import useUserStore from '@/stores/modules/user'
|
||||
import cache from '@/utils/cache'
|
||||
import { getWorkWechatConfig } from '@/api/user'
|
||||
|
||||
const passwordRef = shallowRef<InputInstance>()
|
||||
const formRef = shallowRef<FormInstance>()
|
||||
@@ -73,51 +114,154 @@ const formData = reactive({
|
||||
password: ''
|
||||
})
|
||||
const rules = {
|
||||
account: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入账号',
|
||||
trigger: ['blur']
|
||||
}
|
||||
],
|
||||
password: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入密码',
|
||||
trigger: ['blur']
|
||||
}
|
||||
]
|
||||
account: [{ required: true, message: '请输入账号', trigger: ['blur'] }],
|
||||
password: [{ required: true, message: '请输入密码', trigger: ['blur'] }]
|
||||
}
|
||||
// 回车按键监听
|
||||
|
||||
// 企业微信相关
|
||||
const loginMode = ref<'account' | 'wxwork'>('account')
|
||||
const wxWorkEnabled = ref(false)
|
||||
const wxWorkConfig = ref<{ corp_id: string; agent_id: string }>({ corp_id: '', agent_id: '' })
|
||||
const wxWorkAutoLogin = ref(false)
|
||||
const wxWorkLoading = ref(false)
|
||||
|
||||
const isInWxWork = () => /wxwork/i.test(navigator.userAgent)
|
||||
|
||||
const getRedirectUri = () => {
|
||||
return window.location.origin + window.location.pathname
|
||||
}
|
||||
|
||||
const handleEnter = () => {
|
||||
if (!formData.password) {
|
||||
return passwordRef.value?.focus()
|
||||
}
|
||||
handleLogin()
|
||||
}
|
||||
// 登录处理
|
||||
|
||||
const handleLogin = async () => {
|
||||
await formRef.value?.validate()
|
||||
// 记住账号,缓存
|
||||
cache.set(ACCOUNT_KEY, {
|
||||
remember: remAccount.value,
|
||||
account: remAccount.value ? formData.account : ''
|
||||
})
|
||||
await userStore.login(formData)
|
||||
const {
|
||||
query: { redirect }
|
||||
} = route
|
||||
redirectAfterLogin()
|
||||
}
|
||||
|
||||
const { isLock, lockFn: lockLogin } = useLockFn(handleLogin)
|
||||
|
||||
const redirectAfterLogin = () => {
|
||||
const { query: { redirect } } = route
|
||||
const path = typeof redirect === 'string' ? redirect : PageEnum.INDEX
|
||||
router.push(path)
|
||||
}
|
||||
const { isLock, lockFn: lockLogin } = useLockFn(handleLogin)
|
||||
|
||||
onMounted(() => {
|
||||
// 企业微信 code 登录
|
||||
const handleWxWorkCodeLogin = async (code: string) => {
|
||||
wxWorkAutoLogin.value = true
|
||||
try {
|
||||
await userStore.workWechatLogin(code)
|
||||
redirectAfterLogin()
|
||||
} catch (error: any) {
|
||||
console.error('企业微信登录失败:', error)
|
||||
ElMessage.error(error?.msg || error?.message || '企业微信登录失败,请使用账号密码登录')
|
||||
wxWorkAutoLogin.value = false
|
||||
loginMode.value = 'account'
|
||||
}
|
||||
}
|
||||
|
||||
// 加载企业微信扫码 JS SDK 并渲染二维码
|
||||
const renderWxWorkQRCode = async () => {
|
||||
if (!wxWorkConfig.value.corp_id) return
|
||||
|
||||
wxWorkLoading.value = true
|
||||
|
||||
// 动态加载企业微信扫码 JS
|
||||
if (!(window as any).WwLogin) {
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
const script = document.createElement('script')
|
||||
script.src = 'https://wwcdn.weixin.qq.com/node/wework/wwopen/js/wwLogin-1.2.7.js'
|
||||
script.onload = () => resolve()
|
||||
script.onerror = () => reject(new Error('加载企业微信JS失败'))
|
||||
document.head.appendChild(script)
|
||||
})
|
||||
}
|
||||
|
||||
await nextTick()
|
||||
wxWorkLoading.value = false
|
||||
|
||||
await nextTick()
|
||||
|
||||
const redirectUri = encodeURIComponent(getRedirectUri())
|
||||
|
||||
new (window as any).WwLogin({
|
||||
id: 'wxwork_qrcode_container',
|
||||
appid: wxWorkConfig.value.corp_id,
|
||||
agentid: wxWorkConfig.value.agent_id,
|
||||
redirect_uri: redirectUri,
|
||||
lang: 'zh',
|
||||
state: 'admin_login',
|
||||
href: '',
|
||||
self_redirect: false,
|
||||
})
|
||||
}
|
||||
|
||||
// 切换到企业微信扫码时自动渲染
|
||||
watch(loginMode, (val) => {
|
||||
if (val === 'wxwork') {
|
||||
nextTick(() => renderWxWorkQRCode())
|
||||
}
|
||||
})
|
||||
|
||||
onMounted(async () => {
|
||||
// 恢复记住账号
|
||||
const value = cache.get(ACCOUNT_KEY)
|
||||
if (value?.remember) {
|
||||
remAccount.value = value.remember
|
||||
formData.account = value.account
|
||||
}
|
||||
|
||||
// 检查 URL 中是否有企业微信回调 code
|
||||
const urlParams = new URLSearchParams(window.location.search)
|
||||
const wxCode = urlParams.get('code')
|
||||
const wxState = urlParams.get('state')
|
||||
|
||||
if (wxCode && wxState === 'admin_login') {
|
||||
// 清除 URL 中的 code 参数(避免刷新重复使用)
|
||||
const cleanUrl = window.location.pathname + (route.query.redirect ? `?redirect=${route.query.redirect}` : '')
|
||||
window.history.replaceState({}, '', cleanUrl)
|
||||
|
||||
handleWxWorkCodeLogin(wxCode)
|
||||
return
|
||||
}
|
||||
|
||||
// 获取企业微信配置
|
||||
try {
|
||||
const res = await getWorkWechatConfig()
|
||||
if (res?.enabled) {
|
||||
wxWorkEnabled.value = true
|
||||
wxWorkConfig.value = { corp_id: res.corp_id, agent_id: res.agent_id }
|
||||
|
||||
// 在企业微信内:自动跳转 OAuth 授权
|
||||
if (isInWxWork()) {
|
||||
const redirectUri = encodeURIComponent(getRedirectUri())
|
||||
const authUrl =
|
||||
`https://open.weixin.qq.com/connect/oauth2/authorize` +
|
||||
`?appid=${res.corp_id}` +
|
||||
`&redirect_uri=${redirectUri}` +
|
||||
`&response_type=code` +
|
||||
`&scope=snsapi_privateinfo` +
|
||||
`&agentid=${res.agent_id}` +
|
||||
`&state=admin_login` +
|
||||
`#wechat_redirect`
|
||||
window.location.href = authUrl
|
||||
wxWorkAutoLogin.value = true
|
||||
return
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn('获取企业微信配置失败:', e)
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
@@ -126,7 +270,27 @@ onMounted(() => {
|
||||
background-image: url('./images/login_bg.png');
|
||||
@apply min-h-screen bg-no-repeat bg-center bg-cover;
|
||||
.login-card {
|
||||
height: 400px;
|
||||
height: auto;
|
||||
min-height: 400px;
|
||||
}
|
||||
}
|
||||
|
||||
.wxwork-qrcode-wrap {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
min-height: 400px;
|
||||
}
|
||||
|
||||
.wxwork-qrcode {
|
||||
width: 340px;
|
||||
height: 400px;
|
||||
overflow: hidden;
|
||||
|
||||
:deep(iframe) {
|
||||
width: 340px !important;
|
||||
height: 400px !important;
|
||||
border: none;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user