feat: user

This commit is contained in:
wangdan-fit2cloud 2025-06-12 17:38:07 +08:00
parent 31f6da272f
commit bbcc45181c
12 changed files with 1882 additions and 25 deletions

View File

@ -0,0 +1,127 @@
<template>
<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('views.system.authentication.cas.ldpUri')"
prop="config.ldpUri"
>
<el-input
v-model="form.config.ldpUri"
:placeholder="$t('views.system.authentication.cas.ldpUriPlaceholder')"
/>
</el-form-item>
<el-form-item
:label="$t('views.system.authentication.cas.validateUrl')"
prop="config.validateUrl"
>
<el-input
v-model="form.config.validateUrl"
:placeholder="$t('views.system.authentication.cas.validateUrlPlaceholder')"
/>
</el-form-item>
<el-form-item
:label="$t('views.system.authentication.cas.redirectUrl')"
prop="config.redirectUrl"
>
<el-input
v-model="form.config.redirectUrl"
:placeholder="$t('views.system.authentication.cas.redirectUrlPlaceholder')"
/>
</el-form-item>
<el-form-item>
<el-checkbox v-model="form.is_active"
>{{ $t('views.system.authentication.cas.enableAuthentication') }}
</el-checkbox>
</el-form-item>
</el-form>
<div class="text-right">
<el-button @click="submit(authFormRef)" type="primary" :disabled="loading">
{{ $t('common.save') }}
</el-button>
</div>
</div>
</el-scrollbar>
</div>
</template>
<script setup lang="ts">
import { reactive, ref, watch, onMounted } from 'vue'
import authApi from '@/api/system-settings/auth-setting'
import type { FormInstance, FormRules } from 'element-plus'
import { t } from '@/locales'
import { MsgSuccess } from '@/utils/message'
const form = ref<any>({
id: '',
auth_type: 'CAS',
config: {
ldpUri: '',
validateUrl: '',
redirectUrl: ''
},
is_active: true
})
const authFormRef = ref()
const loading = ref(false)
const rules = reactive<FormRules<any>>({
'config.ldpUri': [
{
required: true,
message: t('views.system.authentication.cas.ldpUriPlaceholder'),
trigger: 'blur'
}
],
'config.validateUrl': [
{
required: true,
message: t('views.system.authentication.cas.validateUrlPlaceholder'),
trigger: 'blur'
}
],
'config.redirectUrl': [
{
required: true,
message: t('views.system.authentication.cas.redirectUrlPlaceholder'),
trigger: 'blur'
}
]
})
const submit = async (formEl: FormInstance | undefined) => {
if (!formEl) return
await formEl.validate((valid, fields) => {
if (valid) {
authApi.putAuthSetting(form.value.auth_type, form.value, loading).then((res) => {
MsgSuccess(t('common.saveSuccess'))
})
}
})
}
function getDetail() {
authApi.getAuthSetting(form.value.auth_type, loading).then((res: any) => {
if (res.data && JSON.stringify(res.data) !== '{}') {
if (!res.data.config.validateUrl) {
res.data.config.validateUrl = res.data.config.ldpUri
}
form.value = res.data
}
})
}
onMounted(() => {
getDetail()
})
</script>
<style lang="scss" scoped></style>

View File

@ -0,0 +1,230 @@
template
<template>
<el-drawer
v-model="visible"
size="60%"
:append-to-body="true"
:destroy-on-close="true"
@close="handleClose"
>
<template #header>
<div class="flex align-center" style="margin-left: -8px">
<h4>
{{ currentPlatform.name + $t('views.system.authentication.scanTheQRCode.setting') }}
</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]"
:type="isPasswordField(key) ? 'password' : 'text'"
:show-password="isPasswordField(key)"
>
</el-input>
</el-form-item>
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button @click="handleClose">{{ $t('common.cancel') }}</el-button>
<el-button @click="validateConnection">{{
$t('views.system.authentication.scanTheQRCode.validate')
}}</el-button>
<el-button type="primary" @click="validateForm">{{ $t('common.save') }}</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/system-settings/platform-source'
import { MsgError, MsgSuccess } from '@/utils/message'
import { t } from '@/locales'
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?: any): string => {
const fieldNames: { [key: string]: string } = {
corp_id: 'Corp ID',
app_key: currentPlatform?.key != 'lark' ? 'APP Key' : 'App ID',
app_secret: 'APP Secret',
agent_id: 'Agent ID',
callback_url: t('views.application.applicationAccess.callback')
}
return (
fieldNames[key as keyof typeof fieldNames] ||
(key ? key.charAt(0).toUpperCase() + key.slice(1) : '')
)
}
const getValidationRules = (key: any) => {
switch (key) {
case 'app_key':
return [
{
required: true,
message: t('views.system.authentication.scanTheQRCode.appKeyPlaceholder'),
trigger: ['blur', 'change']
}
]
case 'app_secret':
return [
{
required: true,
message: t('views.system.authentication.scanTheQRCode.appSecretPlaceholder'),
trigger: ['blur', 'change']
}
]
case 'corp_id':
return [
{
required: true,
message: t('views.system.authentication.scanTheQRCode.corpIdPlaceholder'),
trigger: ['blur', 'change']
}
]
case 'agent_id':
return [
{
required: true,
message: t('views.system.authentication.scanTheQRCode.agentIdPlaceholder'),
trigger: ['blur', 'change']
}
]
case 'callback_url':
return [
{
required: true,
message: t('views.application.applicationAccess.callbackTip'),
trigger: ['blur', 'change']
},
{
pattern: /^https?:\/\/.+/,
message: t('views.system.authentication.scanTheQRCode.callbackWarning'),
trigger: ['blur', 'change']
}
]
default:
return []
}
}
const open = async (platform: Platform) => {
visible.value = true
loading.value = true
Object.assign(currentPlatform, platform)
// callback_url
const defaultCallbackUrl = window.location.origin
switch (platform.key) {
case 'wecom':
if (currentPlatform.config.app_key) {
currentPlatform.config.agent_id = currentPlatform.config.app_key
delete currentPlatform.config.app_key
}
currentPlatform.config.callback_url = `${defaultCallbackUrl}/api/wecom`
break
case 'dingtalk':
if (currentPlatform.config.agent_id) {
currentPlatform.config.corp_id = currentPlatform.config.agent_id
delete currentPlatform.config.agent_id
}
currentPlatform.config = {
corp_id: currentPlatform.config.corp_id,
app_key: currentPlatform.config.app_key,
app_secret: currentPlatform.config.app_secret,
callback_url: defaultCallbackUrl
}
currentPlatform.config.callback_url = `${defaultCallbackUrl}/api/dingtalk`
break
case 'lark':
currentPlatform.config.callback_url = `${defaultCallbackUrl}/api/feishu`
break
default:
break
}
formRef.value?.clearValidate()
}
defineExpose({ open })
const validateForm = () => {
formRef.value?.validate((valid) => {
if (valid) {
saveConfig()
} else {
MsgError(t('views.system.authentication.scanTheQRCode.validateFailedTip'))
}
})
}
const handleClose = () => {
visible.value = false
formRef.value?.clearValidate()
emit('refresh')
}
function validateConnection() {
platformApi.validateConnection(currentPlatform, loading).then((res: any) => {
if (res.data) {
MsgSuccess(t('views.system.authentication.scanTheQRCode.validateSuccess'))
} else {
MsgError(t('views.system.authentication.scanTheQRCode.validateFailed'))
}
})
}
const passwordFields = new Set(['app_secret', 'client_secret', 'secret'])
const isPasswordField = (key: any) => passwordFields.has(key)
const emit = defineEmits(['refresh'])
function saveConfig() {
platformApi.updateConfig(currentPlatform, loading).then((res: any) => {
MsgSuccess(t('common.saveSuccess'))
emit('refresh')
visible.value = false
formRef.value?.clearValidate()
})
}
</script>
<style lang="scss" scoped></style>

