datai-vue/src/views/auth/register/index.vue

741 lines
23 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="salesforce-login-container">
<div class="login-header">
<h2>Salesforce 认证中心</h2>
<p class="login-description">选择适合您的登录方式连接到 Salesforce</p>
</div>
<!-- 登录表单 -->
<div class="login-section" v-if="!isLoggedIn">
<el-card class="login-card" shadow="hover">
<el-tabs v-model="activeTab" @tab-change="handleTabChange" class="login-tabs">
<!-- OAuth 2.0 登录 -->
<el-tab-pane label="OAuth 2.0" name="oauth2">
<div class="tab-content">
<div class="tab-description">
<h3>OAuth 2.0 认证</h3>
<p>使用标准的 OAuth 2.0 协议进行安全认证</p>
</div>
<el-form
ref="oauth2FormRef"
:model="oauth2Form"
:rules="oauth2Rules"
label-width="120px"
class="login-form"
>
<el-form-item label="授权类型" prop="grantType">
<el-select v-model="oauth2Form.grantType" placeholder="请选择授权类型" style="width: 100%">
<el-option label="密码模式" value="password"></el-option>
<el-option label="客户端凭证模式" value="client_credentials"></el-option>
<el-option label="授权码模式" value="authorization_code"></el-option>
</el-select>
</el-form-item>
<el-form-item label="客户端ID" prop="clientId">
<el-input v-model="oauth2Form.clientId" placeholder="请输入客户端ID"></el-input>
</el-form-item>
<el-form-item label="客户端密钥" prop="clientSecret">
<el-input v-model="oauth2Form.clientSecret" type="password" placeholder="请输入客户端密钥" show-password></el-input>
</el-form-item>
<el-form-item label="用户名" prop="username" v-show="oauth2Form.grantType === 'password'">
<el-input v-model="oauth2Form.username" placeholder="请输入用户名"></el-input>
</el-form-item>
<el-form-item label="密码" prop="password" v-show="oauth2Form.grantType === 'password'">
<el-input v-model="oauth2Form.password" type="password" placeholder="请输入密码" show-password></el-input>
</el-form-item>
<el-form-item label="安全令牌" prop="securityToken" v-show="oauth2Form.grantType === 'password'">
<el-input v-model="oauth2Form.securityToken" placeholder="请输入安全令牌(可选)"></el-input>
</el-form-item>
<el-form-item label="授权码" prop="code" v-show="oauth2Form.grantType === 'authorization_code'">
<el-input v-model="oauth2Form.code" placeholder="请输入授权码"></el-input>
</el-form-item>
<el-form-item label="State" prop="state" v-show="oauth2Form.grantType === 'authorization_code'">
<el-input v-model="oauth2Form.state" placeholder="请输入state参数"></el-input>
</el-form-item>
<el-form-item class="form-actions">
<el-button type="primary" @click="handleOAuth2Login" :loading="loading" size="large">
<el-icon><Connection /></el-icon>
登录
</el-button>
</el-form-item>
</el-form>
</div>
</el-tab-pane>
<!-- 传统凭据登录 -->
<el-tab-pane label="传统凭据" name="legacy_credential">
<div class="tab-content">
<div class="tab-description">
<h3>传统凭据认证</h3>
<p>使用用户名和密码进行传统登录</p>
</div>
<el-form
ref="legacyFormRef"
:model="legacyForm"
:rules="legacyRules"
label-width="120px"
class="login-form"
>
<el-form-item label="用户名" prop="username">
<el-input v-model="legacyForm.username" placeholder="请输入用户名"></el-input>
</el-form-item>
<el-form-item label="密码" prop="password">
<el-input v-model="legacyForm.password" type="password" placeholder="请输入密码" show-password></el-input>
</el-form-item>
<el-form-item label="安全令牌" prop="securityToken">
<el-input v-model="legacyForm.securityToken" placeholder="请输入安全令牌(可选)"></el-input>
</el-form-item>
<el-form-item class="form-actions">
<el-button type="primary" @click="handleLegacyLogin" :loading="loading" size="large">
<el-icon><User /></el-icon>
登录
</el-button>
</el-form-item>
</el-form>
</div>
</el-tab-pane>
<!-- Session ID 登录 -->
<el-tab-pane label="Session ID" name="session_id">
<div class="tab-content">
<div class="tab-description">
<h3>Session ID 认证</h3>
<p>使用现有的 Session ID 直接登录</p>
</div>
<el-form
ref="sessionIdFormRef"
:model="sessionIdForm"
:rules="sessionIdRules"
label-width="120px"
class="login-form"
>
<el-form-item label="Session ID" prop="sessionId">
<el-input v-model="sessionIdForm.sessionId" placeholder="请输入Session ID" type="textarea" :rows="3"></el-input>
</el-form-item>
<el-form-item class="form-actions">
<el-button type="primary" @click="handleSessionIdLogin" :loading="loading" size="large">
<el-icon><Key /></el-icon>
登录
</el-button>
</el-form-item>
</el-form>
</div>
</el-tab-pane>
<!-- Salesforce CLI 登录 -->
<el-tab-pane label="Salesforce CLI" name="salesforce_cli">
<div class="tab-content">
<div class="tab-description">
<h3>Salesforce CLI 认证</h3>
<p>使用已配置的 Salesforce CLI 凭据登录</p>
</div>
<el-form
ref="cliFormRef"
:model="cliForm"
label-width="120px"
class="login-form"
>
<el-form-item label="组织别名" prop="orgAlias">
<el-input v-model="cliForm.orgAlias" placeholder="请输入组织别名(可选)"></el-input>
</el-form-item>
<el-form-item class="form-actions">
<el-button type="primary" @click="handleCliLogin" :loading="loading" size="large">
<el-icon><Monitor /></el-icon>
登录
</el-button>
</el-form-item>
</el-form>
</div>
</el-tab-pane>
</el-tabs>
</el-card>
</div>
<!-- 登录成功后的会话信息展示 -->
<div v-if="isLoggedIn && sessionInfo" class="session-info-section">
<el-card class="session-card" shadow="hover">
<template #header>
<div class="card-header">
<h3>会话信息</h3>
<el-tag :type="sessionInfo.status === 'active' ? 'success' : 'danger'" size="large">
{{ sessionInfo.status === 'active' ? '活跃' : '非活跃' }}
</el-tag>
</div>
</template>
<el-descriptions :column="2" border class="session-descriptions">
<el-descriptions-item label="会话ID" span="2">
<el-text class="session-id" copyable>{{ sessionInfo.sessionId }}</el-text>
</el-descriptions-item>
<el-descriptions-item label="租户编号">{{ sessionInfo.tenantId }}</el-descriptions-item>
<el-descriptions-item label="用户名">{{ sessionInfo.username }}</el-descriptions-item>
<el-descriptions-item label="登录类型">{{ sessionInfo.loginType }}</el-descriptions-item>
<el-descriptions-item label="会话状态">
<el-tag :type="sessionInfo.status === 'active' ? 'success' : 'danger'">
{{ sessionInfo.status }}
</el-tag>
</el-descriptions-item>
<el-descriptions-item label="登录时间">{{ sessionInfo.loginTime }}</el-descriptions-item>
<el-descriptions-item label="过期时间">{{ sessionInfo.expireTime }}</el-descriptions-item>
<el-descriptions-item label="最后活动时间">{{ sessionInfo.lastActivityTime }}</el-descriptions-item>
<el-descriptions-item label="登录IP" span="2">{{ sessionInfo.loginIp }}</el-descriptions-item>
<el-descriptions-item label="设备信息" span="2">{{ sessionInfo.deviceInfo }}</el-descriptions-item>
<el-descriptions-item label="浏览器信息" span="2">{{ sessionInfo.browserInfo }}</el-descriptions-item>
<el-descriptions-item label="令牌ID" span="2">
<el-text class="token-id" copyable>{{ sessionInfo.tokenId }}</el-text>
</el-descriptions-item>
</el-descriptions>
<div class="session-actions">
<el-button type="primary" @click="fetchSessionInfo" :loading="loading">
<el-icon><Refresh /></el-icon>
刷新会话信息
</el-button>
<el-button type="danger" @click="logout">
<el-icon><SwitchButton /></el-icon>
退出登录
</el-button>
</div>
</el-card>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, reactive, onMounted } from 'vue'
import { executeLogin as salesforceLogin, getLoginSession as getCurrentSession } from '@/api/auth/register'
import { ElMessage, ElMessageBox, FormInstance, FormRules } from 'element-plus'
import { Connection, User, Key, Monitor, Refresh, SwitchButton } from '@element-plus/icons-vue'
// 表单引用
const oauth2FormRef = ref<FormInstance>()
const legacyFormRef = ref<FormInstance>()
const sessionIdFormRef = ref<FormInstance>()
const cliFormRef = ref<FormInstance>()
// 当前激活的标签页
const activeTab = ref('oauth2')
// 加载状态
const loading = ref(false)
// 登录状态
const isLoggedIn = ref(false)
// 会话信息
const sessionInfo = ref<any>(null)
// OAuth2 表单数据
const oauth2Form = reactive({
loginType: 'oauth2',
grantType: 'password',
username: '',
password: '',
securityToken: '',
clientId: '',
clientSecret: '',
code: '',
state: ''
})
// 传统凭据表单数据
const legacyForm = reactive({
loginType: 'legacy_credential',
username: '',
password: '',
securityToken: ''
})
// Session ID 表单数据
const sessionIdForm = reactive({
loginType: 'session_id',
sessionId: ''
})
// CLI 表单数据
const cliForm = reactive({
loginType: 'salesforce_cli',
orgAlias: ''
})
// OAuth2 表单校验规则
const oauth2Rules = reactive<FormRules>({
grantType: [{ required: true, message: '请选择授权类型', trigger: 'change' }],
clientId: [{ required: true, message: '请输入客户端ID', trigger: 'blur' }],
clientSecret: [{ required: true, message: '请输入客户端密钥', trigger: 'blur' }],
username: [{
required: false,
message: '请输入用户名',
trigger: 'blur',
validator: (rule, value, callback) => {
if (oauth2Form.grantType === 'password' && !value) {
callback(new Error('密码模式下用户名为必填项'))
} else {
callback()
}
}
}],
password: [{
required: false,
message: '请输入密码',
trigger: 'blur',
validator: (rule, value, callback) => {
if (oauth2Form.grantType === 'password' && !value) {
callback(new Error('密码模式下密码为必填项'))
} else {
callback()
}
}
}],
code: [{
required: false,
message: '请输入授权码',
trigger: 'blur',
validator: (rule, value, callback) => {
if (oauth2Form.grantType === 'authorization_code' && !value) {
callback(new Error('授权码模式下授权码为必填项'))
} else {
callback()
}
}
}],
state: [{
required: false,
message: '请输入state参数',
trigger: 'blur',
validator: (rule, value, callback) => {
if (oauth2Form.grantType === 'authorization_code' && !value) {
callback(new Error('授权码模式下state参数为必填项'))
} else {
callback()
}
}
}]
})
// 传统凭据表单校验规则
const legacyRules = reactive<FormRules>({
username: [{ required: true, message: '请输入用户名', trigger: 'blur' }],
password: [{ required: true, message: '请输入密码', trigger: 'blur' }]
})
// Session ID 表单校验规则
const sessionIdRules = reactive<FormRules>({
sessionId: [{ required: true, message: '请输入Session ID', trigger: 'blur' }]
})
// 标签页切换处理
const handleTabChange = () => {
// 清空表单校验状态
oauth2FormRef.value?.clearValidate()
legacyFormRef.value?.clearValidate()
sessionIdFormRef.value?.clearValidate()
cliFormRef.value?.clearValidate()
}
// 通用登录处理函数
const handleLogin = async (formRef: FormInstance | undefined, formData: any, loginType: string) => {
if (!formRef) return
await formRef.validate(async (valid) => {
if (valid) {
loading.value = true
try {
// 处理CLI登录的特殊情况
let loginParams = { ...formData }
if (loginType === 'salesforce_cli' && !loginParams.orgAlias) {
delete loginParams.orgAlias
}
await salesforceLogin(loginParams)
ElMessage.success('登录成功')
await fetchSessionInfo()
} catch (error: any) {
console.error(`${loginType}登录失败:`, error)
const errorMessage = error.response?.data?.message || error.message || '登录失败,请检查您的凭据'
ElMessage.error(errorMessage)
} finally {
loading.value = false
}
}
})
}
// OAuth2 登录处理
const handleOAuth2Login = async () => {
if (!oauth2FormRef.value) return
await oauth2FormRef.value.validate(async (valid) => {
if (valid) {
loading.value = true
try {
// 构造登录参数 - 所有OAuth2模式都需要客户端ID和密钥
const loginParams: any = {
loginType: oauth2Form.loginType,
grantType: oauth2Form.grantType,
clientId: oauth2Form.clientId,
clientSecret: oauth2Form.clientSecret
}
// 根据不同授权类型添加特定参数
if (oauth2Form.grantType === 'password') {
loginParams.username = oauth2Form.username
loginParams.password = oauth2Form.password
// 仅在有值时添加安全令牌
if (oauth2Form.securityToken) {
loginParams.securityToken = oauth2Form.securityToken
}
} else if (oauth2Form.grantType === 'authorization_code') {
loginParams.code = oauth2Form.code
loginParams.state = oauth2Form.state
}
// client_credentials 类型需要 clientId 和 clientSecret已在基础参数中
await salesforceLogin(loginParams)
ElMessage.success('登录成功')
await fetchSessionInfo()
} catch (error: any) {
console.error('OAuth2登录失败:', error)
const errorMessage = error.response?.data?.message || error.message || '登录失败,请检查您的凭据'
ElMessage.error(errorMessage)
} finally {
loading.value = false
}
}
})
}
// 传统凭据登录处理
const handleLegacyLogin = () => handleLogin(legacyFormRef.value, legacyForm, '传统凭据')
// Session ID 登录处理
const handleSessionIdLogin = () => handleLogin(sessionIdFormRef.value, sessionIdForm, 'Session ID')
// CLI 登录处理
const handleCliLogin = () => {
if (!cliFormRef.value) return
loading.value = true
try {
// 如果没有填写组织别名,则删除该字段
const loginParams = { ...cliForm }
if (!loginParams.orgAlias) {
delete loginParams.orgAlias
}
salesforceLogin(loginParams).then(() => {
ElMessage.success('登录成功')
fetchSessionInfo()
}).catch((error: any) => {
console.error('CLI登录失败:', error)
const errorMessage = error.response?.data?.message || error.message || '登录失败请检查您的CLI配置'
ElMessage.error(errorMessage)
}).finally(() => {
loading.value = false
})
} catch (error: any) {
console.error('CLI登录失败:', error)
const errorMessage = error.response?.data?.message || error.message || '登录失败请检查您的CLI配置'
ElMessage.error(errorMessage)
loading.value = false
}
}
// 获取会话信息
const fetchSessionInfo = async () => {
try {
const response = await getCurrentSession()
if (response.code === 200) {
sessionInfo.value = response.data
isLoggedIn.value = true
} else {
ElMessage.error(response.msg || '获取会话信息失败')
isLoggedIn.value = false
sessionInfo.value = null
}
} catch (error: any) {
console.error('获取会话信息失败:', error)
const errorMessage = error.response?.data?.message || error.message || '获取会话信息失败'
ElMessage.error(errorMessage)
isLoggedIn.value = false
sessionInfo.value = null
}
}
// 页面加载时尝试获取会话信息
onMounted(() => {
fetchSessionInfo()
})
// 退出登录
const logout = () => {
ElMessageBox.confirm('确定要退出登录吗?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
isLoggedIn.value = false
sessionInfo.value = null
ElMessage.success('已退出登录')
}).catch(() => {
// 取消退出
})
}
</script>
<style scoped lang="scss">
.salesforce-login-container {
padding: 24px;
max-width: 900px;
margin: 0 auto;
min-height: 100vh;
display: flex;
flex-direction: column;
.login-header {
text-align: center;
margin-bottom: 32px;
h2 {
font-size: 28px;
font-weight: 600;
color: #303133;
margin-bottom: 12px;
}
.login-description {
font-size: 16px;
color: #606266;
max-width: 600px;
margin: 0 auto;
}
}
.login-section {
flex: 1;
display: flex;
flex-direction: column;
justify-content: center;
.login-card {
border-radius: 12px;
overflow: hidden;
.login-tabs {
:deep(.el-tabs__header) {
margin-bottom: 24px;
}
:deep(.el-tabs__nav-wrap) {
&::after {
height: 1px;
}
}
:deep(.el-tabs__item) {
font-size: 16px;
font-weight: 500;
padding: 0 24px;
}
}
.tab-content {
padding: 0 8px;
.tab-description {
margin-bottom: 24px;
text-align: center;
h3 {
font-size: 20px;
font-weight: 600;
color: #303133;
margin-bottom: 8px;
}
p {
font-size: 14px;
color: #909399;
}
}
.login-form {
max-width: 500px;
margin: 0 auto;
:deep(.el-form-item__label) {
font-weight: 500;
color: #606266;
}
:deep(.el-input__wrapper) {
border-radius: 6px;
}
:deep(.el-select .el-input .el-input__wrapper) {
border-radius: 6px;
}
.form-actions {
margin-top: 32px;
text-align: center;
.el-button {
padding: 12px 32px;
font-size: 16px;
border-radius: 6px;
.el-icon {
margin-right: 8px;
}
}
}
}
}
}
}
.session-info-section {
margin-top: 32px;
.session-card {
border-radius: 12px;
overflow: hidden;
.card-header {
display: flex;
justify-content: space-between;
align-items: center;
h3 {
font-size: 20px;
font-weight: 600;
color: #303133;
margin: 0;
}
}
.session-descriptions {
margin: 24px 0;
:deep(.el-descriptions__label) {
font-weight: 500;
color: #606266;
}
:deep(.el-descriptions__content) {
color: #303133;
}
.session-id, .token-id {
font-family: monospace;
font-size: 13px;
word-break: break-all;
}
}
.session-actions {
display: flex;
justify-content: center;
gap: 16px;
margin-top: 24px;
.el-button {
padding: 10px 24px;
border-radius: 6px;
.el-icon {
margin-right: 8px;
}
}
}
}
}
}
@media (max-width: 768px) {
.salesforce-login-container {
padding: 16px;
.login-header {
h2 {
font-size: 24px;
}
.login-description {
font-size: 14px;
}
}
.login-section {
.login-card {
.login-tabs {
:deep(.el-tabs__item) {
font-size: 14px;
padding: 0 16px;
}
}
.tab-content {
.tab-description {
h3 {
font-size: 18px;
}
p {
font-size: 13px;
}
}
.login-form {
max-width: 100%;
.form-actions {
.el-button {
padding: 10px 24px;
font-size: 14px;
}
}
}
}
}
}
.session-info-section {
.session-card {
.session-descriptions {
:deep(.el-descriptions__label) {
font-size: 13px;
}
:deep(.el-descriptions__content) {
font-size: 13px;
}
}
.session-actions {
flex-direction: column;
align-items: center;
.el-button {
width: 100%;
max-width: 200px;
}
}
}
}
}
}
</style>