Perf: Optimize the display of tabs on the authentication settings page

This commit is contained in:
zhaojisen
2025-05-08 11:30:54 +08:00
committed by ZhaoJiSen
parent a20d20dfce
commit dcdcc5c557
16 changed files with 368 additions and 44 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 55 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 584 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 110 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 55 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

View File

@@ -0,0 +1,117 @@
<template>
<el-col :span="8" :xs="24" :sm="12" :md="8" :lg="6" :xl="4" class="auth-item-col">
<el-card ref="card" shadow="hover" class="auth-card">
<div slot="header" class="auth-item-header">
<div class="auth-item-header-left">
<img :src="logo" alt="auth-logo" class="auth-logo">
<div class="auth-item-header-title" :title="title">{{ title }}</div>
</div>
<div class="auth-item-actions">
<el-switch
v-model="isEnabled"
active-color="#148f76"
inactive-color="#e5e6e7"
@change="onChangeStatus"
/>
</div>
</div>
</el-card>
</el-col>
</template>
<script>
export default {
props: {
logo: {
type: String,
default: ''
},
title: {
type: String,
default: ''
},
authKey: {
type: String,
default: ''
},
description: {
type: String,
default: ''
},
enabled: {
type: Boolean,
default: false
}
},
data() {
return {
cardBody: null,
isEnabled: this.enabled
}
},
watch: {
enabled(newVal) {
this.isEnabled = newVal
}
},
mounted() {
const card = this.$refs.card
this.cardBody = card.$el.querySelector('.el-card__body')
this.cardBody.style.display = 'none'
},
methods: {
onChangeStatus(value) {
this.$emit('update:enabled', value, this.authKey)
}
}
}
</script>
<style scoped lang="scss">
.auth-item-col {
margin: 10px 0;
padding: 0 10px;
.auth-card {
transition: all 0.3s;
border-radius: 4px;
overflow: hidden;
.auth-item-header {
display: flex;
align-items: center;
justify-content: space-between;
.auth-item-header-left {
display: flex;
align-items: center;
gap: 8px;
.auth-logo {
width: 32px;
height: 32px;
object-fit: contain;
}
.auth-item-header-title {
max-width: 100px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
font-weight: 500;
}
}
.auth-item-actions {
display: flex;
align-items: center;
gap: 12px;
}
}
::v-deep(.el-card__body) {
display: none !important;
}
}
}
</style>

View File