View File

@ -0,0 +1,184 @@
<template>
<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('views.system.authentication.ldap.address')"
prop="config.ldap_server"
>
<el-input
v-model="form.config.ldap_server"
:placeholder="$t('views.system.authentication.ldap.serverPlaceholder')"
/>
</el-form-item>
<el-form-item
:label="$t('views.system.authentication.ldap.bindDN')"
prop="config.base_dn"
>
<el-input
v-model="form.config.base_dn"
:placeholder="$t('views.system.authentication.ldap.bindDNPlaceholder')"
/>
</el-form-item>
<el-form-item :label="$t('views.system.password')" prop="config.password">
<el-input
v-model="form.config.password"
:placeholder="$t('views.userManage.form.password.placeholder')"
show-password
/>
</el-form-item>
<el-form-item :label="$t('views.system.authentication.ldap.ou')" prop="config.ou">
<el-input
v-model="form.config.ou"
:placeholder="$t('views.system.authentication.ldap.ouPlaceholder')"
/>
</el-form-item>
<el-form-item
:label="$t('views.system.authentication.ldap.ldap_filter')"
prop="config.ldap_filter"
>
<el-input
v-model="form.config.ldap_filter"
:placeholder="$t('views.system.authentication.ldap.ldap_filterPlaceholder')"
/>
</el-form-item>
<el-form-item
:label="$t('views.system.authentication.ldap.ldap_mapping')"
prop="config.ldap_mapping"
>
<el-input
v-model="form.config.ldap_mapping"
placeholder='{"name":"name","email":"mail","username":"cn"}'
/>
</el-form-item>
<el-form-item>
<el-checkbox v-model="form.is_active">{{
$t('views.system.authentication.ldap.enableAuthentication')
}}</el-checkbox>
</el-form-item>
</el-form>
<div class="text-right">
<el-button @click="submit(authFormRef, 'test')" :disabled="loading">
{{ $t('views.system.test') }}</el-button
>
<el-button @click="submit(authFormRef)" type="primary" :disabled="loading">
{{ $t('common.save') }}
</el-button>
</div>
</div>
</el-scrollbar>
</div>
</template>
<script setup lang="ts">
import { reactive, ref, watch, onMounted } from 'vue'
import authApi from '@/api/system-settings/auth-setting'
import type { FormInstance, FormRules } from 'element-plus'
import { t } from '@/locales'
import { MsgSuccess } from '@/utils/message'
const form = ref<any>({
id: '',
auth_type: 'LDAP',
config: {
ldap_server: '',
base_dn: '',
password: '',
ou: '',
ldap_filter: '',
ldap_mapping: ''
},
is_active: true
})
const authFormRef = ref()
const loading = ref(false)
const rules = reactive<FormRules<any>>({
'config.ldap_server': [
{
required: true,
message: t('views.system.authentication.ldap.serverPlaceholder'),
trigger: 'blur'
}
],
'config.base_dn': [
{
required: true,
message: t('views.system.authentication.ldap.bindDNPlaceholder'),
trigger: 'blur'
}
],
'config.password': [
{
required: true,
message: t('views.userManage.form.password.requiredMessage'),
trigger: 'blur'
}
],
'config.ou': [
{
required: true,
message: t('views.system.authentication.ldap.ouPlaceholder'),
trigger: 'blur'
}
],
'config.ldap_filter': [
{
required: true,
message: t('views.system.authentication.ldap.ldap_filterPlaceholder'),
trigger: 'blur'
}
],
'config.ldap_mapping': [
{
required: true,
message: t('views.system.authentication.ldap.ldap_mappingPlaceholder'),
trigger: 'blur'
}
]
})
const submit = async (formEl: FormInstance | undefined, test?: string) => {
if (!formEl) return
await formEl.validate((valid, fields) => {
if (valid) {
if (test) {
authApi.postAuthSetting(form.value, loading).then((res) => {
MsgSuccess(t('views.system.testSuccess'))
})
} else {
authApi.putAuthSetting(form.value.auth_type, form.value, loading).then((res) => {
MsgSuccess(t('common.saveSuccess'))
})
}
}
})
}
function getDetail() {
authApi.getAuthSetting(form.value.auth_type, loading).then((res: any) => {
if (res.data && JSON.stringify(res.data) !== '{}') {
form.value = res.data
if (res.data.config.ldap_mapping) {
form.value.config.ldap_mapping = JSON.stringify(
JSON.parse(res.data.config.ldap_mapping)
)
}
}
})
}
onMounted(() => {
getDetail()
})
</script>
<style lang="scss" scoped></style>

