feat: 扫码登陆功能
Some checks are pending
sync2gitee / repo-sync (push) Waiting to run
Typos Check / Spell Check with Typos (push) Waiting to run

This commit is contained in:
wangdan-fit2cloud 2024-10-09 10:59:45 +08:00 committed by wxg0103
parent 2e7b31dc3c
commit aa8e68a688
21 changed files with 1217 additions and 316 deletions

View File

@ -18,6 +18,7 @@
"@logicflow/core": "^1.2.27",
"@logicflow/extension": "^1.2.27",
"@vueuse/core": "^10.9.0",
"@wecom/jssdk": "^2.1.0",
"axios": "^0.28.0",
"codemirror": "^6.0.1",
"cropperjs": "^1.6.2",

View File

@ -0,0 +1,28 @@
import { Result } from '@/request/Result'
import { get, post, del, put } from '@/request/index'
import type { pageRequest } from '@/api/type/common'
import { type Ref } from 'vue'
const prefix = '/platform'
const getPlatformInfo: (loading?: Ref<boolean>) => Promise<Result<any>> = (loading) => {
return get(`${prefix}/source`, undefined, loading)
}
const updateConfig: (data: any, loading?: Ref<boolean>) => Promise<Result<any>> = (
data,
loading
) => {
return post(`${prefix}/source`, data, undefined, loading)
}
const validateConnection: (data: any, loading?: Ref<boolean>) => Promise<Result<any>> = (
data,
loading
) => {
return put(`${prefix}/source`, data, undefined, loading)
}
export default {
getPlatformInfo,
updateConfig,
validateConnection
}

View File

