提交
This commit is contained in:
@@ -0,0 +1,531 @@
|
||||
<template>
|
||||
<div class="login-page">
|
||||
<div class="login-container">
|
||||
<!-- 左侧展示区域 -->
|
||||
<div class="login-left">
|
||||
<div class="logo-section">
|
||||
<h1 style="color: #fff;">USED CAR</h1>
|
||||
<!-- <p>二手车管理系统</p> -->
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 右侧登录表单 -->
|
||||
<div class="login-right">
|
||||
<div class="login-form-wrapper">
|
||||
<!-- 登录方式切换 -->
|
||||
<div class="login-type-tabs">
|
||||
<span
|
||||
:class="{ active: activeLoginType === 'password' }"
|
||||
@click="switchLoginType('password')"
|
||||
>账号密码登录</span>
|
||||
<span
|
||||
:class="{ active: activeLoginType === 'qrcode' }"
|
||||
@click="switchLoginType('qrcode')"
|
||||
>飞书登录</span>
|
||||
</div>
|
||||
|
||||
<!-- 账号密码登录表单 -->
|
||||
<div v-if="activeLoginType === 'password'" class="password-login">
|
||||
<el-form
|
||||
ref="formRef"
|
||||
:model="formData"
|
||||
:rules="rules"
|
||||
@keyup.enter="handlePasswordLogin"
|
||||
>
|
||||
<el-form-item prop="username">
|
||||
<el-input
|
||||
v-model="formData.username"
|
||||
placeholder="请输入账号"
|
||||
prefix-icon="User"
|
||||
size="large"
|
||||
/>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item prop="password">
|
||||
<el-input
|
||||
v-model="formData.password"
|
||||
type="password"
|
||||
placeholder="请输入密码"
|
||||
prefix-icon="Lock"
|
||||
size="large"
|
||||
show-password
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button
|
||||
type="primary"
|
||||
size="large"
|
||||
style="width: 100%"
|
||||
:loading="loading"
|
||||
@click="handlePasswordLogin"
|
||||
>
|
||||
登录
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
|
||||
<!-- 飞书扫码登录 -->
|
||||
<div v-else class="qrcode-login">
|
||||
<el-form :model="formData">
|
||||
<el-form-item prop="company">
|
||||
<el-select
|
||||
v-model="formData.company"
|
||||
placeholder="请选择公司"
|
||||
size="large"
|
||||
style="width: 100%"
|
||||
@change="handleCompanyChange"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in companyList"
|
||||
:key="item.id"
|
||||
:label="item.name"
|
||||
:value="item.id"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<div v-if="!isInFeishu && formData.company" class="qrcode-container">
|
||||
<div id="feishu_qr_login_container"></div>
|
||||
<p class="qrcode-tip">请使用飞书App扫码登录</p>
|
||||
</div>
|
||||
|
||||
<div v-if="isInFeishu && formData.company" class="feishu-auto-login">
|
||||
<p>正在跳转飞书授权...</p>
|
||||
</div>
|
||||
|
||||
<div v-if="!formData.company" class="qrcode-placeholder">
|
||||
<p>请先选择公司</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, computed, onMounted, nextTick, watch } from 'vue'
|
||||
import { useRouter, useRoute } from 'vue-router'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import type { FormInstance, FormRules } from 'element-plus'
|
||||
import { UserService } from '@/api/usersApi'
|
||||
import { useUserStore } from '@/store/modules/user'
|
||||
import AppConfig from '@/config'
|
||||
|
||||
defineOptions({ name: 'Login' })
|
||||
|
||||
const router = useRouter()
|
||||
const route = useRoute()
|
||||
const userStore = useUserStore()
|
||||
|
||||
// 系统名称
|
||||
const systemName = AppConfig.systemInfo.name || '二手车管理系统'
|
||||
|
||||
// 登录方式
|
||||
const activeLoginType = ref<'password' | 'qrcode'>('password')
|
||||
|
||||
// 是否在飞书环境中
|
||||
const isInFeishu = /Lark|Feishu/i.test(navigator.userAgent)
|
||||
|
||||
// 表单引用
|
||||
const formRef = ref<FormInstance>()
|
||||
|
||||
// 加载状态
|
||||
const loading = ref(false)
|
||||
|
||||
// 公司列表
|
||||
const companyList = ref<any[]>([])
|
||||
|
||||
// 飞书配置
|
||||
const feishuConfig = ref<any>(null)
|
||||
|
||||
// 表单数据
|
||||
const formData = reactive({
|
||||
username: '',
|
||||
password: '',
|
||||
company: undefined as number | undefined
|
||||
})
|
||||
|
||||
// 表单验证规则
|
||||
const rules = computed<FormRules>(() => ({
|
||||
username: [{ required: true, message: '请输入账号', trigger: 'blur' }],
|
||||
password: [{ required: true, message: '请输入密码', trigger: 'blur' }],
|
||||
// 公司选择仅在飞书登录时必填,账号密码登录时可选
|
||||
company: activeLoginType.value === 'qrcode'
|
||||
? [{ required: true, message: '请选择公司', trigger: 'change' }]
|
||||
: []
|
||||
}))
|
||||
|
||||
// 角色设置辅助函数
|
||||
const getRoles = (role: number, role_wp: number | null) => {
|
||||
// 基础角色
|
||||
const baseRole = role == 1 ? 'business' : role == 2 ? 'procurement' : 'alls'
|
||||
const roles = [baseRole]
|
||||
|
||||
// 根据 role_wp 添加额外角色
|
||||
if (role_wp == 1) {
|
||||
roles.push('businesspro')
|
||||
} else if (role_wp == 2) {
|
||||
roles.push('supplier')
|
||||
} else if (role_wp == 3) {
|
||||
roles.push('adminpro')
|
||||
}
|
||||
|
||||
return roles
|
||||
}
|
||||
|
||||
// 切换登录方式
|
||||
const switchLoginType = (type: 'password' | 'qrcode') => {
|
||||
activeLoginType.value = type
|
||||
// 保存登录类型到 userStore
|
||||
if (type === 'qrcode') {
|
||||
userStore.setLoginType('feishu')
|
||||
} else {
|
||||
userStore.setLoginType('upwd')
|
||||
}
|
||||
|
||||
if (type === 'qrcode' && formData.company) {
|
||||
initFeishuLogin()
|
||||
}
|
||||
}
|
||||
|
||||
// 获取公司列表
|
||||
const getCompanyList = async () => {
|
||||
try {
|
||||
const response = await UserService.getCompanyList()
|
||||
companyList.value = response || []
|
||||
} catch (error) {
|
||||
console.error('获取公司列表失败:', error)
|
||||
}
|
||||
}
|
||||
|
||||
// 公司变更处理
|
||||
const handleCompanyChange = (companyId: number) => {
|
||||
const company = companyList.value.find(c => c.id === companyId)
|
||||
if (company) {
|
||||
userStore.setCompany(String(companyId), company.abbr || '')
|
||||
|
||||
// 构建飞书配置,确保 r_url 存在
|
||||
const defaultRUrl = 'http://web-usedcar.dev.hnxinyouhe.com/auth/feishu-callback'
|
||||
feishuConfig.value = {
|
||||
...company,
|
||||
r_url: company.r_url || defaultRUrl,
|
||||
state: company.state || 'xinyouhe'
|
||||
}
|
||||
|
||||
// 保存 app_id 供回调页面使用
|
||||
if (company.app_id) {
|
||||
localStorage.setItem('app_id', company.app_id)
|
||||
}
|
||||
}
|
||||
|
||||
if (activeLoginType.value === 'qrcode' && companyId) {
|
||||
initFeishuLogin()
|
||||
}
|
||||
}
|
||||
|
||||
// 初始化飞书登录
|
||||
const initFeishuLogin = async () => {
|
||||
if (!formData.company || !feishuConfig.value) return
|
||||
|
||||
// 飞书配置已在 handleCompanyChange 中从公司列表获取
|
||||
|
||||
if (isInFeishu) {
|
||||
// 飞书内环境,自动跳转授权
|
||||
await handleFeishuLogin()
|
||||
} else {
|
||||
// 普通浏览器环境,显示二维码
|
||||
await nextTick()
|
||||
await loadFeishuQRCode()
|
||||
}
|
||||
}
|
||||
|
||||
// 加载飞书二维码
|
||||
const loadFeishuQRCode = async () => {
|
||||
// 等待飞书SDK加载
|
||||
const waitForSDK = () =>
|
||||
new Promise<void>((resolve) => {
|
||||
const check = () => {
|
||||
// @ts-ignore
|
||||
if (window.QRLogin) resolve()
|
||||
else setTimeout(check, 100)
|
||||
}
|
||||
check()
|
||||
})
|
||||
|
||||
try {
|
||||
await waitForSDK()
|
||||
|
||||
const container = document.getElementById('feishu_qr_login_container')
|
||||
if (!container) {
|
||||
ElMessage.error('未找到二维码容器')
|
||||
return
|
||||
}
|
||||
|
||||
container.innerHTML = ''
|
||||
|
||||
const { app_id, r_url, state } = feishuConfig.value || {}
|
||||
if (!app_id || !r_url || !state) {
|
||||
ElMessage.error('飞书配置不完整,请重新选择公司')
|
||||
return
|
||||
}
|
||||
|
||||
// URL编码,确保redirect_uri正确传递
|
||||
const encodedRedirectUri = encodeURIComponent(r_url)
|
||||
const gotoUrl = `https://passport.feishu.cn/suite/passport/oauth/authorize?client_id=${app_id}&redirect_uri=${encodedRedirectUri}&response_type=code&state=${state}`
|
||||
|
||||
// @ts-ignore
|
||||
const QRLoginObj = window.QRLogin({
|
||||
id: 'feishu_qr_login_container',
|
||||
goto: gotoUrl,
|
||||
width: '250',
|
||||
height: '250',
|
||||
style: 'width:280px;height:280px;border:none'
|
||||
})
|
||||
|
||||
// 监听扫码消息
|
||||
const handleMessage = (event: MessageEvent) => {
|
||||
// @ts-ignore
|
||||
if (
|
||||
QRLoginObj.matchOrigin &&
|
||||
QRLoginObj.matchData &&
|
||||
QRLoginObj.matchOrigin(event.origin) &&
|
||||
QRLoginObj.matchData(event.data)
|
||||
) {
|
||||
const loginTmpCode = event.data.tmp_code
|
||||
// 使用编码后的跳转URL
|
||||
window.location.href = `${gotoUrl}&tmp_code=${loginTmpCode}`
|
||||
}
|
||||
}
|
||||
|
||||
window.addEventListener('message', handleMessage, false)
|
||||
} catch {
|
||||
ElMessage.error('加载飞书二维码失败')
|
||||
}
|
||||
}
|
||||
|
||||
// 飞书登录(飞书内环境)
|
||||
const handleFeishuLogin = async () => {
|
||||
if (!formData.company) {
|
||||
ElMessage.warning('请先选择公司')
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
const { auth_url } = await UserService.getfeishuscope({
|
||||
app_id: feishuConfig.value?.app_id,
|
||||
is_pc: 1
|
||||
})
|
||||
window.location.href = auth_url
|
||||
} catch (error) {
|
||||
ElMessage.error('获取飞书授权失败')
|
||||
}
|
||||
}
|
||||
|
||||
// 账号密码登录
|
||||
const handlePasswordLogin = async () => {
|
||||
if (!formRef.value) return
|
||||
|
||||
try {
|
||||
const valid = await formRef.value.validate()
|
||||
if (!valid) return
|
||||
|
||||
loading.value = true
|
||||
|
||||
const { data } = await UserService.login({
|
||||
account: formData.username,
|
||||
password: formData.password
|
||||
})
|
||||
|
||||
// 保存token(union_id为token)
|
||||
if (data.union_id) {
|
||||
userStore.setToken(data.union_id)
|
||||
}
|
||||
|
||||
// 设置角色(支持多角色数组)
|
||||
const roles = getRoles(data.role, data.role_wp)
|
||||
localStorage.setItem('role', JSON.stringify(roles))
|
||||
|
||||
// 保存用户信息
|
||||
userStore.setUserInfo(data)
|
||||
userStore.setLoginStatus(true)
|
||||
|
||||
ElMessage.success('登录成功')
|
||||
// 跳转首页
|
||||
const redirect = (route.query.redirect as string) || '/'
|
||||
// router.push(redirect)
|
||||
router.push(data.home)
|
||||
} catch (error: any) {
|
||||
ElMessage.error(error?.message || '登录失败,请检查账号密码')
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 监听登录方式切换
|
||||
watch(activeLoginType, (newType) => {
|
||||
if (newType === 'qrcode' && formData.company) {
|
||||
initFeishuLogin()
|
||||
}
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
getCompanyList()
|
||||
// 设置默认登录类型为账号密码登录
|
||||
userStore.setLoginType('upwd')
|
||||
|
||||
// 检查URL参数中的公司ID
|
||||
const companyId = route.query.corp_id as string
|
||||
if (companyId) {
|
||||
formData.company = Number(companyId)
|
||||
handleCompanyChange(Number(companyId))
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.login-page {
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
}
|
||||
|
||||
.login-container {
|
||||
width: 900px;
|
||||
height: 550px;
|
||||
background: #fff;
|
||||
border-radius: 16px;
|
||||
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.2);
|
||||
display: flex;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.login-left {
|
||||
flex: 1;
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
color: #fff;
|
||||
padding: 40px;
|
||||
|
||||
.logo-section {
|
||||
text-align: center;
|
||||
|
||||
h1 {
|
||||
font-size: 32px;
|
||||
margin-bottom: 12px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
p {
|
||||
font-size: 16px;
|
||||
opacity: 0.9;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.login-right {
|
||||
flex: 1;
|
||||
padding: 40px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.login-form-wrapper {
|
||||
width: 100%;
|
||||
max-width: 360px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.login-type-tabs {
|
||||
display: flex;
|
||||
margin-bottom: 32px;
|
||||
border-bottom: 2px solid #eee;
|
||||
|
||||
span {
|
||||
flex: 1;
|
||||
text-align: center;
|
||||
padding: 12px 0;
|
||||
cursor: pointer;
|
||||
font-size: 16px;
|
||||
color: #666;
|
||||
border-bottom: 2px solid transparent;
|
||||
margin-bottom: -2px;
|
||||
transition: all 0.3s;
|
||||
|
||||
&.active {
|
||||
color: #667eea;
|
||||
border-bottom-color: #667eea;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
color: #667eea;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.password-login,
|
||||
.qrcode-login {
|
||||
:deep(.el-form-item) {
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
:deep(.el-input__wrapper) {
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
:deep(.el-button) {
|
||||
border-radius: 8px;
|
||||
height: 44px;
|
||||
font-size: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
.qrcode-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
padding: 20px 0;
|
||||
|
||||
#feishu_qr_login_container {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.qrcode-tip {
|
||||
color: #999;
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
||||
|
||||
.feishu-btn-login {
|
||||
padding: 20px 0;
|
||||
}
|
||||
|
||||
.feishu-auto-login {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 280px;
|
||||
color: #667eea;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.qrcode-placeholder {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 280px;
|
||||
background: #f5f7fa;
|
||||
border-radius: 8px;
|
||||
color: #999;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user