View File

@ -0,0 +1,210 @@
<template>
<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('views.system.authentication.oauth2.authEndpoint')"
prop="config.authEndpoint"
>
<el-input
v-model="form.config.authEndpoint"
:placeholder="$t('views.system.authentication.oauth2.authEndpointPlaceholder')"
/>
</el-form-item>
<el-form-item
:label="$t('views.system.authentication.oauth2.tokenEndpoint')"
prop="config.tokenEndpoint"
>
<el-input
v-model="form.config.tokenEndpoint"
:placeholder="$t('views.system.authentication.oauth2.tokenEndpointPlaceholder')"
/>
</el-form-item>
<el-form-item
:label="$t('views.system.authentication.oauth2.userInfoEndpoint')"
prop="config.userInfoEndpoint"
>
<el-input
v-model="form.config.userInfoEndpoint"
:placeholder="$t('views.system.authentication.oauth2.userInfoEndpointPlaceholder')"
/>
</el-form-item>
<el-form-item
:label="$t('views.system.authentication.oauth2.scope')"
prop="config.scope"
>
<el-input
v-model="form.config.scope"
:placeholder="$t('views.system.authentication.oauth2.scopePlaceholder')"
/>
</el-form-item>
<el-form-item
:label="$t('views.system.authentication.oauth2.clientId')"
prop="config.clientId"
>
<el-input
v-model="form.config.clientId"
:placeholder="$t('views.system.authentication.oauth2.clientIdPlaceholder')"
/>
</el-form-item>
<el-form-item
:label="$t('views.system.authentication.oauth2.clientSecret')"
prop="config.clientSecret"
>
<el-input
v-model="form.config.clientSecret"
:placeholder="$t('views.system.authentication.oauth2.clientSecretPlaceholder')"
show-password
/>
</el-form-item>
<el-form-item
:label="$t('views.system.authentication.oauth2.redirectUrl')"
prop="config.redirectUrl"
>
<el-input
v-model="form.config.redirectUrl"
:placeholder="$t('views.system.authentication.oauth2.redirectUrlPlaceholder')"
/>
</el-form-item>
<el-form-item
:label="$t('views.system.authentication.oauth2.filedMapping')"
prop="config.fieldMapping"
>
<el-input
v-model="form.config.fieldMapping"
:placeholder="$t('views.system.authentication.oauth2.filedMappingPlaceholder')"
/>
</el-form-item>
<el-form-item>
<el-checkbox v-model="form.is_active"
>{{ $t('views.system.authentication.oauth2.enableAuthentication') }}
</el-checkbox>
</el-form-item>
</el-form>
<div class="text-right">
<el-button @click="submit(authFormRef)" type="primary" :disabled="loading">
{{ $t('common.save') }}
</el-button>
</div>
</div>
</el-scrollbar>
</div>
</template>
<script setup lang="ts">
import { reactive, ref, onMounted } from 'vue'
import authApi from '@/api/system-settings/auth-setting'
import type { FormInstance, FormRules } from 'element-plus'
import { t } from '@/locales'
import { MsgSuccess } from '@/utils/message'
const form = ref<any>({
id: '',
auth_type: 'OAuth2',
config: {
authEndpoint: '',
tokenEndpoint: '',
userInfoEndpoint: '',
scope: '',
clientId: '',
clientSecret: '',
redirectUrl: '',
fieldMapping: ''
},
is_active: true
})
const authFormRef = ref()
const loading = ref(false)
const rules = reactive<FormRules<any>>({
'config.authEndpoint': [
{
required: true,
message: t('views.system.authentication.oauth2.authEndpointPlaceholder'),
trigger: 'blur'
}
],
'config.tokenEndpoint': [
{
required: true,
message: t('views.system.authentication.oauth2.tokenEndpointPlaceholder'),
trigger: 'blur'
}
],
'config.userInfoEndpoint': [
{
required: true,
message: t('views.system.authentication.oauth2.userInfoEndpointPlaceholder'),
trigger: 'blur'
}
],
'config.scope': [
{
required: true,
message: t('views.system.authentication.oauth2.scopePlaceholder'),
trigger: 'blur'
}
],
'config.clientId': [
{
required: true,
message: t('views.system.authentication.oauth2.clientIdPlaceholder'),
trigger: 'blur'
}
],
'config.clientSecret': [
{
required: true,
message: t('views.system.authentication.oauth2.clientSecretPlaceholder'),
trigger: 'blur'
}
],
'config.redirectUrl': [
{
required: true,
message: t('views.system.authentication.oauth2.redirectUrlPlaceholder'),
trigger: 'blur'
}
],
'config.fieldMapping': [
{
required: true,
message: t('views.system.authentication.oauth2.filedMappingPlaceholder'),
trigger: 'blur'
}
]
})
const submit = async (formEl: FormInstance | undefined, test?: string) => {
if (!formEl) return
await formEl.validate((valid, fields) => {
if (valid) {
authApi.putAuthSetting(form.value.auth_type, form.value, loading).then((res) => {
MsgSuccess(t('common.saveSuccess'))
})
}
})
}
function getDetail() {
authApi.getAuthSetting(form.value.auth_type, loading).then((res: any) => {
if (res.data && JSON.stringify(res.data) !== '{}') {
form.value = res.data
}
})
}
onMounted(() => {
getDetail()
})
</script>
<style lang="scss" scoped></style>