@ -1,14 +1,14 @@
import {Result} from '@/request/Result'
import {get, post} from '@/request/index'
import { Result } from '@/request/Result'
import { get, post } from '@/request/index'
import type {
LoginRequest,
RegisterRequest,
CheckCodeRequest,
ResetPasswordRequest,
User,
ResetCurrentUserPasswordRequest
LoginRequest,
RegisterRequest,
CheckCodeRequest,
ResetPasswordRequest,
User,
ResetCurrentUserPasswordRequest
} from '@/api/type/user'
import type {Ref} from 'vue'
import type { Ref } from 'vue'
/**
*
@ -17,15 +17,15 @@ import type {Ref} from 'vue'
* @param loading
* @returns
*/
const login: (auth_type: string, request: LoginRequest, loading?: Ref<boolean>) => Promise<Result<string>> = (
auth_type,
request,
loading
) => {
if (auth_type !== '') {
return post(`/${auth_type}/login`, request, undefined, loading)
}
return post('/user/login', request, undefined, loading)
const login: (
auth_type: string,
request: LoginRequest,
loading?: Ref<boolean>
) => Promise<Result<string>> = (auth_type, request, loading) => {
if (auth_type !== '') {
return post(`/${auth_type}/login`, request, undefined, loading)
}
return post('/user/login', request, undefined, loading)
}
/**
*
@ -33,7 +33,7 @@ const login: (auth_type: string, request: LoginRequest, loading?: Ref<boolean>)
* @returns
*/
const logout: (loading?: Ref<boolean>) => Promise<Result<boolean>> = (loading) => {
return post('/user/logout', undefined, undefined, loading)
return post('/user/logout', undefined, undefined, loading)
}
/**
@ -43,10 +43,10 @@ const logout: (loading?: Ref<boolean>) => Promise<Result<boolean>> = (loading) =
* @returns
*/
const register: (request: RegisterRequest, loading?: Ref<boolean>) => Promise<Result<string>> = (
request,
loading
request,
loading
) => {
return post('/user/register', request, undefined, loading)
return post('/user/register', request, undefined, loading)
}
/**
@ -56,10 +56,10 @@ const register: (request: RegisterRequest, loading?: Ref<boolean>) => Promise<Re
* @returns
*/
const checkCode: (request: CheckCodeRequest, loading?: Ref<boolean>) => Promise<Result<boolean>> = (
request,
loading
request,
loading
) => {
return post('/user/check_code', request, undefined, loading)
return post('/user/check_code', request, undefined, loading)
}
/**
@ -69,11 +69,11 @@ const checkCode: (request: CheckCodeRequest, loading?: Ref<boolean>) => Promise<
* @returns
*/
const sendEmit: (
email: string,
type: 'register' | 'reset_password',
loading?: Ref<boolean>
email: string,
type: 'register' | 'reset_password',
loading?: Ref<boolean>
) => Promise<Result<boolean>> = (email, type, loading) => {
return post('/user/send_email', {email, type}, undefined, loading)
return post('/user/send_email', { email, type }, undefined, loading)
}
/**
*
@ -81,7 +81,7 @@ const sendEmit: (
* @returns
*/
const sendEmailToCurrent: (loading?: Ref<boolean>) => Promise<Result<boolean>> = (loading) => {
return post('/user/current/send_email', undefined, undefined, loading)
return post('/user/current/send_email', undefined, undefined, loading)
}
/**
*
@ -90,10 +90,10 @@ const sendEmailToCurrent: (loading?: Ref<boolean>) => Promise<Result<boolean>> =
* @returns
*/
const resetCurrentUserPassword: (
request: ResetCurrentUserPasswordRequest,
loading?: Ref<boolean>
request: ResetCurrentUserPasswordRequest,
loading?: Ref<boolean>
) => Promise<Result<boolean>> = (request, loading) => {
return post('/user/current/reset_password', request, undefined, loading)
return post('/user/current/reset_password', request, undefined, loading)
}
/**
*
@ -101,7 +101,7 @@ const resetCurrentUserPassword: (
* @returns
*/
const profile: (loading?: Ref<boolean>) => Promise<Result<User>> = (loading) => {
return get('/user', undefined, loading)
return get('/user', undefined, loading)
}
/**
@ -111,10 +111,10 @@ const profile: (loading?: Ref<boolean>) => Promise<Result<User>> = (loading) =>
* @returns
*/
const resetPassword: (
request: ResetPasswordRequest,
loading?: Ref<boolean>
request: ResetPasswordRequest,
loading?: Ref<boolean>
) => Promise<Result<boolean>> = (request, loading) => {
return post('/user/re_password', request, undefined, loading)
return post('/user/re_password', request, undefined, loading)
}
/**
@ -123,17 +123,17 @@ const resetPassword: (
* email_or_username
*/
const getUserList: (email_or_username: string, loading?: Ref<boolean>) => Promise<Result<any>> = (
email_or_username,
loading
email_or_username,
loading
) => {
return get('/user/list', {email_or_username}, loading)
return get('/user/list', { email_or_username }, loading)
}
/**
* profile
*/
const getProfile: (loading?: Ref<boolean>) => Promise<Result<any>> = (loading) => {
return get('/profile', undefined, loading)
return get('/profile', undefined, loading)
}
/**
@ -142,31 +142,55 @@ const getProfile: (loading?: Ref<boolean>) => Promise<Result<any>> = (loading) =
* @param valid_count 校验数量: 5 | 50 | 2
*/
const getValid: (
valid_type: string,
valid_count: number,
loading?: Ref<boolean>
valid_type: string,
valid_count: number,
loading?: Ref<boolean>
) => Promise<Result<any>> = (valid_type, valid_count, loading) => {
return get(`/valid/${valid_type}/${valid_count}`, undefined, loading)
return get(`/valid/${valid_type}/${valid_count}`, undefined, loading)
}
/**
*
*/
const getAuthType: (loading?: Ref<boolean>) => Promise<Result<any>> = (loading) => {
return get('auth/types', undefined, loading)
return get('auth/types', undefined, loading)
}
/**
*
*/
const getQrType: (loading?: Ref<boolean>) => Promise<Result<any>> = (loading) => {
return get('qr_type', undefined, loading)
}
const getDingCallback: (code: string, loading?: Ref<boolean>) => Promise<Result<any>> = (
code,
loading
) => {
return get('dingtalk', { code }, loading)
}
const getWecomCallback: (code: string, loading?: Ref<boolean>) => Promise<Result<any>> = (
code,
loading
) => {
return get('wecom', { code }, loading)
}
export default {
login,
register,
sendEmit,
checkCode,
profile,
resetPassword,
sendEmailToCurrent,
resetCurrentUserPassword,
logout,
getUserList,
getProfile,
getValid,
getAuthType
login,
register,
sendEmit,
checkCode,
profile,
resetPassword,
sendEmailToCurrent,
resetCurrentUserPassword,
logout,
getUserList,
getProfile,
getValid,
getAuthType,
getDingCallback,
getQrType,
getWecomCallback
}

View File

@ -0,0 +1,3 @@
<svg width="19" height="18" viewBox="0 0 19 18" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M3.875 8.625H9.125V3.375H3.875V8.625ZM3.125 1.875H9.875C10.2892 1.875 10.625 2.21079 10.625 2.625V9.375C10.625 9.78921 10.2892 10.125 9.875 10.125H3.125C2.71079 10.125 2.375 9.78921 2.375 9.375V2.625C2.375 2.21079 2.71079 1.875 3.125 1.875ZM5.375 4.875H7.625V7.125H5.375V4.875ZM3.875 12.75V14.625H5.75V12.75H3.875ZM3.125 11.25H6.5C6.91421 11.25 7.25 11.5858 7.25 12V15.375C7.25 15.7892 6.91421 16.125 6.5 16.125H3.125C2.71079 16.125 2.375 15.7892 2.375 15.375V12C2.375 11.5858 2.71079 11.25 3.125 11.25ZM13.25 3.375V5.25H15.125V3.375H13.25ZM12.5 1.875H15.875C16.2892 1.875 16.625 2.21079 16.625 2.625V6C16.625 6.41421 16.2892 6.75 15.875 6.75H12.5C12.0858 6.75 11.75 6.41421 11.75 6V2.625C11.75 2.21079 12.0858 1.875 12.5 1.875ZM13.25 12.75V14.625H15.125V12.75H13.25ZM12.5 11.25H15.875C16.2892 11.25 16.625 11.5858 16.625 12V15.375C16.625 15.7892 16.2892 16.125 15.875 16.125H12.5C12.0858 16.125 11.75 15.7892 11.75 15.375V12C11.75 11.5858 12.0858 11.25 12.5 11.25ZM8.75 11.25H10.25V12.75H8.75V11.25ZM8.75 13.875H10.25V16.125H8.75V13.875ZM11.75 8.25H13.25V9.75H11.75V8.25ZM14.375 8.25H16.625V9.75H14.375V8.25Z" fill="#1F2329"/>
</svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

@ -1206,5 +1206,26 @@ export const iconMap: any = {
)
])
}
},
'app-invisible': {
iconReader: () => {
return h('i', [
h(
'svg',
{
style: { height: '100%', width: '100%' },
viewBox: '0 0 1024 1024',
version: '1.1',
xmlns: 'http://www.w3.org/2000/svg'
},
[
h('path', {
d: 'M512 640c-28.032 0-55.466667-2.218667-82.090667-6.4l-21.248 79.274667a21.333333 21.333333 0 0 1-26.154666 15.061333L341.333333 716.885333a21.333333 21.333333 0 0 1-15.061333-26.112l20.821333-77.653333a473.770667 473.770667 0 0 1-97.152-45.653333l-67.84 67.84a21.333333 21.333333 0 0 1-30.122666 0l-30.165334-30.208a21.333333 21.333333 0 0 1 0-30.165334l59.733334-59.733333A386.389333 386.389333 0 0 1 104.789333 416.426667a37.76 37.76 0 0 1 7.594667-45.397334c10.496-9.514667 17.877333-16 24.32-22.442666a170.24 170.24 0 0 0 1.834667-1.92c9.301333-9.6 25.173333-6.016 30.634666 6.186666C222.336 471.936 349.568 554.666667 512 554.666667c155.648 0 285.866667-80.512 338.090667-190.976 1.365333-2.858667 2.901333-6.485333 4.437333-10.325334a18.346667 18.346667 0 0 1 29.866667-6.613333l27.392 27.434667a36.565333 36.565333 0 0 1 6.997333 42.666666c-1.792 3.456-3.541333 6.698667-5.034667 9.301334a390.4 390.4 0 0 1-76.928 94.293333l54.442667 54.485333a21.333333 21.333333 0 0 1 0 30.165334l-30.165333 30.165333a21.333333 21.333333 0 0 1-30.165334 0l-63.658666-63.658667a475.306667 475.306667 0 0 1-90.282667 41.514667l20.778667 77.653333a21.333333 21.333333 0 0 1-15.061334 26.112l-41.216 11.093334a21.333333 21.333333 0 0 1-26.154666-15.104l-21.248-79.317334c-26.581333 4.266667-54.058667 6.442667-82.090667 6.442667z',
fill: 'currentColor'
})
]
)
])
}
}
}

View File

