mirror of
https://github.com/1Panel-dev/MaxKB.git
synced 2025-12-26 01:33:05 +00:00
feat: 扫码登陆功能
This commit is contained in:
parent
2e7b31dc3c
commit
aa8e68a688
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 |
|
|
@ -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'
|
||||
})
|
||||
]
|
||||
)
|
||||
])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -285,9 +285,6 @@ onBeforeUnmount(() => {
|
|||
.el-tabs__nav-wrap {
|
||||
padding: 0 16px;
|
||||
}
|
||||
.el-tabs__nav-wrap:after {
|
||||
height: 1px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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">
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
@ -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">
|
||||
|
|
|
|||
|
|
@ -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">
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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')
|
||||
|
|
|
|||
Loading…
Reference in New Issue