View File

@ -0,0 +1,221 @@
<template>
<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('views.system.authentication.oidc.authEndpoint')"
prop="config.authEndpoint"
>
<el-input
v-model="form.config.authEndpoint"
:placeholder="$t('views.system.authentication.oidc.authEndpointPlaceholder')"
/>
</el-form-item>
<el-form-item
:label="$t('views.system.authentication.oidc.tokenEndpoint')"
prop="config.tokenEndpoint"
>
<el-input
v-model="form.config.tokenEndpoint"
:placeholder="$t('views.system.authentication.oidc.tokenEndpointPlaceholder')"
/>
</el-form-item>
<el-form-item
:label="$t('views.system.authentication.oidc.userInfoEndpoint')"
prop="config.userInfoEndpoint"
>
<el-input
v-model="form.config.userInfoEndpoint"
:placeholder="$t('views.system.authentication.oidc.userInfoEndpointPlaceholder')"
/>
</el-form-item>
<el-form-item label="Scope" prop="config.scope">
<el-input v-model="form.config.scope" placeholder="openid+profile+email " />
</el-form-item>
<el-form-item label="State" prop="config.state">
<el-input v-model="form.config.state" placeholder="" />
</el-form-item>
<el-form-item
:label="$t('views.system.authentication.oidc.clientId')"
prop="config.clientId"
>
<el-input
v-model="form.config.clientId"
:placeholder="$t('views.system.authentication.oidc.clientIdPlaceholder')"
/>
</el-form-item>
<el-form-item
:label="$t('views.system.authentication.oidc.clientSecret')"
prop="config.clientSecret"
>
<el-input
v-model="form.config.clientSecret"
:placeholder="$t('views.system.authentication.oidc.clientSecretPlaceholder')"
show-password
/>
</el-form-item>
<el-form-item
:label="$t('views.system.authentication.oauth2.filedMapping')"
prop="config.fieldMapping"
>
<el-input
v-model="form.config.fieldMapping"
:placeholder="$t('views.system.authentication.oauth2.filedMappingPlaceholder')"
/>
</el-form-item>
<el-form-item
:label="$t('views.system.authentication.oidc.redirectUrl')"
prop="config.redirectUrl"
>
<el-input
v-model="form.config.redirectUrl"
:placeholder="$t('views.system.authentication.oidc.redirectUrlPlaceholder')"
/>
</el-form-item>
<el-form-item>
<el-checkbox v-model="form.is_active"
>{{ $t('views.system.authentication.oidc.enableAuthentication') }}
</el-checkbox>
</el-form-item>
</el-form>
<div class="text-right">
<el-button @click="submit(authFormRef)" type="primary" :disabled="loading">
{{ $t('common.save') }}
</el-button>
</div>
</div>
</el-scrollbar>
</div>
</template>
<script setup lang="ts">
import { reactive, ref, watch, onMounted } from 'vue'
import authApi from '@/api/system-settings/auth-setting'
import type { FormInstance, FormRules } from 'element-plus'
import { t } from '@/locales'
import { MsgSuccess } from '@/utils/message'
const form = ref<any>({
id: '',
auth_type: 'OIDC',
config: {
authEndpoint: '',
tokenEndpoint: '',
userInfoEndpoint: '',
scope: '',
state: '',
clientId: '',
clientSecret: '',
fieldMapping: '{"username": "preferred_username", "email": "email"}',
redirectUrl: ''
},
is_active: true
})
const authFormRef = ref()
const loading = ref(false)
const rules = reactive<FormRules<any>>({
'config.authEndpoint': [
{
required: true,
message: t('views.system.authentication.oidc.authEndpointPlaceholder'),
trigger: 'blur'
}
],
'config.tokenEndpoint': [
{
required: true,
message: t('views.system.authentication.oidc.tokenEndpointPlaceholder'),
trigger: 'blur'
}
],
'config.userInfoEndpoint': [
{
required: true,
message: t('views.system.authentication.oidc.userInfoEndpointPlaceholder'),
trigger: 'blur'
}
],
'config.scope': [
{
required: true,
message: t('views.system.authentication.oidc.scopePlaceholder'),
trigger: 'blur'
}
],
'config.clientId': [
{
required: true,
message: t('views.system.authentication.oidc.clientIdPlaceholder'),
trigger: 'blur'
}
],
'config.clientSecret': [
{
required: true,
message: t('views.system.authentication.oidc.clientSecretPlaceholder'),
trigger: 'blur'
}
],
'config.fieldMapping': [
{
required: true,
message: t('views.system.authentication.oauth2.filedMappingPlaceholder'),
trigger: 'blur'
}
],
'config.redirectUrl': [
{
required: true,
message: t('views.system.authentication.oidc.redirectUrlPlaceholder'),
trigger: 'blur'
}
],
'config.logoutEndpoint': [
{
required: true,
message: t('views.system.authentication.oidc.logoutEndpointPlaceholder'),
trigger: 'blur'
}
]
})
const submit = async (formEl: FormInstance | undefined, test?: string) => {
if (!formEl) return
await formEl.validate((valid, fields) => {
if (valid) {
authApi.putAuthSetting(form.value.auth_type, form.value, loading).then((res) => {
MsgSuccess(t('common.saveSuccess'))
})
}
})
}
function getDetail() {
authApi.getAuthSetting(form.value.auth_type, loading).then((res: any) => {
if (res.data && JSON.stringify(res.data) !== '{}') {
form.value = res.data
if (
form.value.config.fieldMapping === '' ||
form.value.config.fieldMapping === undefined
) {
form.value.config.fieldMapping = '{"username": "preferred_username", "email": "email"}'
}
}
})
}
onMounted(() => {
getDetail()
})
</script>
<style lang="scss" scoped></style>

