317 lines
11 KiB
Vue
317 lines
11 KiB
Vue
<template>
|
||
<div class="login flex flex-col">
|
||
<div class="flex-1 flex items-center justify-center">
|
||
<div class="login-card flex rounded-md overflow-hidden">
|
||
<div class="flex-1 h-full hidden md:inline-block">
|
||
<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-[420px] w-[380px] flex-none mx-auto"
|
||
>
|
||
<div class="text-center text-3xl font-medium mb-8">{{ config.web_name }}</div>
|
||
|
||
<!-- 企业微信自动授权中 -->
|
||
<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>
|
||
|
||
<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>
|
||
<layout-footer />
|
||
</div>
|
||
</template>
|
||
|
||
<script lang="ts" setup>
|
||
import type { FormInstance, InputInstance } from 'element-plus'
|
||
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'
|
||
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>()
|
||
const appStore = useAppStore()
|
||
const userStore = useUserStore()
|
||
const route = useRoute()
|
||
const router = useRouter()
|
||
const remAccount = ref(false)
|
||
const config = computed(() => appStore.config)
|
||
const formData = reactive({
|
||
account: '',
|
||
password: ''
|
||
})
|
||
const rules = {
|
||
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 : ''
|
||
})
|
||
const result: any = await userStore.login(formData)
|
||
|
||
// 检查是否需要修改密码
|
||
if (result.is_paw === 0) {
|
||
router.push('/change-password')
|
||
return
|
||
}
|
||
if (result.need_bind_work_wechat) {
|
||
router.push('/bind-work-wechat')
|
||
return
|
||
}
|
||
redirectAfterLogin()
|
||
}
|
||
|
||
const { isLock, lockFn: lockLogin } = useLockFn(handleLogin)
|
||
|
||
const redirectAfterLogin = () => {
|
||
const { query: { redirect } } = route
|
||
const path = typeof redirect === 'string' ? redirect : PageEnum.INDEX
|
||
router.push(path)
|
||
}
|
||
|
||
// 企业微信 code 登录
|
||
const handleWxWorkCodeLogin = async (code: string) => {
|
||
wxWorkAutoLogin.value = true
|
||
try {
|
||
const result: any = await userStore.workWechatLogin(code)
|
||
|
||
// 检查是否需要修改密码
|
||
if (result.is_paw === 0) {
|
||
router.push('/change-password')
|
||
return
|
||
}
|
||
if (result.need_bind_work_wechat) {
|
||
router.push('/bind-work-wechat')
|
||
return
|
||
}
|
||
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>
|
||
|
||
<style lang="scss" scoped>
|
||
.login {
|
||
background-image: url('./images/login_bg.png');
|
||
@apply min-h-screen bg-no-repeat bg-center bg-cover;
|
||
.login-card {
|
||
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>
|