@@ -1,23 +1,43 @@
<template>
<IBox>
<GenericCreateUpdateForm v-bind="$data" />
</IBox>
<div class="auth-container">
<IBox class="auth-box-wrapper">
<el-row :gutter="20">
<AuthItem
v-for="item in authItems"
:key="item.title"
v-bind="item"
@update:enabled="updateAuthItemStatus"
/>
</el-row>
</IBox>
<IBox>
<GenericCreateUpdateForm v-bind="$data" />
</IBox>
</div>
</template>
<script>
import { GenericCreateUpdateForm } from '@/layout/components'
import AuthItem from './AuthItem.vue'
import IBox from '@/components/Common/IBox'
import { getAuthItem, setAuthItem } from './const'
import { GenericCreateUpdateForm } from '@/layout/components'
export default {
components: {
IBox,
GenericCreateUpdateForm
GenericCreateUpdateForm,
AuthItem
},
data() {
return {
authItems: [],
searchQuery: '',
url: '/api/v1/settings/setting/?category=auth',
fields: [
'EMAIL_SUFFIX', 'FORGOT_PASSWORD_URL', 'LOGIN_REDIRECT_MSG_ENABLED'
'EMAIL_SUFFIX',
'FORGOT_PASSWORD_URL',
'LOGIN_REDIRECT_MSG_ENABLED'
],
fieldsMeta: {
FORGOT_PASSWORD_URL: {
@@ -32,10 +52,27 @@ export default {
}
},
mounted() {
this.authItems = getAuthItem(this)
},
methods: {
updateAuthItemStatus(value, key) {
setAuthItem(value, key)
this.$nextTick(() => {
this.authItems = getAuthItem(this)
this.$emit('update:tabs', key, value)
})
}
}
}
</script>
<style scoped>
<style scoped lang="scss">
.auth-container {
width: 100%;
.auth-box-wrapper {
margin-bottom: 20px;
}
}
</style>

View File

@@ -1,5 +1,8 @@
import { Select2 } from '@/components/Form/FormFields'
import store from '@/store'
import { Select2 } from '@/components/Form/FormFields'
import { ObjectLocalStorage } from '@/utils/common'
export const authLocalStorage = new ObjectLocalStorage('auth')
export function getOrgSelect2Meta() {
return {
@@ -18,3 +21,137 @@ export function getOrgSelect2Meta() {
}
}
}
export const getAuthItem = (vm) => {
const storageItems = authLocalStorage.get('authItems')
if (storageItems) {
return storageItems
}
const ldapHABackends = [
{
logo: require('@/assets/img/auth/ldap_logo.png'),
title: vm.$t('LDAP HA'),
authKey: 'AUTH_LDAP_HA',
description: '',
enabled: false
}
]
const extraBackends = [
{
logo: require('@/assets/img/auth/oidc_logo.png'),
title: vm.$t('OIDC'),
authKey: 'AUTH_OPENID',
description: '',
enabled: false
},
{
logo: require('@/assets/img/auth/saml2_logo.png'),
title: vm.$t('SAML2'),
authKey: 'AUTH_SAML2',
description: '',
enabled: false
},
{
logo: require('@/assets/img/auth/oauth2_logo.png'),
title: vm.$t('OAuth2'),
authKey: 'AUTH_OAUTH2',
description: '',
enabled: false
},
{
logo: require('@/assets/img/auth/wecom_logo.png'),
title: vm.$t('WeCom'),
authKey: 'AUTH_WECOM',
description: '',
enabled: false
},
{
logo: require('@/assets/img/auth/dingtalk_logo.webp'),
title: vm.$t('DingTalk'),
authKey: 'AUTH_DINGTALK',
description: '',
enabled: false
},
{
logo: require('@/assets/img/auth/feishu_logo.png'),
title: vm.$t('FeiShu'),
authKey: 'AUTH_FEISHU',
description: '',
enabled: false
},
{
logo: require('@/assets/img/auth/lark_logo.png'),
title: vm.$t('Lark'),
authKey: 'AUTH_LARK',
description: '',
enabled: false
},
{
logo: require('@/assets/img/auth/slack_logo.png'),
title: vm.$t('Slack'),
authKey: 'AUTH_SLACK',
description: '',
enabled: false
},
{
logo: require('@/assets/img/auth/radius_logo.png'),
title: vm.$t('Radius'),
authKey: 'AUTH_RADIUS',
description: '',
enabled: false
}
]
const authItems = [
{
logo: require('@/assets/img/auth/ldap_logo.png'),
title: vm.$t('Ldap'),
authKey: 'AUTH_LDAP',
description: '',
enabled: true
},
{
logo: require('@/assets/img/auth/cas_logo.png'),
title: vm.$t('CAS'),
authKey: 'AUTH_CAS',
description: '',
enabled: true
},
{
logo: require('@/assets/img/auth/passkey_logo.png'),
title: vm.$t('Passkey'),
authKey: 'AUTH_PASSKEY',
description: '',
enabled: true
}
]
if (store.getters.hasValidLicense) {
// 把 AUTH_LDAP_HA 加到 authItems 中的 AUTH_LDAP 后面
const ldapIndex = authItems.findIndex(item => item.authKey === 'AUTH_LDAP')
if (ldapIndex !== -1) {
authItems.splice(ldapIndex + 1, 0, ...ldapHABackends)
}
authItems.push(...extraBackends)
}
authLocalStorage.set('authItems', authItems)
return authItems
}
export const setAuthItem = (value, authKey) => {
const authItems = authLocalStorage.get('authItems')
const authItem = authItems.find(item => item.authKey === authKey)
if (authItem) {
authItem.enabled = value
authLocalStorage.set('authItems', authItems)
}
}

View File

@@ -1,7 +1,7 @@
<template>
<TabPage v-if="!loading" :active-menu.sync="activeMenu" :submenu="submenu">
<keep-alive>
<component :is="activeMenu" />
<component :is="activeMenu" @update:tabs="updateActiveMenu" />
</keep-alive>
</TabPage>
</template>
@@ -24,6 +24,7 @@ import SAML2 from './SAML2'
import OAuth2 from './OAuth2'
import Passkey from './Passkey.vue'
import Slack from './Slack.vue'
import { authLocalStorage } from './const'
export default {
components: {
@@ -46,10 +47,27 @@ export default {
Slack
},
data() {
let extraBackends = []
let ldapHABackends = []
if (this.$store.getters.hasValidLicense) {
extraBackends = [
return {
loading: true,
activeMenu: 'Basic',
defaultBackends: [
{
title: this.$t('Ldap'),
name: 'LDAP',
key: 'AUTH_LDAP'
},
{
title: this.$t('CAS'),
name: 'CAS',
key: 'AUTH_CAS'
},
{
title: this.$t('Passkey'),
name: 'Passkey',
key: 'AUTH_PASSKEY'
}
],
extraBackends: [
{
title: this.$t('OIDC'),
name: 'OIDC',
@@ -95,41 +113,15 @@ export default {
name: 'Radius',
key: 'AUTH_RADIUS'
}
]
ldapHABackends = [
],
ldapHABackends: [
{
title: this.$t('LDAP HA'),
name: 'LdapHA',
key: 'AUTH_LDAP_HA'
}
]
}
return {
loading: true,
activeMenu: 'Basic',
submenu: [
{
title: this.$t('Basic'),
name: 'Basic'
},
{
title: this.$t('Ldap'),
name: 'LDAP',
key: 'AUTH_LDAP'
},
...ldapHABackends,
{
title: this.$t('CAS'),
name: 'CAS',
key: 'AUTH_CAS'
},
{
title: this.$t('Passkey'),
name: 'Passkey',
key: 'AUTH_PASSKEY'
},
...extraBackends
]
],
submenu: []
}
},
computed: {
@@ -137,6 +129,9 @@ export default {
return {}
}
},
created() {
this.filterAuthItems()
},
mounted() {
this.$axios.get('/api/v1/settings/setting/?category=auth').then(res => {
for (const item of this.submenu) {
@@ -152,7 +147,45 @@ export default {
this.loading = false
})
},
methods: {}
methods: {
updateActiveMenu(key, status) {
const targetTab =
this.extraBackends.find((item) => item.key === key) ||
this.ldapHABackends.find((item) => item.key === key) ||
this.defaultBackends.find((item) => item.key === key)
status ? this.submenu.push(targetTab) : this.submenu.splice(this.submenu.indexOf(targetTab), 1)
},
filterAuthItems() {
const authItems = authLocalStorage.get('authItems')
const defaultBackends = this.defaultBackends.filter((defaultItem) => {
const authItem = authItems.find((item) => item.enabled && item.authKey === defaultItem.key)
return authItem
})
const ldapHABackends = this.ldapHABackends.filter((ldapHABackendItem) => {
const authItem = authItems.find((item) => item.enabled && item.authKey === ldapHABackendItem.key)
return authItem
})
const extraBackends = this.extraBackends.filter((extraItem) => {
const authItem = authItems.find((item) => item.enabled && item.authKey === extraItem.key)
return authItem
})
this.submenu = [
{
title: this.$t('Basic'),
name: 'Basic'
},
...defaultBackends,
...ldapHABackends,
...extraBackends
]
}
}
}
</script>