View File

@ -0,0 +1,236 @@
<template>
<div v-loading="loading" class="scan-height">
<el-scrollbar>
<div v-for="item in platforms" :key="item.key" class="mb-16">
<el-card class="border-none mb-16" shadow="none">
<div class="flex-between">
<div class="flex align-center">
<img :src="item.logoSrc" alt="" width="24px" />
<h5 class="ml-8">{{ item.name }}</h5>
<el-tag v-if="item.isValid" type="success" class="ml-8"
>{{ $t('views.system.authentication.scanTheQRCode.effective') }}
</el-tag>
</div>
<div>
<el-button type="primary" v-if="!item.isValid" @click="showDialog(item)"
>{{ $t('views.system.authentication.scanTheQRCode.access') }}
</el-button>
<span v-if="item.isValid">
<span class="mr-4">{{
item.isActive
? $t('views.system.authentication.scanTheQRCode.alreadyTurnedOn')
: $t('views.system.authentication.scanTheQRCode.notEnabled')
}}</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, item) }}</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 v-if="key === 'app_secret' && !showPassword[item.key]?.[key]">
<Hide />
</el-icon>
<el-icon v-if="key === 'app_secret' && showPassword[item.key]?.[key]">
<View />
</el-icon>
</el-button>
</div>
</el-col>
</el-row>
<el-button type="primary" @click="showDialog(item)">
{{ $t('common.edit') }}
</el-button>
<el-button @click="validateConnection(item)">
{{ $t('views.system.authentication.scanTheQRCode.validate') }}
</el-button>
</div>
</el-collapse-transition>
</el-card>
</div>
<EditModel ref="EditModelRef" @refresh="refresh" />
</el-scrollbar>
</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/system-settings/platform-source'
import { MsgError, MsgSuccess } from '@/utils/message'
import { t } from '@/locales'
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', t('views.system.authentication.scanTheQRCode.wecom')),
createPlatform('dingtalk', t('views.system.authentication.scanTheQRCode.dingtalk')),
createPlatform('lark', t('views.system.authentication.scanTheQRCode.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: '' }),
app_secret: '',
callback_url: ''
}
return {
key,
logoSrc: new URL(`../../../assets/scan/logo_${logo}.svg`, import.meta.url).href,
name,
isActive: false,
isValid: false,
config
}
}
function formatFieldName(key?: any, item?: Platform): string {
const fieldNames: { [key: string]: string } = {
corp_id: 'Corp ID',
app_key: item?.key != 'lark' ? 'APP Key' : 'App ID',
app_secret: 'APP Secret',
agent_id: 'Agent ID',
callback_url: t('views.application.applicationAccess.callback')
}
return (
fieldNames[key as keyof typeof fieldNames] ||
(key ? key.charAt(0).toUpperCase() + key.slice(1) : '')
)
}
function getPlatformInfo() {
loading.value = true
platformApi.getPlatformInfo(loading).then((res: any) => {
if (res) {
platforms.forEach((platform) => {
const data = res.data.find((item: any) => item.auth_type === platform.key)
if (data) {
Object.assign(platform, {
isValid: data.is_valid,
isActive: data.is_active,
config: data.config
})
if (platform.key === 'dingtalk') {
const { corp_id, app_key, app_secret } = platform.config
platform.config = {
corp_id,
app_key,
app_secret,
callback_url: platform.config.callback_url
}
}
showPassword[platform.key] = {}
showPassword[platform.key]['app_secret'] = false
}
})
}
})
}
function validateConnection(currentPlatform: Platform) {
platformApi.validateConnection(currentPlatform, loading).then((res: any) => {
res.data
? MsgSuccess(t('views.system.authentication.scanTheQRCode.validateSuccess'))
: MsgError(t('views.system.authentication.scanTheQRCode.validateFailed'))
})
}
function refresh() {
getPlatformInfo()
}
function changeStatus(currentPlatform: Platform) {
platformApi.updateConfig(currentPlatform, loading).then((res: any) => {
MsgSuccess(t('common.saveSuccess'))
})
}
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>
.scan-height {
height: calc(100vh - var(--app-header-height) - var(--app-view-padding) * 2 - 70px);
}
</style>

View File