@ -129,6 +129,20 @@ const useUserStore = defineStore({
return this.profile()
})
},
async dingCallback(code: string) {
return UserApi.getDingCallback(code).then((ok) => {
this.token = ok.data
localStorage.setItem('token', ok.data)
return this.profile()
})
},
async wecomCallback(code: string) {
return UserApi.getWecomCallback(code).then((ok) => {
this.token = ok.data
localStorage.setItem('token', ok.data)
return this.profile()
})
},
async logout() {
return UserApi.logout().then(() => {
@ -140,6 +154,11 @@ const useUserStore = defineStore({
return UserApi.getAuthType().then((ok) => {
return ok.data
})
},
async getQrType() {
return UserApi.getQrType().then((ok) => {
return ok.data
})
}
}
})

View File

@ -274,6 +274,12 @@
padding: 0 14px;
}
.el-tabs__nav-wrap:after {
height: 1px;
}
.el-tabs__active-bar {
height: 3px;
}
.el-drawer {
.el-drawer__header {
padding: 16px 24px;

View File

@ -285,9 +285,6 @@ onBeforeUnmount(() => {
.el-tabs__nav-wrap {
padding: 0 16px;
}
.el-tabs__nav-wrap:after {
height: 1px;
}
}
}

View File

@ -1,36 +1,40 @@
<template>
<div class="p-24" v-loading="loading">
<el-form
ref="authFormRef"
:rules="rules"
:model="form"
label-position="top"
require-asterisk-position="right"
>
<el-form-item :label="$t('login.cas.ldpUri')" prop="config_data.ldpUri">
<el-input
v-model="form.config_data.ldpUri"
:placeholder="$t('login.cas.ldpUriPlaceholder')"
/>
</el-form-item>
<el-form-item :label="$t('login.cas.redirectUrl')" prop="config_data.redirectUrl">
<el-input
v-model="form.config_data.redirectUrl"
:placeholder="$t('login.cas.redirectUrlPlaceholder')"
/>
</el-form-item>
<el-form-item>
<el-checkbox v-model="form.is_active"
>{{ $t('login.cas.enableAuthentication') }}
</el-checkbox>
</el-form-item>
</el-form>
<div class="authentication-setting__main main-calc-height">
<el-scrollbar>
<div class="form-container p-24" v-loading="loading">
<el-form
ref="authFormRef"
:rules="rules"
:model="form"
label-position="top"
require-asterisk-position="right"
>
<el-form-item :label="$t('login.cas.ldpUri')" prop="config_data.ldpUri">
<el-input
v-model="form.config_data.ldpUri"
:placeholder="$t('login.cas.ldpUriPlaceholder')"
/>
</el-form-item>
<el-form-item :label="$t('login.cas.redirectUrl')" prop="config_data.redirectUrl">
<el-input
v-model="form.config_data.redirectUrl"
:placeholder="$t('login.cas.redirectUrlPlaceholder')"
/>
</el-form-item>
<el-form-item>
<el-checkbox v-model="form.is_active"
>{{ $t('login.cas.enableAuthentication') }}
</el-checkbox>
</el-form-item>
</el-form>
<div class="text-right">
<el-button @click="submit(authFormRef)" type="primary" :disabled="loading">
{{ $t('login.cas.save') }}
</el-button>
</div>
<div class="text-right">
<el-button @click="submit(authFormRef)" type="primary" :disabled="loading">
{{ $t('login.cas.save') }}
</el-button>
</div>
</div>
</el-scrollbar>
</div>
</template>
<script setup lang="ts">

View File

@ -0,0 +1,157 @@
template
<template>
<el-drawer v-model="visible" size="60%" :append-to-body="true">
<template #header>
<div class="flex align-center" style="margin-left: -8px">
<h4>{{ currentPlatform.name + '设置' }}</h4>
</div>
</template>
<el-form
:model="currentPlatform.config"
label-width="120px"
label-position="top"
require-asterisk-position="right"
ref="formRef"
>
<el-form-item
v-for="(value, key) in currentPlatform.config"
:key="key"
:label="formatFieldName(key)"
:prop="key"
:rules="getValidationRules(key)"
>
<el-input v-model="currentPlatform.config[key]" :default-value="''"></el-input>
</el-form-item>
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button @click="visible = false"> </el-button>
<el-button @click="validateConnection"> </el-button>
<el-button type="primary" @click="validateForm"> </el-button>
</span>
</template>
</el-drawer>
</template>
<script setup lang="ts">
import { reactive, ref } from 'vue'
import { ElForm } from 'element-plus'
import platformApi from '@/api/platform-source'
import { MsgError, MsgSuccess } from '@/utils/message'
const visible = ref(false)
const loading = ref(false)
const formRef = ref<InstanceType<typeof ElForm>>()
interface PlatformConfig {
[key: string]: string
}
interface Platform {
key: string
logoSrc: string
name: string
isActive: boolean
isValid: boolean
config: PlatformConfig
}
const currentPlatform = reactive<Platform>({
key: '',
logoSrc: '',
name: '',
isActive: false,
isValid: false,
config: {}
})
const formatFieldName = (key?: string): string => {
const fieldNames: { [key: string]: string } = {
app_key: 'APP Key',
app_secret: 'APP Secret',
corp_id: 'Corp ID',
agent_id: 'Agent ID',
callback_url: '回调地址'
}
return (
fieldNames[key as keyof typeof fieldNames] ||
(key ? key.charAt(0).toUpperCase() + key.slice(1) : '')
)
}
const getValidationRules = (key: string) => {
switch (key) {
case 'app_key':
return [{ required: true, message: '请输入 APP Key', trigger: ['blur', 'change'] }]
case 'app_secret':
return [{ required: true, message: '请输入 APP Secret', trigger: ['blur', 'change'] }]
case 'corp_id':
return [{ required: true, message: '请输入 Corp ID', trigger: ['blur', 'change'] }]
case 'agent_id':
return [{ required: true, message: '请输入 Agent ID', trigger: ['blur', 'change'] }]
case 'callback_url':
return [
{ required: true, message: '请输入回调地址', trigger: ['blur', 'change'] },
{ pattern: /^https?:\/\/.+/, message: '请输入有效的 URL 地址', trigger: ['blur', 'change'] }
]
default:
return []
}
}
const open = async (platform: Platform) => {
visible.value = true
loading.value = true
Object.assign(currentPlatform, platform)
// callback_url
let defaultCallbackUrl = window.location.origin
// callback_url
switch (platform.key) {
case 'wecom':
case 'dingtalk':
currentPlatform.config.callback_url = defaultCallbackUrl
break
case 'lark':
currentPlatform.config.callback_url = `${defaultCallbackUrl}/api/feishu`
break
default:
break
}
}
defineExpose({ open })
const validateForm = () => {
formRef.value?.validate((valid) => {
if (valid) {
saveConfig()
} else {
MsgError('请填写所有必填项并确保格式正确')
}
})
}
function validateConnection() {
platformApi.validateConnection(currentPlatform, loading).then((res: any) => {
if (res.data) {
MsgSuccess('校验成功')
} else {
MsgError('校验失败')
}
})
}
function saveConfig() {
platformApi.updateConfig(currentPlatform, loading).then((res: any) => {
MsgSuccess('保存成功')
visible.value = false
})
}
</script>
<style lang="scss" scoped>
//
</style>

View File

@ -1,61 +1,65 @@
<template>
<div class="p-24" v-loading="loading">
<el-form
ref="authFormRef"
:rules="rules"
:model="form"
label-position="top"
require-asterisk-position="right"
>
<el-form-item :label="$t('login.ldap.address')" prop="config_data.ldap_server">
<el-input
v-model="form.config_data.ldap_server"
:placeholder="$t('login.ldap.serverPlaceholder')"
/>
</el-form-item>
<el-form-item :label="$t('login.ldap.bindDN')" prop="config_data.base_dn">
<el-input
v-model="form.config_data.base_dn"
:placeholder="$t('login.ldap.bindDNPlaceholder')"
/>
</el-form-item>
<el-form-item :label="$t('login.ldap.password')" prop="config_data.password">
<el-input
v-model="form.config_data.password"
:placeholder="$t('login.ldap.passwordPlaceholder')"
show-password
/>
</el-form-item>
<el-form-item :label="$t('login.ldap.ou')" prop="config_data.ou">
<el-input v-model="form.config_data.ou" :placeholder="$t('login.ldap.ouPlaceholder')" />
</el-form-item>
<el-form-item :label="$t('login.ldap.ldap_filter')" prop="config_data.ldap_filter">
<el-input
v-model="form.config_data.ldap_filter"
:placeholder="$t('login.ldap.ldap_filterPlaceholder')"
/>
</el-form-item>
<el-form-item :label="$t('login.ldap.ldap_mapping')" prop="config_data.ldap_mapping">
<el-input
v-model="form.config_data.ldap_mapping"
placeholder='{"name":"name","email":"mail","username":"cn"}'
/>
</el-form-item>
<el-form-item>
<el-checkbox v-model="form.is_active">{{
$t('login.ldap.enableAuthentication')
}}</el-checkbox>
</el-form-item>
<el-button @click="submit(authFormRef, 'test')" :disabled="loading">
{{ $t('login.ldap.test') }}</el-button
>
</el-form>
<div class="authentication-setting__main main-calc-height">
<el-scrollbar>
<div class="form-container p-24" v-loading="loading">
<el-form
ref="authFormRef"
:rules="rules"
:model="form"
label-position="top"
require-asterisk-position="right"
>
<el-form-item :label="$t('login.ldap.address')" prop="config_data.ldap_server">
<el-input
v-model="form.config_data.ldap_server"
:placeholder="$t('login.ldap.serverPlaceholder')"
/>
</el-form-item>
<el-form-item :label="$t('login.ldap.bindDN')" prop="config_data.base_dn">
<el-input
v-model="form.config_data.base_dn"
:placeholder="$t('login.ldap.bindDNPlaceholder')"
/>
</el-form-item>
<el-form-item :label="$t('login.ldap.password')" prop="config_data.password">
<el-input
v-model="form.config_data.password"
:placeholder="$t('login.ldap.passwordPlaceholder')"
show-password
/>
</el-form-item>
<el-form-item :label="$t('login.ldap.ou')" prop="config_data.ou">
<el-input v-model="form.config_data.ou" :placeholder="$t('login.ldap.ouPlaceholder')" />
</el-form-item>
<el-form-item :label="$t('login.ldap.ldap_filter')" prop="config_data.ldap_filter">
<el-input
v-model="form.config_data.ldap_filter"
:placeholder="$t('login.ldap.ldap_filterPlaceholder')"
/>
</el-form-item>
<el-form-item :label="$t('login.ldap.ldap_mapping')" prop="config_data.ldap_mapping">
<el-input
v-model="form.config_data.ldap_mapping"
placeholder='{"name":"name","email":"mail","username":"cn"}'
/>
</el-form-item>
<el-form-item>
<el-checkbox v-model="form.is_active">{{
$t('login.ldap.enableAuthentication')
}}</el-checkbox>
</el-form-item>
<el-button @click="submit(authFormRef, 'test')" :disabled="loading">
{{ $t('login.ldap.test') }}</el-button
>
</el-form>
<div class="text-right">
<el-button @click="submit(authFormRef)" type="primary" :disabled="loading">
{{ $t('login.ldap.save') }}
</el-button>
</div>
<div class="text-right">
<el-button @click="submit(authFormRef)" type="primary" :disabled="loading">
{{ $t('login.ldap.save') }}
</el-button>
</div>
</div>
</el-scrollbar>
</div>
</template>
<script setup lang="ts">

View File

@ -1,61 +1,68 @@
<template>
<div class="p-24" v-loading="loading">
<el-form
ref="authFormRef"
:rules="rules"
:model="form"
label-position="top"
require-asterisk-position="right"
>
<el-form-item :label="$t('login.oidc.authEndpoint')" prop="config_data.authEndpoint">
<el-input
v-model="form.config_data.authEndpoint"
:placeholder="$t('login.oidc.authEndpointPlaceholder')"
/>
</el-form-item>
<el-form-item :label="$t('login.oidc.tokenEndpoint')" prop="config_data.tokenEndpoint">
<el-input
v-model="form.config_data.tokenEndpoint"
:placeholder="$t('login.oidc.tokenEndpointPlaceholder')"
/>
</el-form-item>
<el-form-item :label="$t('login.oidc.userInfoEndpoint')" prop="config_data.userInfoEndpoint">
<el-input
v-model="form.config_data.userInfoEndpoint"
:placeholder="$t('login.oidc.userInfoEndpointPlaceholder')"
/>
</el-form-item>
<el-form-item :label="$t('login.oidc.clientId')" prop="config_data.clientId">
<el-input
v-model="form.config_data.clientId"
:placeholder="$t('login.oidc.clientIdPlaceholder')"
/>
</el-form-item>
<el-form-item :label="$t('login.oidc.clientSecret')" prop="config_data.clientSecret">
<el-input
v-model="form.config_data.clientSecret"
:placeholder="$t('login.oidc.clientSecretPlaceholder')"
show-password
/>
</el-form-item>
<el-form-item :label="$t('login.oidc.redirectUrl')" prop="config_data.redirectUrl">
<el-input
v-model="form.config_data.redirectUrl"
:placeholder="$t('login.oidc.redirectUrlPlaceholder')"
/>
</el-form-item>
<el-form-item>
<el-checkbox v-model="form.is_active"
>{{ $t('login.oidc.enableAuthentication') }}
</el-checkbox>
</el-form-item>
</el-form>
<div class="authentication-setting__main main-calc-height">
<el-scrollbar>
<div class="form-container p-24" v-loading="loading">
<el-form
ref="authFormRef"
:rules="rules"
:model="form"
label-position="top"
require-asterisk-position="right"
>
<el-form-item :label="$t('login.oidc.authEndpoint')" prop="config_data.authEndpoint">
<el-input
v-model="form.config_data.authEndpoint"
:placeholder="$t('login.oidc.authEndpointPlaceholder')"
/>
</el-form-item>
<el-form-item :label="$t('login.oidc.tokenEndpoint')" prop="config_data.tokenEndpoint">
<el-input
v-model="form.config_data.tokenEndpoint"
:placeholder="$t('login.oidc.tokenEndpointPlaceholder')"
/>
</el-form-item>
<el-form-item
:label="$t('login.oidc.userInfoEndpoint')"
prop="config_data.userInfoEndpoint"
>
<el-input
v-model="form.config_data.userInfoEndpoint"
:placeholder="$t('login.oidc.userInfoEndpointPlaceholder')"
/>
</el-form-item>
<el-form-item :label="$t('login.oidc.clientId')" prop="config_data.clientId">
<el-input
v-model="form.config_data.clientId"
:placeholder="$t('login.oidc.clientIdPlaceholder')"
/>
</el-form-item>
<el-form-item :label="$t('login.oidc.clientSecret')" prop="config_data.clientSecret">
<el-input
v-model="form.config_data.clientSecret"
:placeholder="$t('login.oidc.clientSecretPlaceholder')"
show-password
/>
</el-form-item>
<el-form-item :label="$t('login.oidc.redirectUrl')" prop="config_data.redirectUrl">
<el-input
v-model="form.config_data.redirectUrl"
:placeholder="$t('login.oidc.redirectUrlPlaceholder')"
/>
</el-form-item>
<el-form-item>
<el-checkbox v-model="form.is_active"
>{{ $t('login.oidc.enableAuthentication') }}
</el-checkbox>
</el-form-item>
</el-form>
<div class="text-right">
<el-button @click="submit(authFormRef)" type="primary" :disabled="loading">
{{ $t('login.ldap.save') }}
</el-button>
</div>
<div class="text-right">
<el-button @click="submit(authFormRef)" type="primary" :disabled="loading">
{{ $t('login.ldap.save') }}
</el-button>
</div>
</div>
</el-scrollbar>
</div>
</template>
<script setup lang="ts">

View File

@ -0,0 +1,250 @@
<template>
<div v-loading="loading">
<el-card shadow="hover" class="border-none cursor" style="--el-card-padding: 16px 24px">
<div v-for="item in platforms" :key="item.key" class="mb-16">
<el-card class="mb-16" shadow="hover" style="--el-card-padding: 16px">
<div class="flex-between">
<div class="flex align-center ml-8 mr-8">
<img :src="item.logoSrc" alt="" class="icon" />
<h5 style="margin-left: 8px">{{ item.name }}</h5>
<el-tag v-if="item.isValid" type="success" class="ml-4">有效</el-tag>
</div>
<div>
<el-button type="primary" v-if="!item.isValid" @click="showDialog(item)"
>接入</el-button
>
<span v-if="item.isValid">
<span class="mr-4">{{ item.isActive ? '已开启' : '未开启' }}</span>
<el-switch
size="small"
v-model="item.isActive"
:disabled="!item.isValid"
@change="changeStatus(item)"
/>
</span>
</div>
</div>
<el-collapse-transition>
<div v-if="item.isValid" class="border-t mt-16">
<el-row :gutter="12" class="mt-16">
<el-col v-for="(value, key) in item.config" :key="key" :span="12">
<el-text type="info">{{ formatFieldName(key) }}</el-text>
<div class="mt-4 mb-16 flex align-center">
<span
v-if="key !== 'app_secret'"
class="vertical-middle lighter break-all ellipsis-1"
>{{ value }}</span
>
<span
v-if="key === 'app_secret' && !showPassword[item.key]?.[key]"
class="vertical-middle lighter break-all ellipsis-1"
>************</span
>
<span
v-if="key === 'app_secret' && showPassword[item.key]?.[key]"
class="vertical-middle lighter break-all ellipsis-1"
>{{ value }}</span
>
<el-button type="primary" text @click="() => copyClick(value)">
<AppIcon iconName="app-copy" />
</el-button>
<el-button
v-if="key === 'app_secret'"
type="primary"
text
@click="toggleShowPassword(item.key)"
>
<el-icon>
<Hide />
</el-icon>
</el-button>
</div>
</el-col>
</el-row>
<el-button type="primary" @click="showDialog(item)">编辑</el-button>
<el-button @click="validateConnection(item)">校验</el-button>
</div>
</el-collapse-transition>
</el-card>
</div>
<EditModel ref="EditModelRef" @refresh="refresh" />
</el-card>
</div>
</template>
<script setup lang="ts">
import { reactive, ref, onMounted } from 'vue'
import { copyClick } from '@/utils/clipboard'
import EditModel from './EditModal.vue'
import platformApi from '@/api/platform-source'
import { MsgError, MsgSuccess } from '@/utils/message'
interface PlatformConfig {
[key: string]: string
}
interface Platform {
key: string
logoSrc: string
name: string
isActive: boolean
isValid: boolean
config: PlatformConfig
}
const EditModelRef = ref()
const loading = ref(false)
const platforms = reactive<Platform[]>(initializePlatforms())
const showPassword = reactive<{ [platformKey: string]: { [key: string]: boolean } }>({})
onMounted(() => {
getPlatformInfo()
})
function initializePlatforms(): Platform[] {
return [
createPlatform('wecom', '企业微信'),
createPlatform('dingtalk', '钉钉'),
createPlatform('lark', '飞书')
]
}
function createPlatform(key: string, name: string): Platform {
let logo = ''
switch (key) {
case 'wecom':
logo = 'wechat-work'
break
case 'dingtalk':
logo = 'dingtalk'
break
case 'lark':
logo = 'lark'
break
default:
logo = '' //
break
}
const config = {
...(key === 'wecom' ? { corp_id: '', agent_id: '' } : { app_key: '' }),
...(key === 'dingtalk' ? { agent_id: '' } : {}),
app_secret: '',
callback_url: ''
}
return {
key,
logoSrc: new URL(`../../../assets/logo_${logo}.svg`, import.meta.url).href,
name,
isActive: false,
isValid: false,
config
}
}
function formatFieldName(key?: string): string {
const fieldNames: { [key: string]: string } = {
app_key: 'APP Key',
app_secret: 'APP Secret',
corp_id: 'Corp ID',
agent_id: 'Agent ID',
callback_url: '回调地址'
}
return (
fieldNames[key as keyof typeof fieldNames] ||
(key ? key.charAt(0).toUpperCase() + key.slice(1) : '')
)
}
function getPlatformInfo() {
platformApi.getPlatformInfo(loading).then((res: any) => {
if (res) {
platforms.forEach((platform) => {
const data = res.data.find((item: any) => item.platform === platform.key)
if (data) {
Object.assign(platform, {
isValid: data.is_valid,
isActive: data.is_active,
config: data.config
})
showPassword[platform.key] = {}
showPassword[platform.key]['app_secret'] = false
}
})
}
})
}
function validateConnection(currentPlatform: Platform) {
platformApi.validateConnection(currentPlatform, loading).then((res: any) => {
res.data ? MsgSuccess('校验成功') : MsgError('校验失败')
})
}
function refresh() {
getPlatformInfo()
}
function changeStatus(currentPlatform: Platform) {
platformApi.updateConfig(currentPlatform, loading).then((res: any) => {
MsgSuccess('操作成功')
})
}
function toggleShowPassword(platformKey: string) {
if (!showPassword[platformKey]) {
showPassword[platformKey] = {}
}
showPassword[platformKey]['app_secret'] = !showPassword[platformKey]['app_secret']
}
function showDialog(platform: Platform) {
EditModelRef.value?.open(platform)
}
</script>
<style lang="scss" scoped>
.icon {
width: 24px;
height: 24px;
}
.flex-between {
display: flex;
justify-content: space-between;
align-items: center;
}
.flex {
display: flex;
}
.align-center {
align-items: center;
}
.ml-4 {
margin-left: 4px;
}
.ml-8 {
margin-left: 8px;
}
.mr-8 {
margin-right: 8px;
}
.mb-16 {
margin-bottom: 16px;
}
.border-t {
border-top: 1px solid #ebeef5;
}
.vertical-middle {
vertical-align: middle;
}
</style>

View File

@ -4,13 +4,7 @@
<el-tabs v-model="activeName" class="demo-tabs" @tab-click="handleClick">
<template v-for="(item, index) in tabList" :key="index">
<el-tab-pane :label="item.label" :name="item.name">
<div class="authentication-setting__main main-calc-height">
<el-scrollbar>
<div class="form-container">
<component :is="item.component" />
</div>
</el-scrollbar>
</div>
<component :is="item.component" />
</el-tab-pane>
</template>
</el-tabs>
@ -22,6 +16,7 @@ import { useRouter } from 'vue-router'
import LDAP from './component/LDAP.vue'
import CAS from './component/CAS.vue'
import OIDC from './component/OIDC.vue'
import SCAN from './component/SCAN.vue'
import { t } from '@/locales'
import useStore from '@/stores'
@ -44,6 +39,11 @@ const tabList = [
label: t('login.oidc.title'),
name: 'OIDC',
component: OIDC
},
{
label: '扫码登录',
name: 'SCAN',
component: SCAN
}
]
@ -62,12 +62,9 @@ onMounted(() => {
min-width: 700px;
height: calc(100vh - var(--app-header-height) - var(--app-view-padding) * 2 - 70px);
box-sizing: border-box;
.form-container {
:deep(.form-container) {
width: 70%;
margin: 0 auto;
:deep(.el-checkbox__label) {
font-weight: 400;
}
}
}
</style>

View File

@ -0,0 +1,76 @@
<template>
<el-tabs v-model="activeKey" @tab-change="selectTab">
<template v-for="item in tabs" :key="item.key">
<el-tab-pane :label="item.value" :name="item.key">
<div class="text-center mt-16" v-if="item.key === activeKey">
<component
:is="defineAsyncComponent(() => import(`./${item.key}QrCode.vue`))"
:config="config"
/>
</div>
</el-tab-pane>
</template>
</el-tabs>
</template>
<script setup lang="ts">
import { defineProps, onMounted, ref, defineAsyncComponent } from 'vue'
import platformApi from '@/api/platform-source'
interface Tab {
key: string
value: string
}
interface PlatformConfig {
app_key: string
app_secret: string
platform: string
config: any
}
interface Config {
app_key: string
app_secret: string
corpId?: string
agentId?: string
}
const props = defineProps<{ tabs: Tab[] }>()
const activeKey = ref('')
const allConfigs = ref<PlatformConfig[]>([])
const config = ref<Config>({ app_key: '', app_secret: '' })
// const logoUrl = ref('')
async function getPlatformInfo() {
try {
const res = await platformApi.getPlatformInfo()
return res.data
} catch (error) {
return []
}
}
onMounted(async () => {
if (props.tabs.length > 0) {
activeKey.value = props.tabs[0].key
}
allConfigs.value = await getPlatformInfo()
updateConfig(activeKey.value)
})
const updateConfig = (key: string) => {
const selectedConfig = allConfigs.value.find((item) => item.platform === key)
if (selectedConfig && selectedConfig.config) {
config.value = selectedConfig.config
}
}
const selectTab = (key: string) => {
activeKey.value = key
updateConfig(key)
}
</script>
<style scoped lang="scss"></style>

View File

@ -0,0 +1,136 @@
<template>
<div class="flex-center mb-16">
<img src="@/assets/logo_dingtalk.svg " alt="" width="24px" class="mr-4" />
<h2>钉钉扫码登录</h2>
</div>
<div class="ding-talk-qrName">
<div id="ding-talk-qr"></div>
</div>
</template>
<script lang="ts" setup>
import { useRouter } from 'vue-router'
import { useScriptTag } from '@vueuse/core'
import { defineProps, onMounted, ref } from 'vue'
import useStore from '@/stores'
// DTFrameLogin QRLogin
declare global {
interface Window {
DTFrameLogin: (
frameParams: IDTLoginFrameParams,
loginParams: IDTLoginLoginParams,
successCbk: (result: IDTLoginSuccess) => void,
errorCbk?: (errorMsg: string) => void
) => void
QRLogin: (QRLogin: qrLogin) => Record<any, any>
}
}
//
interface IDTLoginFrameParams {
id: string
width?: number
height?: number
}
interface IDTLoginLoginParams {
redirect_uri: string
response_type: string
client_id: string
scope: string
prompt: string
state?: string
org_type?: string
corpId?: string
exclusiveLogin?: string
exclusiveCorpId?: string
}
interface IDTLoginSuccess {
redirectUrl: string
authCode: string
state?: string
}
interface qrLogin {
id: string
goto: string
width: string
height: string
style?: string
}
const props = defineProps<{
config: {
app_secret: string
app_key: string
}
}>()
const router = useRouter()
const { user } = useStore()
const { load } = useScriptTag('https://g.alicdn.com/dingding/h5-dingtalk-login/0.21.0/ddlogin.js')
const isConfigReady = ref(false)
const initActive = async () => {
try {
await load(true)
if (!isConfigReady.value) {
return
}
const data = {
appKey: props.config.app_key,
appSecret: props.config.app_secret
}
const redirectUri = encodeURIComponent(window.location.origin)
window.DTFrameLogin(
{
id: 'ding-talk-qr',
width: 280,
height: 280
},
{
redirect_uri: redirectUri,
client_id: data.appKey || '',
scope: 'openid',
response_type: 'code',
state: 'fit2cloud-ding-qr',
prompt: 'consent'
},
(loginResult) => {
const authCode = loginResult.authCode
user.dingCallback(authCode).then(() => {
router.push({ name: 'home' })
})
},
(errorMsg: string) => {
console.error(errorMsg)
}
)
} catch (error) {
console.error(error)
}
}
onMounted(() => {
// config
setTimeout(() => {
isConfigReady.value = true
initActive()
}, 1000)
})
</script>
<style lang="scss">
.ding-talk-qrName {
border: 1px solid #e8e8e8;
border-radius: 8px;
height: 280px;
width: 280px;
margin: 0 auto;
}
</style>

View File

@ -0,0 +1,59 @@
<template>
<div class="flex-center mb-16">
<img src="@/assets/logo_lark.svg " alt="" width="24px" class="mr-4" />
<h2>飞书扫码登录</h2>
</div>
<div id="lark-qr" class="lark-qrName"></div>
</template>
<script lang="ts" setup>
import { useScriptTag } from '@vueuse/core'
import { defineProps, onMounted } from 'vue'
const { load } = useScriptTag(
'https://lf-package-cn.feishucdn.com/obj/feishu-static/lark/passport/qrcode/LarkSSOSDKWebQRCode-1.0.3.js'
)
const props = defineProps<{
config: {
app_secret: string
app_key: string
}
}>()
const initActive = async () => {
const scriptLoaded = await load(true)
if (!scriptLoaded) {
console.error('飞书二维码 SDK 加载失败')
return
}
const data = {
agentId: props.config.app_key,
appSecret: props.config.app_secret
}
const redirectUrl = encodeURIComponent(`${window.location.origin}/api/feishu`)
const url = `https://passport.feishu.cn/suite/passport/oauth/authorize?client_id=${data.agentId}&redirect_uri=${redirectUrl}&response_type=code&state=fit2cloud-lark-qr`
const QRLoginObj = window.QRLogin({
id: 'lark-qr',
goto: url,
width: '266',
height: '266',
style: 'width:280px;height:280px;border:1px solid #e8e8e8;margin:0 auto;border-radius:8px;'
})
window.addEventListener('message', async (event: any) => {
if (QRLoginObj.matchOrigin(event.origin) && QRLoginObj.matchData(event.data)) {
const loginTmpCode = event.data.tmp_code
window.location.href = `${url}&tmp_code=${loginTmpCode}`
}
})
}
onMounted(() => {
initActive()
})
</script>
<style scoped lang="scss"></style>

View File

@ -0,0 +1,68 @@
<template>
<div id="wecom-qr" class="wecom-qr"></div>
</template>
<script lang="ts" setup>
import { useRouter } from 'vue-router'
import * as ww from '@wecom/jssdk'
import { WWLoginPanelSizeType, WWLoginRedirectType, WWLoginType } from '@wecom/jssdk'
import { ref, nextTick, defineProps } from 'vue'
import { MsgError } from '@/utils/message'
import useStore from '@/stores'
const router = useRouter()
const wwLogin = ref({})
const obj = ref<any>({ isWeComLogin: false })
const { user } = useStore()
const props = defineProps<{
config: {
app_secret: string
app_key: string
corp_id?: string
agent_id?: string
}
}>()
const init = async () => {
await nextTick() // DOM
const data = {
corpId: props.config.corp_id,
agentId: props.config.agent_id
}
const redirectUri = window.location.origin
try {
wwLogin.value = ww.createWWLoginPanel({
el: '#wecom-qr',
params: {
login_type: WWLoginType.corpApp,
appid: data.corpId || '',
agentid: data.agentId,
redirect_uri: redirectUri,
state: 'fit2cloud-wecom-qr',
redirect_type: WWLoginRedirectType.callback,
panel_size: WWLoginPanelSizeType.small
},
onCheckWeComLogin: obj.value,
async onLoginSuccess({ code }: any) {
console.log('Login success:', code)
user.wecomCallback(code).then(() => {
setTimeout(() => {
router.push({ name: 'home' })
})
})
},
onLoginFail(err) {
MsgError(`errorMsg of errorCbk: ${err.errMsg}`)
}
})
} catch (error) {
console.error('Error initializing login panel:', error)
}
}
init()
</script>
<style scoped lang="scss"></style>

View File

@ -1,52 +1,58 @@
<template>
<login-layout v-if="user.isEnterprise() ? user.themeInfo : true" v-loading="loading">
<LoginContainer :subTitle="user.themeInfo?.slogan || '欢迎使用 MaxKB 智能知识库问答系统'">
<h2 class="mb-24">{{ loginMode || '普通登录' }}</h2>
<el-form
<h2 class="mb-24" v-if="!showQrCodeTab">{{ loginMode || '普通登录' }}</h2>
<div v-if="!showQrCodeTab">
<el-form
class="login-form"
:rules="rules"
:model="loginForm"
ref="loginFormRef"
@keyup.enter="login"
>
<div class="mb-24">
<el-form-item prop="username">
<el-input
>
<div class="mb-24">
<el-form-item prop="username">
<el-input
size="large"
class="input-item"
v-model="loginForm.username"
placeholder="请输入用户名"
>
</el-input>
</el-form-item>
</div>
<div class="mb-24">
<el-form-item prop="password">
<el-input
>
</el-input>
</el-form-item>
</div>
<div class="mb-24">
<el-form-item prop="password">
<el-input
type="password"
size="large"
class="input-item"
v-model="loginForm.password"
placeholder="请输入密码"
show-password
>
</el-input>
</el-form-item>
</div>
</el-form>
<el-button size="large" type="primary" class="w-full" @click="login">登录</el-button>
<div class="operate-container flex-between mt-12">
<!-- <el-button class="register" @click="router.push('/register')" link type="primary">
>
</el-input>
</el-form-item>
</div>
</el-form>
<el-button size="large" type="primary" class="w-full" @click="login">登录</el-button>
<div class="operate-container flex-between mt-12">
<!-- <el-button class="register" @click="router.push('/register')" link type="primary">
注册
</el-button> -->
<el-button
<el-button
class="forgot-password"
@click="router.push('/forgot_password')"
link
type="primary"
>
忘记密码?
</el-button>
>
忘记密码?
</el-button>
</div>
</div>
<div v-if="showQrCodeTab">
<QrCodeTab :tabs="orgOptions" />
</div>
<div class="login-gradient-divider lighter mt-24" v-if="modeList.length > 1">
@ -55,21 +61,30 @@
<div class="text-center mt-16">
<template v-for="item in modeList">
<el-button
v-if="item !== '' && loginMode !== item"
circle
:key="item"
class="login-button-circle color-secondary"
@click="changeMode(item)"
>{{ item }}
v-if="item !== '' && loginMode !== item && item !== 'QR_CODE'"
circle
:key="item"
class="login-button-circle color-secondary"
@click="changeMode(item)"
>{{ item }}
</el-button>
<el-button
v-if="item === '' && loginMode !== ''"
circle
:key="item"
class="login-button-circle color-secondary"
style="font-size: 24px"
icon="UserFilled"
@click="changeMode('')"
v-if="item === 'QR_CODE' && loginMode !== item"
circle
:key="item"
class="login-button-circle color-secondary"
@click="changeMode('QR_CODE')"
>
<img src="@/assets/icon_qr_outlined.svg" width="25px" />
</el-button>
<el-button
v-if="item === '' && loginMode !== ''"
circle
:key="item"
class="login-button-circle color-secondary"
style="font-size: 24px"
icon="UserFilled"
@click="changeMode('')"
/>
</template>
</div>
@ -77,18 +92,17 @@
</login-layout>
</template>
<script setup lang="ts">
import {onMounted, ref, onBeforeMount} from 'vue'
import type {LoginRequest} from '@/api/type/user'
import {useRouter} from 'vue-router'
import type {FormInstance, FormRules} from 'element-plus'
import { onMounted, ref, onBeforeMount } from 'vue'
import type { LoginRequest } from '@/api/type/user'
import { useRouter } from 'vue-router'
import type { FormInstance, FormRules } from 'element-plus'
import useStore from '@/stores'
import authApi from "@/api/auth-setting";
import {MsgConfirm, MsgSuccess} from "@/utils/message";
import {t} from "@/locales";
import systemKeyApi from "@/api/system-api-key";
import authApi from '@/api/auth-setting'
import { MsgConfirm, MsgSuccess } from '@/utils/message'
import { t } from '@/locales'
import QrCodeTab from '@/views/login/components/QrCodeTab.vue'
const loading = ref<boolean>(false)
const {user} = useStore()
const { user } = useStore()
const router = useRouter()
const loginForm = ref<LoginRequest>({
username: '',
@ -114,50 +128,56 @@ const rules = ref<FormRules<LoginRequest>>({
const loginFormRef = ref<FormInstance>()
const modeList = ref<string[]>([''])
const QrList = ref<any[]>([''])
const loginMode = ref('')
const showQrCodeTab = ref(false)
interface qrOption {
key: string
value: string
}
const orgOptions = ref<qrOption[]>([])
function redirectAuth(authType: string) {
if (authType === 'LDAP' || authType === '') {
return;
return
}
authApi.getAuthSetting(authType, loading).then((res: any) => {
if (!res.data) {
return;
return
}
MsgConfirm(
`${t('login.jump_tip')}`,
t(''),
{
confirmButtonText: t('login.jump'),
cancelButtonText: t('views.applicationOverview.appInfo.APIKeyDialog.cancel'),
confirmButtonClass: ''
MsgConfirm(`${t('login.jump_tip')}`, t(''), {
confirmButtonText: t('login.jump'),
cancelButtonText: t('views.applicationOverview.appInfo.APIKeyDialog.cancel'),
confirmButtonClass: ''
})
.then(() => {
if (!res.data.config_data) {
return
}
)
.then(() => {
if (!res.data.config_data) {
return;
}
const config = res.data.config_data
const redirectUrl = eval(`\`${config.redirectUrl}\``);
let url;
if (authType === 'CAS') {
url = `${config.ldpUri}?service=${encodeURIComponent(redirectUrl)}`;
}
if (authType === 'OIDC') {
url = `${config.authEndpoint}?client_id=${config.clientId}&redirect_uri=${redirectUrl}&response_type=code&scope=openid+profile+email`;
}
if (url) {
window.location.href = url;
}
})
.catch(() => {
})
});
const config = res.data.config_data
const redirectUrl = eval(`\`${config.redirectUrl}\``)
let url
if (authType === 'CAS') {
url = `${config.ldpUri}?service=${encodeURIComponent(redirectUrl)}`
}
if (authType === 'OIDC') {
url = `${config.authEndpoint}?client_id=${config.clientId}&redirect_uri=${redirectUrl}&response_type=code&scope=openid+profile+email`
}
if (url) {
window.location.href = url
}
})
.catch(() => {})
})
}
function changeMode(val: string) {
loginMode.value = val === 'LDAP' ? val : '';
loginMode.value = val === 'LDAP' ? val : ''
if (val === 'QR_CODE') {
loginMode.value = val
showQrCodeTab.value = true
return
}
showQrCodeTab.value = false
loginForm.value = {
username: '',
password: ''
@ -170,11 +190,11 @@ const login = () => {
loginFormRef.value?.validate().then(() => {
loading.value = true
user
.login(loginMode.value, loginForm.value.username, loginForm.value.password)
.then(() => {
router.push({name: 'home'})
})
.finally(() => (loading.value = false))
.login(loginMode.value, loginForm.value.username, loginForm.value.password)
.then(() => {
router.push({ name: 'home' })
})
.finally(() => (loading.value = false))
})
}
@ -183,11 +203,26 @@ onMounted(() => {
if (user.isEnterprise()) {
loading.value = true
user
.getAuthType()
.then((res) => {
modeList.value = [...modeList.value, ...res]
})
.finally(() => (loading.value = false))
.getAuthType()
.then((res) => {
modeList.value = [...modeList.value, ...res]
})
.finally(() => (loading.value = false))
user
.getQrType()
.then((res) => {
if (res.length > 0) {
modeList.value = ['QR_CODE', ...modeList.value]
QrList.value = res
QrList.value.forEach((item) => {
orgOptions.value.push({
key: item,
value: item === 'wecom' ? '企业微信' : item === 'dingtalk' ? '钉钉' : '飞书'
})
})
}
})
.finally(() => (loading.value = false))
}
})
})
@ -225,6 +260,10 @@ onBeforeMount(() => {
}
.login-button-circle {
padding: 25px !important;
padding: 20px !important;
margin: 0 4px;
width: 32px;
height: 32px;
text-align: center;
}
</style>

View File

@ -267,15 +267,10 @@ onMounted(() => {
&__tabs {
margin-top: 10px;
:deep(.el-tabs__nav-wrap::after) {
height: 1px;
}
:deep(.el-tabs__nav-scroll) {
padding: 0 24px;
}
:deep(.el-tabs__active-bar) {
height: 3px;
}
}
&__table {
flex: 1;

View File

@ -27,7 +27,17 @@
<el-table-column prop="phone" label="手机号" />
<el-table-column prop="source" label="用户类型">
<template #default="{ row }">
{{ row.source === 'LOCAL' ? '系统用户' : row.source }}
{{
row.source === 'LOCAL'
? '系统用户'
: row.source === 'wecom'
? '企业微信'
: row.source === 'lark'
? '飞书'
: row.source === 'dingtalk'
? '钉钉'
: row.source
}}
</template>
</el-table-column>
<el-table-column label="状态" width="60">
@ -142,7 +152,7 @@ function createUser() {
} else {
MsgConfirm(`提示`, '社区版最多支持 2 个用户,如需拥有更多用户,请升级为专业版。', {
cancelButtonText: '确定',
confirmButtonText: '购买专业版',
confirmButtonText: '购买专业版'
})
.then(() => {
window.open('https://maxkb.cn/pricing.html', '_blank')