Files
zyt/admin/src/views/account/login.vue
T
2026-04-07 18:13:03 +08:00

317 lines
11 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 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>