@ -0,0 +1,77 @@
<template>
<div class="authentication-setting p-16-24">
<h4 class="mb-16">{{ $t('views.system.authentication.title') }}</h4>
<el-tabs v-model="activeName" class="mt-4" @tab-click="handleClick">
<template v-for="(item, index) in tabList" :key="index">
<el-tab-pane :label="item.label" :name="item.name">
<component :is="item.component" />
</el-tab-pane>
</template>
</el-tabs>
</div>
</template>
<script setup lang="ts">
import { ref, onMounted } from 'vue'
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 OAuth2 from './component/OAuth2.vue'
import { t } from '@/locales'
import useStore from '@/stores'
const { user } = useStore()
const router = useRouter()
const activeName = ref('LDAP')
const tabList = [
{
label: t('views.system.authentication.ldap.title'),
name: 'LDAP',
component: LDAP,
},
{
label: t('views.system.authentication.cas.title'),
name: 'CAS',
component: CAS,
},
{
label: t('views.system.authentication.oidc.title'),
name: 'OIDC',
component: OIDC,
},
{
label: t('views.system.authentication.oauth2.title'),
name: 'OAuth2',
component: OAuth2,
},
{
label: t('views.system.authentication.scanTheQRCode.title'),
name: 'SCAN',
component: SCAN,
},
]
function handleClick() {}
onMounted(() => {
if (user.isExpire()) {
router.push({ path: `/application` })
}
})
</script>
<style lang="scss" scoped>
.authentication-setting__main {
background-color: var(--app-view-bg-color);
box-sizing: border-box;
min-width: 700px;
height: calc(100vh - var(--app-header-height) - var(--app-view-padding) * 2 - 70px);
box-sizing: border-box;
:deep(.form-container) {
width: 70%;
margin: 0 auto;
}
}
</style>

View File

@ -0,0 +1,199 @@
<template>
<el-drawer v-model="visible" size="60%">
<template #header>
<h4>{{ title }}</h4>
</template>
<h4 class="title-decoration-1 mb-16 mt-8">{{ $t('common.info') }}</h4>
<el-form
ref="userFormRef"
:model="userForm"
:rules="rules"
label-position="top"
require-asterisk-position="right"
@submit.prevent
:close-on-click-modal="false"
:close-on-press-escape="false"
>
<el-form-item
:prop="isEdit ? '' : 'username'"
:label="$t('views.userManage.form.username.label')"
>
<el-input
v-model="userForm.username"
:placeholder="$t('views.userManage.form.username.placeholder')"
maxlength="20"
show-word-limit
:disabled="isEdit"
>
</el-input>
</el-form-item>
<el-form-item :label="$t('views.userManage.form.nick_name.label')">
<el-input
v-model="userForm.nick_name"
:placeholder="$t('views.userManage.form.nick_name.placeholder')"
maxlength="64"
show-word-limit
>
</el-input>
</el-form-item>
<el-form-item :label="$t('views.userManage.form.email.label')" prop="email">
<el-input
type="email"
v-model="userForm.email"
:placeholder="$t('views.userManage.form.email.placeholder')"
>
</el-input>
</el-form-item>
<el-form-item :label="$t('views.userManage.form.phone.label')">
<el-input
v-model="userForm.phone"
:placeholder="$t('views.userManage.form.phone.placeholder')"
>
</el-input>
</el-form-item>
<el-form-item
:label="$t('views.userManage.form.password.label')"
prop="password"
v-if="!isEdit"
>
<el-input
type="password"
v-model="userForm.password"
:placeholder="$t('views.userManage.form.password.placeholder')"
show-password
>
</el-input>
</el-form-item>
<el-form-item
:label="$t('views.userManage.form.password.label')"
prop="password"
v-if="!isEdit"
>
<el-input
type="password"
v-model="userForm.password"
:placeholder="$t('views.userManage.form.password.placeholder')"
show-password
>
</el-input>
</el-form-item>
</el-form>
<template #footer>
<el-button @click.prevent="visible = false"> {{ $t('common.cancel') }} </el-button>
<el-button type="primary" @click="submit(userFormRef)" :loading="loading">
{{ $t('common.save') }}
</el-button>
</template>
</el-drawer>
</template>
<script setup lang="ts">
import { ref, reactive, watch } from 'vue'
import type { FormInstance } from 'element-plus'
import userManageApi from '@/api/user/user-manage'
import { MsgSuccess } from '@/utils/message'
import { t } from '@/locales'
const props = defineProps({
title: String,
})
const emit = defineEmits(['refresh'])
const userFormRef = ref()
const userForm = ref<any>({
username: '',
email: '',
password: '',
phone: '',
nick_name: '',
})
const rules = reactive({
username: [
{
required: true,
message: t('views.userManage.form.username.requiredMessage'),
trigger: 'blur',
},
{
min: 6,
max: 20,
message: t('views.userManage.form.username.lengthMessage'),
trigger: 'blur',
},
],
email: [
{
required: true,
message: t('views.userManage.form.email.requiredMessage'),
trigger: 'blur',
},
],
password: [
{
required: true,
message: t('views.userManage.form.password.requiredMessage'),
trigger: 'blur',
},
{
min: 6,
max: 20,
message: t('views.userManage.form.password.lengthMessage'),
trigger: 'blur',
},
],
})
const visible = ref<boolean>(false)
const loading = ref(false)
const isEdit = ref(false)
watch(visible, (bool) => {
if (!bool) {
userForm.value = {
username: '',
email: '',
password: '',
phone: '',
nick_name: '',
}
isEdit.value = false
userFormRef.value?.clearValidate()
}
})
const open = (data: any) => {
if (data) {
userForm.value['id'] = data.id
userForm.value.username = data.username
userForm.value.email = data.email
userForm.value.password = data.password
userForm.value.phone = data.phone
userForm.value.nick_name = data.nick_name
isEdit.value = true
}
visible.value = true
}
const submit = async (formEl: FormInstance | undefined) => {
if (!formEl) return
await formEl.validate((valid, fields) => {
if (valid) {
if (isEdit.value) {
userManageApi.putUserManage(userForm.value.id, userForm.value, loading).then((res) => {
emit('refresh')
MsgSuccess(t('common.editSuccess'))
visible.value = false
})
} else {
userManageApi.postUserManage(userForm.value, loading).then((res) => {
emit('refresh')
MsgSuccess(t('common.createSuccess'))
visible.value = false
})
}
}
})
}
defineExpose({ open })
</script>
<style lang="scss" scoped></style>

View File

@ -0,0 +1,133 @@
<template>
<el-dialog :title="$t('views.userManage.setting.updatePwd')" v-model="dialogVisible">
<el-form
ref="userFormRef"
:model="userForm"
:rules="rules"
label-position="top"
require-asterisk-position="right"
@submit.prevent
:close-on-click-modal="false"
:close-on-press-escape="false"
>
<el-form-item :label="$t('views.userManage.form.new_password.label')" prop="password">
<el-input
type="password"
v-model="userForm.password"
:placeholder="$t('views.userManage.form.new_password.placeholder')"
show-password
>
</el-input>
</el-form-item>
<el-form-item :label="$t('views.userManage.form.re_password.label')" prop="re_password">
<el-input
type="password"
v-model="userForm.re_password"
:placeholder="$t('views.userManage.form.re_password.placeholder')"
show-password
>
</el-input>
</el-form-item>
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button @click.prevent="dialogVisible = false"> {{ $t('common.cancel') }} </el-button>
<el-button type="primary" @click="submit(userFormRef)" :loading="loading">
{{ $t('common.save') }}
</el-button>
</span>
</template>
</el-dialog>
</template>
<script setup lang="ts">
import { ref, reactive, watch } from 'vue'
import useStore from '@/stores'
import type { FormInstance, FormRules } from 'element-plus'
import type { ResetPasswordRequest } from '@/api/type/user'
import userManageApi from '@/api/user/user-manage'
import { MsgSuccess } from '@/utils/message'
import { t } from '@/locales'
const emit = defineEmits(['refresh'])
const { user } = useStore()
const userFormRef = ref()
const userForm = ref<any>({
password: '',
re_password: ''
})
const rules = reactive<FormRules<ResetPasswordRequest>>({
password: [
{
required: true,
message: t('views.userManage.form.new_password.requiredMessage'),
trigger: 'blur'
},
{
min: 6,
max: 20,
message: t('views.userManage.form.password.lengthMessage'),
trigger: 'blur'
}
],
re_password: [
{
required: true,
message: t('views.userManage.form.re_password.requiredMessage'),
trigger: 'blur'
},
{
min: 6,
max: 20,
message: t('views.userManage.form.password.lengthMessage'),
trigger: 'blur'
},
{
validator: (rule, value, callback) => {
if (userFormRef.value.password != userFormRef.value.re_password) {
callback(new Error(t('views.userManage.form.re_password.validatorMessage')))
} else {
callback()
}
},
trigger: 'blur'
}
]
})
const dialogVisible = ref<boolean>(false)
const loading = ref(false)
const userId = ref('')
watch(dialogVisible, (bool) => {
if (!bool) {
userForm.value = {
password: '',
re_password: ''
}
}
})
const open = (data: any) => {
userId.value = data.id
dialogVisible.value = true
userFormRef.value?.clearValidate()
}
const submit = async (formEl: FormInstance | undefined) => {
if (!formEl) return
await formEl.validate((valid, fields) => {
if (valid) {
userManageApi.putUserManagePassword(userId.value, userForm.value, loading).then((res) => {
emit('refresh')
user.profile()
MsgSuccess(t('views.userManage.tip.updatePwdSuccess'))
dialogVisible.value = false
})
}
})
}
defineExpose({ open })
</script>
<style lang="scss" scoped></style>

View File

@ -0,0 +1,261 @@
<template>
<div class="p-16-24">
<h2 class="mb-16">{{ $t('views.userManage.title') }}</h2>
<el-card>
<div class="flex-between mb-16">
<el-button type="primary" @click="createUser()">{{
$t('views.userManage.createUser')
}}</el-button>
<div class="flex-between complex-search">
<el-select
class="complex-search__left"
v-model="search_type"
style="width: 120px"
@change="search_type_change"
>
<el-option :label="$t('views.login.loginForm.username.label')" value="name" />
</el-select>
<el-input
v-if="search_type === 'name'"
v-model="search_form.name"
@change="getList"
:placeholder="$t('common.searchBar.placeholder')"
style="width: 220px"
clearable
/>
</div>
</div>
<app-table
class="mt-16"
:data="userTableData"
:pagination-config="paginationConfig"
@sizeChange="handleSizeChange"
@changePage="getList"
v-loading="loading"
>
<el-table-column prop="nick_name" :label="$t('views.userManage.form.nick_name.label')" />
<el-table-column prop="username" :label="$t('views.userManage.form.username.label')" />
<el-table-column prop="is_active" :label="$t('common.status.label')">
<template #default="{ row }">
<div v-if="row.is_active" class="flex align-center">
<el-icon class="color-success mr-8" style="font-size: 16px"
><SuccessFilled
/></el-icon>
<span class="color-secondary">
{{ $t('common.status.enabled') }}
</span>
</div>
<div v-else class="flex align-center">
<AppIcon iconName="app-disabled" class="color-secondary mr-8"></AppIcon>
<span class="color-secondary">
{{ $t('common.status.disabled') }}
</span>
</div>
</template>
</el-table-column>
<el-table-column
prop="email"
:label="$t('views.userManage.form.email.label')"
show-overflow-tooltip
>
<template #default="{ row }">
{{ row.email || '-' }}
</template>
</el-table-column>
<el-table-column prop="phone" :label="$t('views.userManage.form.phone.label')">
<template #default="{ row }">
{{ row.phone || '-' }}
</template>
</el-table-column>
<el-table-column prop="source" :label="$t('views.userManage.source.label')">
<template #default="{ row }">
{{
row.source === 'LOCAL'
? $t('views.userManage.source.local')
: row.source === 'wecom'
? $t('views.userManage.source.wecom')
: row.source === 'lark'
? $t('views.userManage.source.lark')
: row.source === 'dingtalk'
? $t('views.userManage.source.dingtalk')
: row.source === 'OAUTH2' || row.source === 'OAuth2'
? 'OAuth2'
: row.source
}}
</template>
</el-table-column>
<el-table-column :label="$t('common.createTime')" width="180">
<template #default="{ row }">
{{ datetimeFormat(row.create_time) }}
</template>
</el-table-column>
<el-table-column :label="$t('common.operation')" width="160" align="left" fixed="right">
<template #default="{ row }">
<span @click.stop>
<el-switch
:disabled="row.role === 'ADMIN'"
size="small"
v-model="row.is_active"
:before-change="() => changeState(row)"
/>
</span>
<el-divider direction="vertical" />
<span class="mr-8">
<el-button type="primary" text @click.stop="editUser(row)" :title="$t('common.edit')">
<el-icon><EditPen /></el-icon>
</el-button>
</span>
<span class="mr-8">
<el-button
type="primary"
text
@click.stop="editPwdUser(row)"
:title="$t('views.userManage.setting.updatePwd')"
>
<el-icon><Lock /></el-icon>
</el-button>
</span>
<span>
<el-button
:disabled="row.role === 'ADMIN'"
type="primary"
text
@click.stop="deleteUserManage(row)"
:title="$t('common.delete')"
>
<el-icon><Delete /></el-icon>
</el-button>
</span>
</template>
</el-table-column>
</app-table>
</el-card>
<UserDrawer :title="title" ref="UserDrawerRef" @refresh="refresh" />
<UserPwdDialog ref="UserPwdDialogRef" @refresh="refresh" />
</div>
</template>
<script lang="ts" setup>
import { onMounted, ref, reactive, watch } from 'vue'
import UserDrawer from './component/UserDrawer.vue'
import UserPwdDialog from './component/UserPwdDialog.vue'
import userManageApi from '@/api/user/user-manage'
import { datetimeFormat } from '@/utils/time'
import { MsgSuccess, MsgConfirm } from '@/utils/message'
import { t } from '@/locales'
const search_type = ref('name')
const search_form = ref<{
name: string
}>({
name: '',
})
const UserDrawerRef = ref()
const UserPwdDialogRef = ref()
const loading = ref(false)
const paginationConfig = reactive({
current_page: 1,
page_size: 20,
total: 0,
})
const userTableData = ref<any[]>([])
const search_type_change = () => {
search_form.value = { name: '' }
}
function handleSizeChange() {
paginationConfig.current_page = 1
getList()
}
function getList() {
return userManageApi
.getUserManage(paginationConfig, search_form.value.name, loading)
.then((res) => {
userTableData.value = res.data.records
paginationConfig.total = res.data.total
})
}
function changeState(row: any) {
const obj = {
is_active: !row.is_active,
}
const str = obj.is_active ? t('common.status.enableSuccess') : t('common.status.disableSuccess')
userManageApi
.putUserManage(row.id, obj, loading)
.then((res) => {
getList()
MsgSuccess(str)
return true
})
.catch(() => {
return false
})
}
const title = ref('')
function editUser(row: any) {
title.value = t('views.userManage.editUser')
UserDrawerRef.value.open(row)
}
function createUser() {
title.value = t('views.userManage.createUser')
UserDrawerRef.value.open(1)
// common.asyncGetValid(ValidType.User, ValidCount.User, loading).then(async (res: any) => {
// if (res?.data) {
// title.value = t('views.userManage.createUser')
// UserDrawerRef.value.open()
// } else if (res?.code === 400) {
// MsgConfirm(t('common.tip'), t('views.userManage.tip.professionalMessage'), {
// cancelButtonText: t('common.confirm'),
// confirmButtonText: t('common.professional'),
// })
// .then(() => {
// window.open('https://maxkb.cn/pricing.html', '_blank')
// })
// .catch(() => {})
// }
// })
}
function deleteUserManage(row: any) {
MsgConfirm(
`${t('views.user.delete.confirmTitle')}${row.username} ?`,
t('views.user.delete.confirmMessage'),
{
confirmButtonText: t('common.confirm'),
confirmButtonClass: 'danger',
},
)
.then(() => {
loading.value = true
userManageApi.delUserManage(row.id, loading).then(() => {
MsgSuccess(t('common.deleteSuccess'))
getList()
})
})
.catch(() => {})
}
function editPwdUser(row: any) {
UserPwdDialogRef.value.open(row)
}
function refresh() {
getList()
}
onMounted(() => {
getList()
})
</script>
<style lang="scss" scoped></style>

View File

@ -51,31 +51,8 @@
>
</el-input>
</el-form-item>
<el-form-item
:label="$t('views.userManage.form.password.label')"
prop="password"
v-if="!isEdit"
>
<el-input
type="password"
v-model="userForm.password"
:placeholder="$t('views.userManage.form.password.placeholder')"
show-password
>
</el-input>
</el-form-item>
<el-form-item
:label="$t('views.userManage.form.password.label')"
prop="password"
v-if="!isEdit"
>
<el-input
type="password"
v-model="userForm.password"
:placeholder="$t('views.userManage.form.password.placeholder')"
show-password
>
</el-input>
<el-form-item label="默认密码" v-if="!isEdit">
<span>MaxKB@123</span>
</el-form-item>
</el-form>
<template #footer>

View File

@ -207,6 +207,8 @@ function editUser(row: any) {
}
function createUser() {
title.value = t('views.userManage.createUser')
UserDrawerRef.value.open()
// common.asyncGetValid(ValidType.User, ValidCount.User, loading).then(async (res: any) => {
// if (res?.data) {
// title.value = t('views.userManage.createUser')