mirror of
https://github.com/1Panel-dev/MaxKB.git
synced 2025-12-26 01:33:05 +00:00
feat: ui add system settings
This commit is contained in:
parent
40df595173
commit
0cb33dad5a
|
|
@ -17,16 +17,18 @@ from common.utils.common import encryption
|
|||
from users.api.login import LoginAPI, CaptchaAPI
|
||||
from users.serializers.login import LoginSerializer, CaptchaSerializer
|
||||
|
||||
|
||||
def _get_details(request):
|
||||
path = request.path
|
||||
body = request.data
|
||||
query = request.query_params
|
||||
return {
|
||||
'path':path,
|
||||
'body':{**body, 'password': encryption(body.get('password',''))},
|
||||
'path': path,
|
||||
'body': {**body, 'password': encryption(body.get('password', ''))},
|
||||
'query': query
|
||||
}
|
||||
|
||||
|
||||
class LoginView(APIView):
|
||||
@extend_schema(methods=['POST'],
|
||||
description=_("Log in"),
|
||||
|
|
@ -37,7 +39,7 @@ class LoginView(APIView):
|
|||
responses=LoginAPI.get_response())
|
||||
@log(menu='User management', operate='Log in', get_user=lambda r: {'username': r.data.get('username', None)},
|
||||
get_details=_get_details,
|
||||
get_operation_object=lambda r,k: {'name': r.data.get('username')})
|
||||
get_operation_object=lambda r, k: {'name': r.data.get('username')})
|
||||
def post(self, request: Request):
|
||||
return result.success(LoginSerializer().login(request.data))
|
||||
|
||||
|
|
|
|||
|
|
@ -32,10 +32,11 @@ def get_user_operation_object(user_id):
|
|||
user_model = QuerySet(model=User).filter(id=user_id).first()
|
||||
if user_model is not None:
|
||||
return {
|
||||
"name":user_model.name
|
||||
"name": user_model.name
|
||||
}
|
||||
return {}
|
||||
|
||||
|
||||
def get_re_password_details(request):
|
||||
path = request.path
|
||||
body = request.data
|
||||
|
|
@ -43,12 +44,11 @@ def get_re_password_details(request):
|
|||
return {
|
||||
"path": path,
|
||||
"body": {**body, 'password': encryption(body.get('password', '')),
|
||||
're_password': encryption(body.get('re_password',''))},
|
||||
're_password': encryption(body.get('re_password', ''))},
|
||||
"query": query
|
||||
}
|
||||
|
||||
|
||||
|
||||
class UserProfileView(APIView):
|
||||
authentication_classes = [TokenAuth]
|
||||
|
||||
|
|
@ -145,7 +145,7 @@ class UserManage(APIView):
|
|||
responses=DefaultModelResponse.get_response())
|
||||
@has_permissions(PermissionConstants.USER_DELETE)
|
||||
@log(menu='User management', operate='Delete user',
|
||||
get_operation_object= lambda r,k: get_user_operation_object(k.get('user_id')))
|
||||
get_operation_object=lambda r, k: get_user_operation_object(k.get('user_id')))
|
||||
def delete(self, request: Request, user_id):
|
||||
return result.success(UserManageSerializer.Operate(data={'id': user_id}).delete(with_valid=True))
|
||||
|
||||
|
|
@ -185,7 +185,7 @@ class UserManage(APIView):
|
|||
responses=DefaultModelResponse.get_response())
|
||||
@has_permissions(PermissionConstants.USER_DELETE)
|
||||
@log(menu='User management', operate='Batch delete user',
|
||||
get_operation_object= lambda r,k: get_user_operation_object(k.get('user_id')))
|
||||
get_operation_object=lambda r, k: get_user_operation_object(k.get('user_id')))
|
||||
def post(self, request: Request):
|
||||
return result.success(UserManageSerializer.BatchDelete(data=request.data).batch_delete(with_valid=True))
|
||||
|
||||
|
|
@ -201,7 +201,7 @@ class UserManage(APIView):
|
|||
request=ChangeUserPasswordApi.get_request(),
|
||||
responses=DefaultModelResponse.get_response())
|
||||
@log(menu='User management', operate='Change password',
|
||||
get_operation_object= lambda r,k: get_user_operation_object(k.get('user_id')),
|
||||
get_operation_object=lambda r, k: get_user_operation_object(k.get('user_id')),
|
||||
get_details=get_re_password_details)
|
||||
def put(self, request: Request, user_id):
|
||||
return result.success(
|
||||
|
|
|
|||
|
|
@ -16,12 +16,15 @@
|
|||
"@codemirror/lang-json": "^6.0.1",
|
||||
"@codemirror/lang-python": "^6.2.1",
|
||||
"@codemirror/theme-one-dark": "^6.1.2",
|
||||
"@wecom/jssdk": "^2.3.1",
|
||||
"axios": "^1.8.4",
|
||||
"dingtalk-jsapi": "^3.1.0",
|
||||
"element-plus": "^2.9.10",
|
||||
"nprogress": "^0.2.0",
|
||||
"pinia": "^3.0.1",
|
||||
"use-element-plus-theme": "^0.0.5",
|
||||
"vue": "^3.5.13",
|
||||
"vue-clipboard3": "^2.0.0",
|
||||
"vue-codemirror": "^6.1.1",
|
||||
"vue-i18n": "^11.1.3",
|
||||
"vue-router": "^4.5.0"
|
||||
|
|
|
|||
|
|
@ -0,0 +1,38 @@
|
|||
import {Result} from '@/request/Result'
|
||||
import {get, post, put} from '@/request/index'
|
||||
import {type Ref} from 'vue'
|
||||
|
||||
const prefix = '/auth'
|
||||
/**
|
||||
* 获取认证设置
|
||||
*/
|
||||
const getAuthSetting: (auth_type: string, loading?: Ref<boolean>) => Promise<Result<any>> = (auth_type, loading) => {
|
||||
return get(`${prefix}/${auth_type}/detail`, undefined, loading)
|
||||
}
|
||||
|
||||
/**
|
||||
* ldap连接测试
|
||||
*/
|
||||
const postAuthSetting: (data: any, loading?: Ref<boolean>) => Promise<Result<any>> = (
|
||||
data,
|
||||
loading
|
||||
) => {
|
||||
return post(`${prefix}/connection`, data, undefined, loading)
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改邮箱设置
|
||||
*/
|
||||
const putAuthSetting: (auth_type: string, data: any, loading?: Ref<boolean>) => Promise<Result<any>> = (
|
||||
auth_type,
|
||||
data,
|
||||
loading
|
||||
) => {
|
||||
return put(`${prefix}/${auth_type}/info`, data, undefined, loading)
|
||||
}
|
||||
|
||||
export default {
|
||||
getAuthSetting,
|
||||
postAuthSetting,
|
||||
putAuthSetting
|
||||
}
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
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 = '/email_setting'
|
||||
/**
|
||||
* 获取邮箱设置
|
||||
*/
|
||||
const getEmailSetting: (loading?: Ref<boolean>) => Promise<Result<any>> = (loading) => {
|
||||
return get(`${prefix}`, undefined, loading)
|
||||
}
|
||||
|
||||
/**
|
||||
* 邮箱测试
|
||||
*/
|
||||
const postTestEmail: (data: any, loading?: Ref<boolean>) => Promise<Result<any>> = (
|
||||
data,
|
||||
loading
|
||||
) => {
|
||||
return post(`${prefix}`, data, undefined, loading)
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改邮箱设置
|
||||
*/
|
||||
const putEmailSetting: (data: any, loading?: Ref<boolean>) => Promise<Result<any>> = (
|
||||
data,
|
||||
loading
|
||||
) => {
|
||||
return put(`${prefix}`, data, undefined, loading)
|
||||
}
|
||||
|
||||
export default {
|
||||
getEmailSetting,
|
||||
postTestEmail,
|
||||
putEmailSetting
|
||||
}
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
import { Result } from '@/request/Result'
|
||||
import { get, post, del, put } from '@/request/index'
|
||||
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
|
||||
}
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
import {Result} from '@/request/Result'
|
||||
import {get, post, del, put} from '@/request/index'
|
||||
import type {Ref} from 'vue'
|
||||
|
||||
const prefix = '/display'
|
||||
|
||||
/**
|
||||
* 查看外观设置
|
||||
*/
|
||||
const getThemeInfo: (loading?: Ref<boolean>) => Promise<Result<any>> = (loading) => {
|
||||
return get(`${prefix}/info`, undefined, loading)
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新外观设置
|
||||
* @param 参数
|
||||
* * formData {
|
||||
* theme
|
||||
* icon
|
||||
* loginLogo
|
||||
* loginImage
|
||||
* title
|
||||
* slogan
|
||||
* }
|
||||
*/
|
||||
const postThemeInfo: (data: any, loading?: Ref<boolean>) => Promise<Result<boolean>> = (
|
||||
data,
|
||||
loading
|
||||
) => {
|
||||
return put(`${prefix}/update`, data, undefined, loading)
|
||||
}
|
||||
|
||||
export default {
|
||||
getThemeInfo,
|
||||
postThemeInfo
|
||||
}
|
||||
|
|
@ -1,7 +1,9 @@
|
|||
import { Result } from '@/request/Result'
|
||||
import { get, post } from '@/request/index'
|
||||
import type { LoginRequest } from '@/api/type/login'
|
||||
import type { Ref } from 'vue'
|
||||
import {Result} from '@/request/Result'
|
||||
import {get, post} from '@/request/index'
|
||||
import type {LoginRequest} from '@/api/type/login'
|
||||
import type {Ref} from 'vue'
|
||||
import type {User} from "@/api/type/user.ts";
|
||||
|
||||
/**
|
||||
* 登录
|
||||
* @param request 登录接口请求表单
|
||||
|
|
@ -15,6 +17,23 @@ const login: (request: LoginRequest, loading?: Ref<boolean>) => Promise<Result<a
|
|||
return post('/user/login', request, undefined, loading)
|
||||
}
|
||||
|
||||
const ldapLogin: (request: LoginRequest, loading?: Ref<boolean>) => Promise<Result<any>> = (
|
||||
request,
|
||||
loading,
|
||||
) => {
|
||||
return post('/ldap/login', request, undefined, loading)
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 登出
|
||||
* @param loading 接口加载器
|
||||
* @returns
|
||||
*/
|
||||
const logout: (loading?: Ref<boolean>) => Promise<Result<boolean>> = (loading) => {
|
||||
return post('/user/logout', undefined, undefined, loading)
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取验证码
|
||||
* @param loading 接口加载器
|
||||
|
|
@ -23,7 +42,75 @@ const getCaptcha: (loading?: Ref<boolean>) => Promise<Result<any>> = (loading) =
|
|||
return get('/user/captcha', undefined, loading)
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取登录方式
|
||||
*/
|
||||
const getAuthType: (loading?: Ref<boolean>) => Promise<Result<any>> = (loading) => {
|
||||
return get('auth/types', undefined, loading)
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取二维码类型
|
||||
*/
|
||||
const getQrType: (loading?: Ref<boolean>) => Promise<Result<any>> = (loading) => {
|
||||
return get('qr_type', undefined, loading)
|
||||
}
|
||||
|
||||
const getQrSource: (loading?: Ref<boolean>) => Promise<Result<any>> = (loading) => {
|
||||
return get('qr_type/source', undefined, loading)
|
||||
}
|
||||
|
||||
const getDingCallback: (code: string, loading?: Ref<boolean>) => Promise<Result<any>> = (
|
||||
code,
|
||||
loading
|
||||
) => {
|
||||
return get('dingtalk', {code}, loading)
|
||||
}
|
||||
|
||||
const getDingOauth2Callback: (code: string, loading?: Ref<boolean>) => Promise<Result<any>> = (
|
||||
code,
|
||||
loading
|
||||
) => {
|
||||
return get('dingtalk/oauth2', {code}, loading)
|
||||
}
|
||||
|
||||
const getWecomCallback: (code: string, loading?: Ref<boolean>) => Promise<Result<any>> = (
|
||||
code,
|
||||
loading
|
||||
) => {
|
||||
return get('wecom', {code}, loading)
|
||||
}
|
||||
const getLarkCallback: (code: string, loading?: Ref<boolean>) => Promise<Result<any>> = (
|
||||
code,
|
||||
loading
|
||||
) => {
|
||||
return get('lark/oauth2', {code}, loading)
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置语言
|
||||
* data: {
|
||||
* "language": "string"
|
||||
* }
|
||||
*/
|
||||
const postLanguage: (data: any, loading?: Ref<boolean>) => Promise<Result<User>> = (
|
||||
data,
|
||||
loading
|
||||
) => {
|
||||
return post('/user/language', data, undefined, loading)
|
||||
}
|
||||
|
||||
export default {
|
||||
login,
|
||||
logout,
|
||||
getCaptcha,
|
||||
getAuthType,
|
||||
getDingCallback,
|
||||
getQrType,
|
||||
getWecomCallback,
|
||||
postLanguage,
|
||||
getDingOauth2Callback,
|
||||
getLarkCallback,
|
||||
getQrSource,
|
||||
ldapLogin
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,6 +12,13 @@ const getUserProfile: (loading?: Ref<boolean>) => Promise<Result<User>> = (loadi
|
|||
return get('/user/profile', undefined, loading)
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取profile
|
||||
*/
|
||||
const getProfile: (loading?: Ref<boolean>) => Promise<Result<any>> = (loading) => {
|
||||
return get('/profile', undefined, loading)
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取版本profile
|
||||
*/
|
||||
|
|
@ -21,4 +28,5 @@ const getUserProfile: (loading?: Ref<boolean>) => Promise<Result<User>> = (loadi
|
|||
|
||||
export default {
|
||||
getUserProfile,
|
||||
getProfile
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 |
|
|
@ -0,0 +1,3 @@
|
|||
<svg width="40" height="40" viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M32.7371 16.6285C32.6394 16.9833 32.5125 17.3284 32.3563 17.6604H32.3595L32.34 17.7027C31.23 20.1961 28.3297 25.0917 28.3297 25.0917L28.3167 25.0591L27.4704 26.6085H31.5523L23.7531 37.5L25.5239 30.0915H22.3079L23.4244 25.1926C22.5195 25.4205 21.455 25.7362 20.1888 26.1626C20.1888 26.1626 18.4799 27.214 15.2607 24.138C15.2607 24.138 13.0895 22.1328 14.3492 21.6283C14.8831 21.4167 16.9468 21.1466 18.5711 20.9122C20.7617 20.5997 22.1126 20.4337 22.1126 20.4337C22.1126 20.4337 15.3518 20.5411 13.7471 20.2742C12.1423 20.0105 10.1112 17.1982 9.67497 14.7276C9.67497 14.7276 9.00443 13.3702 11.1137 14.0115C13.2262 14.6527 21.9661 16.5113 21.9661 16.5113C21.9661 16.5113 10.6027 12.8559 9.84749 11.964C9.09232 11.0721 7.62103 7.08794 7.81308 4.64339C7.81308 4.64339 7.89771 4.03144 8.49339 4.19419C8.49339 4.19419 16.898 8.23047 22.6431 10.4341C28.3883 12.6443 33.3816 13.7706 32.7371 16.6285Z" fill="#3296FA"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1023 B |
|
|
@ -0,0 +1,12 @@
|
|||
<svg width="40" height="40" viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<mask id="mask0_7009_1160" style="mask-type:luminance" maskUnits="userSpaceOnUse" x="4" y="6" width="34" height="28">
|
||||
<path d="M37.5349 6.66666H4.00006V33.3333H37.5349V6.66666Z" fill="white"/>
|
||||
</mask>
|
||||
<g mask="url(#mask0_7009_1160)">
|
||||
<path d="M21.4253 20.6715C21.4526 20.644 21.4819 20.6185 21.5093 20.5911C21.5659 20.5381 21.6207 20.4833 21.6755 20.4284L21.7889 20.3171L22.1305 19.9789L22.5946 19.5204L22.9929 19.1257L23.3674 18.7548L23.7585 18.3693L24.1166 18.0148L24.6154 17.5215C24.7103 17.4284 24.8073 17.3369 24.9059 17.2475C25.0867 17.0812 25.2731 16.9223 25.465 16.7688C25.6403 16.6281 25.8213 16.4928 26.0039 16.3613C26.2615 16.1787 26.5283 16.0068 26.8006 15.8461C27.0674 15.6889 27.3414 15.5409 27.6227 15.4057C27.8859 15.2779 28.1563 15.1609 28.4303 15.0531C28.5838 14.9928 28.7373 14.9361 28.8945 14.8832C28.973 14.8557 29.0515 14.8301 29.1302 14.8064C28.4267 12.0365 27.1405 9.50052 25.4065 7.33532C25.0703 6.91146 24.555 6.66666 24.0106 6.66666H9.62754C9.36634 6.66666 9.25848 6.99919 9.46861 7.15266C14.3617 10.7393 18.4434 15.3637 21.3923 20.7025C21.4033 20.6933 21.4142 20.6824 21.4253 20.6715Z" fill="#00D6B9"/>
|
||||
<path d="M15.721 33.3333C23.1482 33.3333 29.6179 29.2351 32.9945 23.1764C33.1133 22.9645 33.2283 22.7489 33.3379 22.5315C33.1662 22.8603 32.9781 23.1709 32.7715 23.4633C32.6985 23.5675 32.6235 23.6697 32.545 23.7703C32.4482 23.8963 32.3477 24.0169 32.2454 24.1356C32.1631 24.2288 32.0809 24.3201 31.995 24.4097C31.8215 24.5907 31.6406 24.7605 31.4523 24.9213C31.3482 25.0108 31.2423 25.0967 31.1326 25.1808C31.0047 25.2776 30.875 25.3708 30.7435 25.4603C30.6594 25.5169 30.5754 25.5717 30.4895 25.6229C30.3999 25.6777 30.3105 25.7306 30.2173 25.7818C30.0309 25.886 29.8409 25.9811 29.6473 26.0688C29.4791 26.1455 29.3074 26.2149 29.1338 26.2788C28.9455 26.3483 28.7538 26.4085 28.5601 26.4633C28.2714 26.5437 27.979 26.6059 27.6794 26.6535C27.4638 26.6863 27.2463 26.7119 27.0253 26.7265C26.7951 26.7429 26.5613 26.7484 26.3255 26.7448C26.0679 26.741 25.8085 26.7247 25.5454 26.6955C25.3535 26.6753 25.1598 26.6461 24.9679 26.6132C24.7999 26.584 24.6318 26.5475 24.4619 26.5072C24.3723 26.4853 24.2846 26.4633 24.1951 26.4396C23.9466 26.372 23.6999 26.3044 23.4551 26.2332C23.3327 26.1984 23.2103 26.1619 23.0879 26.1253C22.9051 26.0705 22.7225 26.0157 22.5415 25.9591C22.3935 25.9135 22.2438 25.8659 22.0958 25.8165C21.9551 25.7709 21.8143 25.7252 21.6737 25.6777C21.5787 25.6448 21.4818 25.6137 21.3869 25.5809C21.2699 25.5407 21.1549 25.5005 21.0397 25.4603C20.9575 25.4311 20.8734 25.4018 20.7913 25.3727C20.6267 25.3141 20.4642 25.2539 20.3015 25.1936C20.2065 25.1588 20.1134 25.1223 20.0183 25.0857C19.8923 25.0383 19.7662 24.9889 19.6401 24.9396C19.5067 24.8865 19.3751 24.8355 19.2418 24.7807C19.1559 24.7459 19.0701 24.7112 18.9842 24.6747C18.8782 24.6308 18.7705 24.5869 18.6645 24.5413C18.5822 24.5065 18.5001 24.4719 18.4178 24.4371C18.3319 24.4005 18.2461 24.364 18.1619 24.3257C18.0889 24.2928 18.0139 24.2617 17.9409 24.2288C17.8733 24.1996 17.8057 24.1685 17.7381 24.1393C17.6705 24.1083 17.6011 24.0772 17.5335 24.0461C17.4622 24.0132 17.3927 23.9821 17.3215 23.9493C17.2319 23.9072 17.1443 23.8671 17.0547 23.8251C16.9615 23.7812 16.8685 23.7373 16.777 23.6935C16.6783 23.646 16.5797 23.5985 16.481 23.5492C16.397 23.5071 16.313 23.4669 16.2289 23.4249C11.7817 21.2013 7.83513 18.2377 4.46597 14.6475C4.29787 14.4684 4.00006 14.5872 4.00006 14.832L4.00737 27.5249V28.5554C4.00737 29.1529 4.30337 29.7156 4.80034 30.0464C7.92833 32.122 11.683 33.3333 15.721 33.3333Z" fill="#3370FF"/>
|
||||
<path d="M32.9945 23.1764C32.9909 23.1819 32.9871 23.1873 32.9854 23.1929L32.9945 23.1764L33.0621 23.0521C33.0383 23.0943 33.0163 23.1344 32.9945 23.1764Z" fill="#133C92"/>
|
||||
<path d="M33.3033 22.5954L33.3216 22.5625L33.3306 22.5461C33.3215 22.5625 33.3123 22.5789 33.3033 22.5954Z" fill="#133C92"/>
|
||||
<path d="M37.5348 15.4422C36.0292 14.7005 34.3355 14.2838 32.5431 14.2838C31.4779 14.2838 30.4475 14.43 29.4718 14.706C29.3586 14.737 29.2471 14.7717 29.1356 14.8064C29.0571 14.832 28.9784 14.8576 28.8999 14.8832C28.7428 14.9361 28.5892 14.9928 28.4358 15.053C28.1618 15.1609 27.8914 15.2778 27.6283 15.4057C27.3468 15.5409 27.0728 15.6889 26.806 15.8461C26.532 16.0068 26.2671 16.1786 26.0094 16.3613C25.8248 16.491 25.6458 16.6281 25.4704 16.7688C25.2786 16.9222 25.0922 17.083 24.9114 17.2474C24.8127 17.3369 24.7158 17.4284 24.6208 17.5214L24.122 18.0148L23.7639 18.3693L23.3728 18.7548L22.9983 19.1257L22.6 19.5204L22.1359 19.9789L21.7943 20.317L21.681 20.4284C21.6262 20.4833 21.5695 20.5362 21.5147 20.591C21.4874 20.6185 21.458 20.644 21.4307 20.6714C21.3887 20.7116 21.3466 20.7518 21.3046 20.792C21.2571 20.8358 21.2114 20.8797 21.1639 20.9236C19.9324 22.0545 18.5566 23.0321 17.0675 23.8232C17.1552 23.8652 17.2448 23.9073 17.3343 23.9474C17.4038 23.9804 17.475 24.0132 17.5463 24.0442C17.6139 24.0753 17.6832 24.1064 17.7508 24.1374C17.8184 24.1685 17.886 24.1977 17.9536 24.227C18.0268 24.2598 18.1016 24.2928 18.1748 24.3238C18.2607 24.3604 18.3447 24.3988 18.4306 24.4353C18.5128 24.47 18.595 24.5048 18.6772 24.5394C18.7832 24.585 18.891 24.6289 18.997 24.6728C19.0828 24.7076 19.1687 24.7422 19.2546 24.7788C19.3862 24.8317 19.5195 24.8848 19.6528 24.9377C19.779 24.987 19.9051 25.0364 20.0311 25.084C20.1262 25.1205 20.2194 25.1552 20.3143 25.1917C20.477 25.252 20.6395 25.3122 20.804 25.3708C20.8862 25.4 20.9684 25.4292 21.0524 25.4585C21.1676 25.4986 21.2846 25.5389 21.3996 25.579C21.4946 25.612 21.5915 25.6448 21.6864 25.6758C21.8271 25.7234 21.9679 25.769 22.1086 25.8148C22.2566 25.8622 22.4046 25.9097 22.5543 25.9573C22.7352 26.0138 22.9179 26.0688 23.1007 26.1236C23.2231 26.1601 23.3455 26.1966 23.4679 26.2313C23.7127 26.3026 23.9594 26.372 24.2079 26.4378C24.2974 26.4616 24.3851 26.4853 24.4747 26.5054C24.6446 26.5456 24.8127 26.5804 24.9807 26.6113C25.1744 26.6461 25.3663 26.6734 25.5582 26.6936C25.8194 26.7228 26.0807 26.7393 26.3383 26.7429C26.574 26.7466 26.806 26.741 27.038 26.7246C27.2591 26.7082 27.4766 26.6845 27.6922 26.6516C27.9918 26.6058 28.2842 26.542 28.5728 26.4616C28.7666 26.4085 28.9584 26.3465 29.1466 26.277C29.3202 26.213 29.4919 26.1436 29.66 26.0669C29.8536 25.9792 30.0436 25.8842 30.23 25.78C30.3214 25.7289 30.4127 25.6758 30.5023 25.621C30.5882 25.5681 30.6722 25.5133 30.7563 25.4585C30.8878 25.3689 31.0175 25.2757 31.1454 25.1789C31.2532 25.0949 31.361 25.009 31.4651 24.9194C31.6534 24.7586 31.8343 24.5869 32.0078 24.4078C32.0936 24.3184 32.1759 24.2288 32.2582 24.1338C32.3604 24.015 32.461 23.8945 32.5578 23.7684C32.6346 23.6678 32.7112 23.5656 32.7843 23.4614C32.989 23.1709 33.1754 22.8621 33.3452 22.5369L33.5371 22.1532L35.2766 18.6872L35.2728 18.6945C35.8192 17.474 36.592 16.3741 37.5348 15.4422Z" fill="#133C9A"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 6.7 KiB |
|
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 128 128"><path fill="#de1c59" d="M27.255 80.719c0 7.33-5.978 13.317-13.309 13.317S.63 88.049.63 80.719s5.987-13.317 13.317-13.317h13.309zm6.709 0c0-7.33 5.987-13.317 13.317-13.317s13.317 5.986 13.317 13.317v33.335c0 7.33-5.986 13.317-13.317 13.317c-7.33 0-13.317-5.987-13.317-13.317zm0 0"/><path fill="#35c5f0" d="M47.281 27.255c-7.33 0-13.317-5.978-13.317-13.309S39.951.63 47.281.63s13.317 5.987 13.317 13.317v13.309zm0 6.709c7.33 0 13.317 5.987 13.317 13.317s-5.986 13.317-13.317 13.317H13.946C6.616 60.598.63 54.612.63 47.281c0-7.33 5.987-13.317 13.317-13.317zm0 0"/><path fill="#2eb57d" d="M100.745 47.281c0-7.33 5.978-13.317 13.309-13.317s13.317 5.987 13.317 13.317s-5.987 13.317-13.317 13.317h-13.309zm-6.709 0c0 7.33-5.987 13.317-13.317 13.317s-13.317-5.986-13.317-13.317V13.946C67.402 6.616 73.388.63 80.719.63c7.33 0 13.317 5.987 13.317 13.317zm0 0"/><path fill="#ebb02e" d="M80.719 100.745c7.33 0 13.317 5.978 13.317 13.309s-5.987 13.317-13.317 13.317s-13.317-5.987-13.317-13.317v-13.309zm0-6.709c-7.33 0-13.317-5.987-13.317-13.317s5.986-13.317 13.317-13.317h33.335c7.33 0 13.317 5.986 13.317 13.317c0 7.33-5.987 13.317-13.317 13.317zm0 0"/></svg>
|
||||
|
After Width: | Height: | Size: 1.2 KiB |
|
|
@ -0,0 +1,7 @@
|
|||
<svg width="40" height="40" viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M26.9508 30.4241C26.7624 30.6118 26.7508 30.9095 26.9144 31.1116C26.9254 31.1254 26.9374 31.1388 26.9508 31.1522C26.9732 31.1746 26.9976 31.1938 27.0227 31.2117C27.0729 31.2584 27.1227 31.3066 27.1715 31.3554C28.1351 32.3193 28.7408 33.5049 28.9893 34.7479C28.9934 34.8177 29.001 34.8875 29.0127 34.9569C29.0247 35.0332 29.0409 35.1092 29.0612 35.1841C29.1564 35.5334 29.341 35.8637 29.6153 36.1381C30.4561 36.9789 31.8195 36.9789 32.6599 36.1381C33.5008 35.2972 33.5008 33.9342 32.6599 33.0934C32.3636 32.7967 32.002 32.6056 31.6218 32.518C31.5795 32.5083 31.5365 32.4997 31.4936 32.4929C31.4506 32.4856 31.4073 32.4795 31.3633 32.4746C30.0893 32.2375 28.8714 31.6263 27.8862 30.6407C27.815 30.5699 27.7459 30.4974 27.6792 30.4238L27.6789 30.4241C27.5782 30.3238 27.4465 30.2732 27.3149 30.2732C27.1829 30.2732 27.0512 30.3238 26.9508 30.4241Z" fill="#FB6500"/>
|
||||
<path d="M35.7037 27.0198C35.4074 27.3168 35.2163 27.6781 35.1286 28.0583C35.119 28.1006 35.1104 28.1436 35.1028 28.1865C35.096 28.2298 35.0898 28.2732 35.0853 28.3168C34.8481 29.5904 34.2369 30.8087 33.2514 31.7943C33.1802 31.8651 33.1077 31.9342 33.0345 32.0009L33.0348 32.0012C32.8337 32.2023 32.8337 32.5282 33.0348 32.7293C33.2225 32.9173 33.5198 32.9293 33.7223 32.7657C33.7357 32.7547 33.7495 32.7427 33.7625 32.7293C33.7849 32.7069 33.8045 32.6829 33.8223 32.6578C33.8691 32.6072 33.9165 32.5577 33.966 32.5086C34.9296 31.545 36.1152 30.9393 37.3582 30.6911C37.4284 30.687 37.4981 30.6795 37.5676 30.6678C37.6439 30.6557 37.7195 30.6396 37.7945 30.6186C38.1444 30.5234 38.4744 30.3395 38.7487 30.0652C39.5895 29.2243 39.5895 27.8613 38.7487 27.0198C38.3283 26.5994 37.7773 26.3894 37.2262 26.3894C36.6752 26.3894 36.1241 26.5994 35.7037 27.0198Z" fill="#3880E7"/>
|
||||
<path d="M29.6306 20.9315C28.7905 21.7723 28.7905 23.1357 29.6306 23.9765C29.9269 24.2728 30.2882 24.4643 30.6684 24.5516C30.7114 24.5612 30.7544 24.5698 30.7973 24.577C30.8406 24.5842 30.884 24.5904 30.9273 24.5949C32.2016 24.8324 33.4192 25.4433 34.4047 26.4288C34.4759 26.4997 34.545 26.5729 34.612 26.6458C34.8134 26.8469 35.139 26.8469 35.3404 26.6458C35.5285 26.4577 35.5401 26.1604 35.3765 25.9582C35.3648 25.9441 35.3531 25.9304 35.3404 25.9177C35.3181 25.895 35.2933 25.8757 35.2686 25.8585C35.218 25.8111 35.1685 25.763 35.1194 25.7142C34.1555 24.7506 33.5501 23.565 33.3013 22.3216C33.2978 22.2519 33.2899 22.1821 33.2789 22.113C33.2669 22.0367 33.25 21.9607 33.2301 21.8854C33.1342 21.5362 32.9499 21.2061 32.676 20.9315C32.2556 20.5111 31.7042 20.3007 31.1531 20.3007C30.6021 20.3007 30.051 20.5111 29.6306 20.9315Z" fill="#2DBC00"/>
|
||||
<path d="M28.5685 24.3038C28.5547 24.3155 28.541 24.3272 28.5283 24.3406C28.5056 24.3629 28.4863 24.3866 28.4684 24.4121C28.4217 24.4623 28.3739 24.5118 28.3248 24.5609C27.3612 25.5248 26.1756 26.1305 24.9322 26.3784C24.8624 26.3825 24.7927 26.3904 24.7232 26.4017C24.6469 26.4134 24.5709 26.4303 24.496 26.4509C24.1464 26.5461 23.8164 26.7304 23.5417 27.0043C22.7009 27.8455 22.7009 29.2089 23.5417 30.0497C24.3829 30.8905 25.7459 30.8905 26.5871 30.0497C26.883 29.7537 27.0749 29.3921 27.1625 29.0119C27.1718 28.9689 27.1807 28.9259 27.1873 28.883C27.1948 28.8397 27.2003 28.7964 27.2055 28.753C27.443 27.4791 28.0539 26.2611 29.0394 25.2753C29.1106 25.2044 29.1831 25.1357 29.2567 25.0687L29.2563 25.0683C29.4571 24.8672 29.4571 24.5413 29.2563 24.3406C29.1556 24.2399 29.024 24.1897 28.892 24.1897C28.7771 24.1897 28.6627 24.2278 28.5685 24.3038Z" fill="#FFCC00"/>
|
||||
<path d="M15.5511 5.69592C11.6835 6.11633 8.16861 7.77118 5.65369 10.3549C4.66092 11.3748 3.85206 12.5143 3.24911 13.7419C2.43097 15.407 2.01605 17.175 2.01605 18.9962C2.01605 21.341 2.72969 23.6527 4.07997 25.6823C4.7637 26.7105 5.8816 28.0054 6.91046 28.9177V28.9198L6.4457 32.5736C6.42714 32.6234 6.4072 32.6726 6.39586 32.7252C6.3852 32.7726 6.38211 32.8221 6.37695 32.8713C6.3742 32.9084 6.36595 32.9448 6.36595 32.9833C6.36595 33.0239 6.3742 33.0624 6.37798 33.1026C6.43917 33.7038 6.94037 34.1751 7.55776 34.1751C7.77295 34.1751 7.97198 34.1136 8.14592 34.0136C8.15142 34.0105 8.15761 34.0077 8.16277 34.0046C8.18855 33.9895 8.21433 33.9764 8.23874 33.9593L9.34907 33.402L12.6584 31.741C13.6096 32.0143 14.5536 32.1886 15.5487 32.2979C16.1957 32.369 16.8519 32.4055 17.5006 32.4055C18.1613 32.4055 18.8467 32.3656 19.5366 32.2872C20.8941 32.1329 22.1997 31.8197 23.439 31.3752C23.3049 31.3302 23.1733 31.2725 23.0457 31.1996C22.2874 30.7661 21.8952 29.9411 21.9729 29.1247C21.0873 29.4059 20.1623 29.6076 19.2066 29.7163C18.6264 29.7823 18.0523 29.8156 17.5006 29.8156C16.9578 29.8156 16.4088 29.7854 15.867 29.7259C15.7543 29.7139 15.6415 29.696 15.5288 29.6809C14.7849 29.5819 14.0499 29.4299 13.3394 29.2223C13.195 29.1762 13.0414 29.1525 12.8846 29.1525C12.6392 29.1525 12.4016 29.2175 12.161 29.3423C12.1297 29.3584 12.0984 29.3718 12.0672 29.39L9.34907 30.9919L9.23151 31.0617L9.22841 31.0631C9.16929 31.0975 9.13629 31.1105 9.10604 31.1105C9.006 31.1105 8.92522 31.0263 8.92522 30.9225L9.03007 30.4993C9.05963 30.3873 9.10122 30.2302 9.15072 30.0387C9.20985 29.8142 9.27895 29.5502 9.34907 29.2842C9.42642 28.9899 9.50376 28.6932 9.56976 28.4416C9.59727 28.3436 9.62511 28.2261 9.62511 28.0941C9.62511 27.7287 9.45082 27.3839 9.15966 27.1721C9.0115 27.0628 8.86369 26.948 8.70831 26.8215C8.47043 26.6273 8.24252 26.4255 8.02355 26.2179C7.40788 25.6338 6.86749 24.9965 6.41305 24.3134C5.34431 22.7064 4.77883 20.8827 4.77883 19.0395C4.77883 17.6088 5.10608 16.218 5.75097 14.9045C6.23326 13.9237 6.88193 13.0104 7.68048 12.1902C9.74783 10.0658 12.6553 8.70379 15.867 8.35487C16.4253 8.29437 16.9753 8.26309 17.5006 8.26309C18.052 8.26309 18.6257 8.29644 19.2066 8.36278C22.4032 8.72579 25.2935 10.095 27.3454 12.217C28.1402 13.0392 28.7858 13.9547 29.2643 14.9378C29.8989 16.2424 30.2206 17.6226 30.2206 19.0395C30.2206 19.1863 30.2113 19.3334 30.2045 19.4802C31.0333 18.9728 32.1288 19.0722 32.8473 19.7899C32.8834 19.8267 32.9139 19.8663 32.9466 19.9044C32.97 19.6026 32.9837 19.2997 32.9837 18.9962C32.9837 17.1918 32.5753 15.438 31.7703 13.7838C31.1721 12.5528 30.3671 11.4109 29.3787 10.3882C26.882 7.80658 23.3864 6.14314 19.5366 5.7052C18.8454 5.62682 18.1602 5.58694 17.5006 5.58694C16.8712 5.58694 16.2156 5.62338 15.5511 5.69592Z" fill="#0082EF"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 6.2 KiB |
|
|
@ -0,0 +1,3 @@
|
|||
<svg width="40" height="40" viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M26.4855 13.974C26.9025 13.974 27.3196 14.0125 27.7345 14.0489C26.6184 8.86961 21.0198 5 14.6421 5C7.51045 5.02502 1.66672 9.84509 1.66672 15.9656C1.66672 19.4941 3.61468 22.4043 6.85045 24.6575L5.54437 28.5227L10.0863 26.2719C11.7134 26.5901 13.0196 26.9084 14.6261 26.9084C15.0432 26.9084 15.4397 26.8902 15.8361 26.8538C15.5785 25.9748 15.4451 25.0642 15.4397 24.1483C15.4581 18.537 20.3208 13.9762 26.4878 13.9762L26.4855 13.974H26.4855ZM19.5051 10.4862C20.4905 10.4862 21.1321 11.1228 21.1321 12.1004C21.1321 13.0553 20.4905 13.7147 19.5051 13.7147C18.5402 13.7147 17.5571 13.0759 17.5571 12.1003C17.5571 11.1228 18.5402 10.4862 19.5051 10.4862ZM10.4231 13.7147C9.46072 13.7147 8.47535 13.0759 8.47535 12.1003C8.47535 11.1228 9.46072 10.4862 10.4231 10.4862C11.388 10.4862 12.0503 11.1228 12.0503 12.1004C12.0503 13.0553 11.4086 13.7147 10.4231 13.7147ZM38.3334 24.0141C38.3334 18.8712 33.1497 14.6674 27.3012 14.6674C21.1343 14.6674 16.2714 18.8712 16.2714 24.0141C16.2714 29.1752 21.1343 33.3631 27.3012 33.3631C28.5892 33.3631 29.8953 33.0448 31.1994 32.7243L34.756 34.6568L33.7729 31.4464C36.3854 29.4776 38.3334 26.9038 38.3334 24.0141ZM23.7262 22.3997C23.0846 22.3997 22.42 21.7632 22.42 21.1039C22.42 20.4673 23.0846 19.808 23.7262 19.808C24.7116 19.808 25.3534 20.4469 25.3534 21.1039C25.3534 21.7632 24.7116 22.3997 23.7262 22.3997ZM30.8602 22.3997C30.2163 22.3997 29.5722 21.7632 29.5722 21.1039C29.5722 20.4673 30.214 19.808 30.8602 19.808C31.8433 19.808 32.4872 20.4469 32.4872 21.1039C32.4872 21.7632 31.8433 22.3997 30.8602 22.3997Z" fill="#09BB07"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.6 KiB |
|
|
@ -54,4 +54,44 @@ export default {
|
|||
])
|
||||
},
|
||||
},
|
||||
'app-setting': {
|
||||
iconReader: () => {
|
||||
return h('i', [
|
||||
h(
|
||||
'svg',
|
||||
{
|
||||
viewBox: '0 0 20 20',
|
||||
version: '1.1',
|
||||
xmlns: 'http://www.w3.org/2000/svg'
|
||||
},
|
||||
[
|
||||
h('path', {
|
||||
d: 'M3.60734 16.4448L3.34807 16.1624C2.44036 15.1737 1.75935 13.9944 1.36011 12.7066L1.24756 12.3435L2.95427 10.0001L1.24756 7.65668L1.36011 7.29361C1.75935 6.00574 2.44036 4.82649 3.34807 3.83779L3.60734 3.55539L6.47552 3.86889L7.64049 1.21319L8.01405 1.12909C8.66134 0.983366 9.32633 0.90918 10.0004 0.90918C10.6744 0.90918 11.3394 0.983366 11.9867 1.12909L12.3603 1.21319L13.5252 3.86889L16.3934 3.55539L16.6527 3.83779C17.5604 4.82649 18.2414 6.00574 18.6406 7.29361L18.7532 7.65668L17.0465 10.0001L18.7532 12.3435L18.6406 12.7066C18.2414 13.9944 17.5604 15.1737 16.6527 16.1624L16.3934 16.4448L13.5252 16.1313L12.3603 18.787L11.9867 18.8711C11.3394 19.0168 10.6744 19.091 10.0004 19.091C9.32633 19.091 8.66134 19.0168 8.01405 18.8711L7.64049 18.787L6.47552 16.1313L3.60734 16.4448ZM6.51159 14.6031C7.05002 14.5443 7.56436 14.8417 7.78194 15.3377L8.71565 17.4662C9.13677 17.5389 9.56603 17.5758 10.0004 17.5758C10.4347 17.5758 10.864 17.5389 11.2851 17.4662L12.2188 15.3377C12.4364 14.8417 12.9507 14.5443 13.4892 14.6031L15.7844 14.854C16.3387 14.1868 16.7757 13.4286 17.0741 12.6116L15.7038 10.7301C15.3869 10.295 15.3869 9.70511 15.7038 9.26999L17.0741 7.38847C16.7757 6.57146 16.3387 5.81331 15.7844 5.14609L13.4892 5.39696C12.9507 5.45581 12.4364 5.1584 12.2188 4.66238L11.2851 2.53389C10.864 2.46117 10.4347 2.42429 10.0004 2.42429C9.56603 2.42429 9.13677 2.46117 8.71565 2.53389L7.78194 4.66238C7.56436 5.1584 7.05002 5.45581 6.51159 5.39696L4.21641 5.14609C3.66208 5.81331 3.22502 6.57146 2.92666 7.38847L4.29697 9.26999C4.61387 9.70511 4.61387 10.295 4.29697 10.7301L2.92666 12.6116C3.22502 13.4286 3.66208 14.1868 4.21641 14.854L6.51159 14.6031ZM10.0004 13.788C7.91555 13.788 6.22693 12.0913 6.22693 10.0001C6.22693 7.9089 7.91555 6.2122 10.0004 6.2122C12.0852 6.2122 13.7738 7.9089 13.7738 10.0001C13.7738 12.0913 12.0852 13.788 10.0004 13.788ZM10.0004 12.2729C11.2468 12.2729 12.2587 11.2561 12.2587 10.0001C12.2587 8.74413 11.2468 7.72741 10.0004 7.72741C8.75397 7.72741 7.74208 8.74413 7.74208 10.0001C7.74208 11.2561 8.75397 12.2729 10.0004 12.2729Z',
|
||||
fill: 'currentColor'
|
||||
})
|
||||
]
|
||||
)
|
||||
])
|
||||
}
|
||||
},
|
||||
'app-setting-active': {
|
||||
iconReader: () => {
|
||||
return h('i', [
|
||||
h(
|
||||
'svg',
|
||||
{
|
||||
viewBox: '0 0 20 20',
|
||||
version: '1.1',
|
||||
xmlns: 'http://www.w3.org/2000/svg'
|
||||
},
|
||||
[
|
||||
h('path', {
|
||||
d: 'M3.26425 16.2151C2.35478 15.2292 1.65887 14.0432 1.25 12.7305L2.70785 10.7384C3.02952 10.2988 3.02952 9.70154 2.70785 9.26197L1.25 7.26979C1.65887 5.95714 2.35478 4.77112 3.26425 3.78522L5.71416 4.05172C6.25589 4.11065 6.77338 3.81185 6.99316 3.31321L7.98848 1.05505C8.63579 0.910018 9.30896 0.833496 10 0.833496C10.691 0.833496 11.3642 0.910018 12.0115 1.05505L13.0068 3.31321C13.2266 3.81185 13.7441 4.11065 14.2858 4.05172L16.7357 3.78522C17.6452 4.77112 18.3411 5.95714 18.75 7.26979L17.2921 9.26197C16.9705 9.70154 16.9705 10.2988 17.2921 10.7384L18.75 12.7305C18.3411 14.0432 17.6452 15.2292 16.7357 16.2151L14.2858 15.9486C13.7441 15.8897 13.2266 16.1885 13.0068 16.6871L12.0115 18.9453C11.3642 19.0903 10.691 19.1668 10 19.1668C9.30896 19.1668 8.63579 19.0903 7.98848 18.9453L6.99316 16.6871C6.77338 16.1885 6.25589 15.8897 5.71416 15.9486L3.26425 16.2151ZM10 13.3335C11.8409 13.3335 13.3333 11.8411 13.3333 10.0002C13.3333 8.15921 11.8409 6.66683 10 6.66683C8.15905 6.66683 6.66667 8.15921 6.66667 10.0002C6.66667 11.8411 8.15905 13.3335 10 13.3335Z',
|
||||
fill: 'currentColor'
|
||||
})
|
||||
]
|
||||
)
|
||||
])
|
||||
}
|
||||
},
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
import { useLocalStorage, usePreferredLanguages } from '@vueuse/core'
|
||||
import { computed } from 'vue'
|
||||
import { createI18n } from 'vue-i18n'
|
||||
import {useLocalStorage, usePreferredLanguages} from '@vueuse/core'
|
||||
import {computed} from 'vue'
|
||||
import {createI18n} from 'vue-i18n'
|
||||
|
||||
// 导入语言文件
|
||||
const langModules = import.meta.glob('./lang/*/index.ts', { eager: true }) as Record<
|
||||
const langModules = import.meta.glob('./lang/*/index.ts', {eager: true}) as Record<
|
||||
string,
|
||||
() => Promise<{ default: Object }>
|
||||
>
|
||||
|
|
@ -79,7 +79,6 @@ export const langList = computed(() => {
|
|||
return list
|
||||
})
|
||||
|
||||
// @ts-ignore
|
||||
export const { t } = i18n.global
|
||||
export const {t} = i18n.global
|
||||
|
||||
export default i18n
|
||||
|
|
|
|||
|
|
@ -32,6 +32,56 @@ const systemRouter = {
|
|||
},
|
||||
component: () => import('@/views/resource-authorization/index.vue'),
|
||||
},
|
||||
{
|
||||
path:'/system/setting',
|
||||
name: 'setting',
|
||||
meta: {
|
||||
icon: 'app-setting',
|
||||
iconActive: 'app-setting-active',
|
||||
title: 'views.system.subTitle',
|
||||
activeMenu: '/system',
|
||||
parentPath: '/system',
|
||||
parentName: 'system',
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: '/system/theme',
|
||||
name: 'theme',
|
||||
meta: {
|
||||
title: 'views.system.theme.title',
|
||||
activeMenu: '/setting',
|
||||
parentPath: '/setting',
|
||||
parentName: 'setting',
|
||||
//permission: new ComplexPermission(['ADMIN'], ['x-pack'], 'AND')
|
||||
},
|
||||
component: () => import('@/views/theme/index.vue'),
|
||||
},
|
||||
{
|
||||
path: '/system/authentication',
|
||||
name: 'authentication',
|
||||
meta: {
|
||||
title: 'views.system.authentication.title',
|
||||
activeMenu: '/setting',
|
||||
parentPath: '/setting',
|
||||
parentName: 'setting',
|
||||
//permission: new ComplexPermission(['ADMIN'], ['x-pack'], 'AND')
|
||||
},
|
||||
component: () => import('@/views/authentication/index.vue')
|
||||
},
|
||||
{
|
||||
path: '/system/email',
|
||||
name: 'email',
|
||||
meta: {
|
||||
title: 'views.system.email.title',
|
||||
activeMenu: '/setting',
|
||||
parentPath: '/setting',
|
||||
parentName: 'setting',
|
||||
//permission: new Role('ADMIN')
|
||||
},
|
||||
component: () => import('@/views/email/index.vue')
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -37,6 +37,14 @@ const useLoginStore = defineStore('login', {
|
|||
return user.profile(loading)
|
||||
})
|
||||
},
|
||||
async asyncLdapLogin(data: LoginRequest, loading?: Ref<boolean>) {
|
||||
return loginApi.ldapLogin(data).then((ok) => {
|
||||
this.token = ok?.data?.token
|
||||
localStorage.setItem('token', ok?.data?.token)
|
||||
const user = useUserStore()
|
||||
return user.profile(loading)
|
||||
})
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
import { defineStore } from 'pinia'
|
||||
import { cloneDeep } from 'lodash'
|
||||
import { useElementPlusTheme } from 'use-element-plus-theme'
|
||||
import ThemeApi from '@/api/systemSettings/theme'
|
||||
import type {Ref} from "vue";
|
||||
export interface themeStateTypes {
|
||||
themeInfo: any
|
||||
}
|
||||
|
|
@ -18,8 +20,8 @@ const useThemeStore = defineStore('theme', {
|
|||
setTheme(data?: any) {
|
||||
const { changeTheme } = useElementPlusTheme(this.themeInfo?.theme || defalueColor)
|
||||
changeTheme(defalueColor)
|
||||
// changeTheme(data?.['theme'])
|
||||
// this.themeInfo = cloneDeep(data)
|
||||
changeTheme(data?.['theme'])
|
||||
this.themeInfo = cloneDeep(data)
|
||||
},
|
||||
|
||||
// async theme(loading?: Ref<boolean>) {
|
||||
|
|
|
|||
|
|
@ -1,20 +1,25 @@
|
|||
import { defineStore } from 'pinia'
|
||||
import { type Ref } from 'vue'
|
||||
import type { User } from '@/api/type/user'
|
||||
import { cloneDeep } from 'lodash'
|
||||
import {defineStore} from 'pinia'
|
||||
import {type Ref} from 'vue'
|
||||
import type {User} from '@/api/type/user'
|
||||
import UserApi from '@/api/user/user'
|
||||
// import ThemeApi from '@/api/theme'
|
||||
// import { useElementPlusTheme } from 'use-element-plus-theme'
|
||||
import LoginApi from '@/api/user/login'
|
||||
import {cloneDeep} from 'lodash'
|
||||
import ThemeApi from '@/api/systemSettings/theme'
|
||||
// import { defaultPlatformSetting } from '@/utils/theme'
|
||||
import { useLocalStorage } from '@vueuse/core'
|
||||
import { localeConfigKey, getBrowserLang } from '@/locales/index'
|
||||
import {useLocalStorage} from '@vueuse/core'
|
||||
import {localeConfigKey, getBrowserLang} from '@/locales/index'
|
||||
import useThemeStore from './theme'
|
||||
import {useElementPlusTheme} from "use-element-plus-theme";
|
||||
import {defaultPlatformSetting} from "@/utils/theme.ts";
|
||||
|
||||
export interface userStateTypes {
|
||||
userType: number // 1 系统操作者 2 对话用户
|
||||
userInfo: User | null
|
||||
version?: string
|
||||
XPACK_LICENSE_IS_VALID: false
|
||||
isXPack: false
|
||||
XPACK_LICENSE_IS_VALID: true
|
||||
isXPack: true
|
||||
themeInfo: any
|
||||
token: any
|
||||
}
|
||||
|
||||
const useLoginStore = defineStore('user', {
|
||||
|
|
@ -24,6 +29,8 @@ const useLoginStore = defineStore('user', {
|
|||
version: '',
|
||||
XPACK_LICENSE_IS_VALID: false,
|
||||
isXPack: false,
|
||||
themeInfo: null,
|
||||
token: ''
|
||||
}),
|
||||
actions: {
|
||||
getLanguage() {
|
||||
|
|
@ -31,6 +38,14 @@ const useLoginStore = defineStore('user', {
|
|||
? localStorage.getItem('MaxKB-locale') || getBrowserLang()
|
||||
: sessionStorage.getItem('language') || getBrowserLang()
|
||||
},
|
||||
isDefaultTheme() {
|
||||
return !this.themeInfo?.theme || this.themeInfo?.theme === '#3370FF'
|
||||
},
|
||||
setTheme(data: any) {
|
||||
const {changeTheme} = useElementPlusTheme(this.themeInfo?.theme)
|
||||
changeTheme(data?.['theme'])
|
||||
this.themeInfo = cloneDeep(data)
|
||||
},
|
||||
async profile(loading?: Ref<boolean>) {
|
||||
return UserApi.getUserProfile(loading).then((ok) => {
|
||||
this.userInfo = ok.data
|
||||
|
|
@ -38,31 +53,31 @@ const useLoginStore = defineStore('user', {
|
|||
ok?.data?.language || this.getLanguage()
|
||||
const theme = useThemeStore()
|
||||
theme.setTheme()
|
||||
// return this.asyncGetProfile()
|
||||
return this.asyncGetProfile()
|
||||
})
|
||||
},
|
||||
// async asyncGetProfile() {
|
||||
// return new Promise((resolve, reject) => {
|
||||
// UserApi.getProfile()
|
||||
// .then(async (ok) => {
|
||||
// this.version = ok.data?.version || '-'
|
||||
// this.isXPack = ok.data?.IS_XPACK
|
||||
// this.XPACK_LICENSE_IS_VALID = ok.data?.XPACK_LICENSE_IS_VALID
|
||||
async asyncGetProfile() {
|
||||
return new Promise((resolve, reject) => {
|
||||
UserApi.getProfile()
|
||||
.then(async (ok) => {
|
||||
// this.version = ok.data?.version || '-'
|
||||
this.isXPack = true
|
||||
this.XPACK_LICENSE_IS_VALID = true
|
||||
|
||||
// if (this.isEnterprise()) {
|
||||
// await this.theme()
|
||||
// } else {
|
||||
// this.themeInfo = {
|
||||
// ...defaultPlatformSetting
|
||||
// }
|
||||
// }
|
||||
// resolve(ok)
|
||||
// })
|
||||
// .catch((error) => {
|
||||
// reject(error)
|
||||
// })
|
||||
// })
|
||||
// },
|
||||
if (this.isEnterprise()) {
|
||||
await this.theme()
|
||||
} else {
|
||||
this.themeInfo = {
|
||||
...defaultPlatformSetting
|
||||
}
|
||||
}
|
||||
resolve(ok)
|
||||
})
|
||||
.catch((error) => {
|
||||
reject(error)
|
||||
})
|
||||
})
|
||||
},
|
||||
|
||||
getPermissions() {
|
||||
if (this.userInfo) {
|
||||
|
|
@ -80,16 +95,26 @@ const useLoginStore = defineStore('user', {
|
|||
return ''
|
||||
}
|
||||
},
|
||||
// showXpack() {
|
||||
// return this.isXPack
|
||||
// },
|
||||
async theme(loading?: Ref<boolean>) {
|
||||
return await ThemeApi.getThemeInfo(loading).then((ok) => {
|
||||
this.setTheme(ok.data)
|
||||
// window.document.title = this.themeInfo['title'] || 'MaxKB'
|
||||
// const link = document.querySelector('link[rel="icon"]') as any
|
||||
// if (link) {
|
||||
// link['href'] = this.themeInfo['icon'] || '/favicon.ico'
|
||||
// }
|
||||
})
|
||||
},
|
||||
showXpack() {
|
||||
return this.isXPack
|
||||
},
|
||||
|
||||
// isExpire() {
|
||||
// return this.isXPack && !this.XPACK_LICENSE_IS_VALID
|
||||
// },
|
||||
// isEnterprise() {
|
||||
// return this.isXPack && this.XPACK_LICENSE_IS_VALID
|
||||
// },
|
||||
isExpire() {
|
||||
return this.isXPack && !this.XPACK_LICENSE_IS_VALID
|
||||
},
|
||||
isEnterprise() {
|
||||
return this.isXPack && this.XPACK_LICENSE_IS_VALID
|
||||
},
|
||||
|
||||
// changeUserType(num: number, token?: string) {
|
||||
// this.userType = num
|
||||
|
|
@ -97,70 +122,69 @@ const useLoginStore = defineStore('user', {
|
|||
// },
|
||||
|
||||
|
||||
async dingCallback(code: string) {
|
||||
return LoginApi.getDingCallback(code).then((ok) => {
|
||||
this.token = ok.data
|
||||
localStorage.setItem('token', ok.data)
|
||||
return this.profile()
|
||||
})
|
||||
},
|
||||
async dingOauth2Callback(code: string) {
|
||||
return LoginApi.getDingOauth2Callback(code).then((ok) => {
|
||||
this.token = ok.data
|
||||
localStorage.setItem('token', ok.data)
|
||||
return this.profile()
|
||||
})
|
||||
},
|
||||
async wecomCallback(code: string) {
|
||||
return LoginApi.getWecomCallback(code).then((ok) => {
|
||||
this.token = ok.data
|
||||
localStorage.setItem('token', ok.data)
|
||||
return this.profile()
|
||||
})
|
||||
},
|
||||
async larkCallback(code: string) {
|
||||
return LoginApi.getLarkCallback(code).then((ok) => {
|
||||
this.token = ok.data
|
||||
localStorage.setItem('token', ok.data)
|
||||
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 dingOauth2Callback(code: string) {
|
||||
// return UserApi.getDingOauth2Callback(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 larkCallback(code: string) {
|
||||
// return UserApi.getlarkCallback(code).then((ok) => {
|
||||
// this.token = ok.data
|
||||
// localStorage.setItem('token', ok.data)
|
||||
// return this.profile()
|
||||
// })
|
||||
// },
|
||||
|
||||
// async logout() {
|
||||
// return UserApi.logout().then(() => {
|
||||
// localStorage.removeItem('token')
|
||||
// return true
|
||||
// })
|
||||
// },
|
||||
// async getAuthType() {
|
||||
// return UserApi.getAuthType().then((ok) => {
|
||||
// return ok.data
|
||||
// })
|
||||
// },
|
||||
// async getQrType() {
|
||||
// return UserApi.getQrType().then((ok) => {
|
||||
// return ok.data
|
||||
// })
|
||||
// },
|
||||
// async getQrSource() {
|
||||
// return UserApi.getQrSource().then((ok) => {
|
||||
// return ok.data
|
||||
// })
|
||||
// },
|
||||
// async postUserLanguage(lang: string, loading?: Ref<boolean>) {
|
||||
// return new Promise((resolve, reject) => {
|
||||
// UserApi.postLanguage({ language: lang }, loading)
|
||||
// .then(async (ok) => {
|
||||
// useLocalStorage(localeConfigKey, 'en-US').value = lang
|
||||
// window.location.reload()
|
||||
// resolve(ok)
|
||||
// })
|
||||
// .catch((error) => {
|
||||
// reject(error)
|
||||
// })
|
||||
// })
|
||||
// }
|
||||
async logout() {
|
||||
return LoginApi.logout().then(() => {
|
||||
localStorage.removeItem('token')
|
||||
return true
|
||||
})
|
||||
},
|
||||
async getAuthType() {
|
||||
return LoginApi.getAuthType().then((ok) => {
|
||||
return ok.data
|
||||
})
|
||||
},
|
||||
async getQrType() {
|
||||
return LoginApi.getQrType().then((ok) => {
|
||||
return ok.data
|
||||
})
|
||||
},
|
||||
async getQrSource() {
|
||||
return LoginApi.getQrSource().then((ok) => {
|
||||
return ok.data
|
||||
})
|
||||
},
|
||||
async postUserLanguage(lang: string, loading?: Ref<boolean>) {
|
||||
return new Promise((resolve, reject) => {
|
||||
LoginApi.postLanguage({ language: lang }, loading)
|
||||
.then(async (ok) => {
|
||||
useLocalStorage(localeConfigKey, 'en-US').value = lang
|
||||
window.location.reload()
|
||||
resolve(ok)
|
||||
})
|
||||
.catch((error) => {
|
||||
reject(error)
|
||||
})
|
||||
})
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,167 @@
|
|||
import { MsgError } from '@/utils/message'
|
||||
|
||||
export function toThousands(num: any) {
|
||||
return num?.toString().replace(/\d+/, function (n: any) {
|
||||
return n.replace(/(\d)(?=(?:\d{3})+$)/g, '$1,')
|
||||
})
|
||||
}
|
||||
export function numberFormat(num: number) {
|
||||
return num < 1000 ? toThousands(num) : toThousands((num / 1000).toFixed(1)) + 'k'
|
||||
}
|
||||
|
||||
export function filesize(size: number) {
|
||||
if (!size) return ''
|
||||
/* byte */
|
||||
const num = 1024.0
|
||||
|
||||
if (size < num) return size + 'B'
|
||||
if (size < Math.pow(num, 2)) return (size / num).toFixed(2) + 'K' //kb
|
||||
if (size < Math.pow(num, 3)) return (size / Math.pow(num, 2)).toFixed(2) + 'M' //M
|
||||
if (size < Math.pow(num, 4)) return (size / Math.pow(num, 3)).toFixed(2) + 'G' //G
|
||||
return (size / Math.pow(num, 4)).toFixed(2) + 'T' //T
|
||||
}
|
||||
|
||||
/*
|
||||
随机id
|
||||
*/
|
||||
export const randomId = function () {
|
||||
return Math.floor(Math.random() * 10000) + ''
|
||||
}
|
||||
|
||||
/*
|
||||
获取文件后缀
|
||||
*/
|
||||
export function fileType(name: string) {
|
||||
const suffix = name.split('.')
|
||||
return suffix[suffix.length - 1]
|
||||
}
|
||||
|
||||
/*
|
||||
获得文件对应图片
|
||||
*/
|
||||
const typeList: any = {
|
||||
txt: ['txt', 'pdf', 'docx', 'md', 'html', 'zip', 'xlsx', 'xls', 'csv'],
|
||||
table: ['xlsx', 'xls', 'csv'],
|
||||
QA: ['xlsx', 'csv', 'xls', 'zip']
|
||||
}
|
||||
|
||||
export function getImgUrl(name: string) {
|
||||
const list = Object.values(typeList).flat()
|
||||
|
||||
const type = list.includes(fileType(name).toLowerCase()) ? fileType(name).toLowerCase() : 'unknown'
|
||||
return new URL(`../assets/fileType/${type}-icon.svg`, import.meta.url).href
|
||||
}
|
||||
// 是否是白名单后缀
|
||||
export function isRightType(name: string, type: string) {
|
||||
return typeList[type].includes(fileType(name).toLowerCase())
|
||||
}
|
||||
|
||||
/*
|
||||
从指定数组中过滤出对应的对象
|
||||
*/
|
||||
export function relatedObject(list: any, val: any, attr: string) {
|
||||
const filterData: any = list.filter((item: any) => item[attr] === val)?.[0]
|
||||
return filterData || null
|
||||
}
|
||||
|
||||
// 排序
|
||||
export function arraySort(list: Array<any>, property: any, desc?: boolean) {
|
||||
return list.sort((a: any, b: any) => {
|
||||
return desc ? b[property] - a[property] : a[property] - b[property]
|
||||
})
|
||||
}
|
||||
|
||||
// 判断对象里所有属性全部为空
|
||||
export function isAllPropertiesEmpty(obj: object) {
|
||||
return Object.values(obj).every(
|
||||
(value) =>
|
||||
value === null || typeof value === 'undefined' || (typeof value === 'string' && !value)
|
||||
)
|
||||
}
|
||||
|
||||
// 数组对象中某一属性值的集合
|
||||
export function getAttrsArray(array: Array<any>, attr: string) {
|
||||
return array.map((item) => {
|
||||
return item[attr]
|
||||
})
|
||||
}
|
||||
|
||||
// 求和
|
||||
export function getSum(array: Array<any>) {
|
||||
return array.reduce((total, item) => total + item, 0)
|
||||
}
|
||||
|
||||
// 下载
|
||||
export function downloadByURL(url: string, name: string) {
|
||||
const a = document.createElement('a')
|
||||
a.setAttribute('href', url)
|
||||
a.setAttribute('target', '_blank')
|
||||
a.setAttribute('download', name)
|
||||
document.body.appendChild(a)
|
||||
a.click()
|
||||
document.body.removeChild(a)
|
||||
}
|
||||
|
||||
// 截取文件名
|
||||
export function cutFilename(filename: string, num: number) {
|
||||
const lastIndex = filename.lastIndexOf('.')
|
||||
const suffix = lastIndex === -1 ? '' : filename.substring(lastIndex + 1)
|
||||
return filename.substring(0, num - suffix.length - 1) + '.' + suffix
|
||||
}
|
||||
|
||||
export function getNormalizedUrl(url: string) {
|
||||
if (url && !url.endsWith('/') && !/\.[^/]+$/.test(url)) {
|
||||
return url + '/'
|
||||
}
|
||||
return url
|
||||
}
|
||||
|
||||
interface LoadScriptOptions {
|
||||
jsId?: string // 自定义脚本 ID
|
||||
forceReload?: boolean // 是否强制重新加载(默认 false)
|
||||
}
|
||||
|
||||
export const loadScript = (url: string, options: LoadScriptOptions = {}): Promise<void> => {
|
||||
const { jsId, forceReload = false } = options
|
||||
const scriptId = jsId || `script-${btoa(url).slice(0, 12)}` // 生成唯一 ID
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
// 检查是否已存在且无需强制加载
|
||||
const existingScript = document.getElementById(scriptId) as HTMLScriptElement | null
|
||||
if (existingScript && !forceReload) {
|
||||
if (existingScript.src === url) {
|
||||
existingScript.onload = () => resolve() // 复用现有脚本
|
||||
return
|
||||
}
|
||||
// URL 不同则移除旧脚本
|
||||
existingScript.parentElement?.removeChild(existingScript)
|
||||
}
|
||||
|
||||
// 创建新脚本
|
||||
const script = document.createElement('script')
|
||||
script.id = scriptId
|
||||
script.src = url
|
||||
script.async = true // 明确启用异步加载
|
||||
|
||||
// 成功回调
|
||||
script.onload = () => {
|
||||
resolve()
|
||||
}
|
||||
|
||||
// 错误处理(兼容性增强)
|
||||
script.onerror = () => {
|
||||
reject(new Error(`Failed to load script: ${url}`))
|
||||
cleanupScript(script)
|
||||
}
|
||||
|
||||
// 插入到 <head> 确保加载顺序
|
||||
document.head.appendChild(script)
|
||||
})
|
||||
}
|
||||
|
||||
// 清理脚本(可选)
|
||||
const cleanupScript = (script: HTMLScriptElement) => {
|
||||
script.onload = null
|
||||
script.onerror = null
|
||||
script.parentElement?.removeChild(script)
|
||||
}
|
||||
|
|
@ -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/systemSettings/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>
|
||||
|
|
@ -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/systemSettings/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
|
||||
let 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>
|
||||
|
|
@ -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/systemSettings/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>
|
||||
|
|
@ -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/systemSettings/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>
|
||||
|
|
@ -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/systemSettings/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>
|
||||
|
|
@ -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/systemSettings/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>
|
||||
|
|
@ -0,0 +1,76 @@
|
|||
<template>
|
||||
<div class="authentication-setting p-16-24">
|
||||
<h4>{{ $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>
|
||||
|
|
@ -0,0 +1,146 @@
|
|||
<template>
|
||||
<div class="email-setting">
|
||||
<h4 class="p-16-24">{{ $t('views.system.email.title') }}</h4>
|
||||
<el-scrollbar>
|
||||
<div class="p-24" v-loading="loading">
|
||||
<el-form
|
||||
ref="emailFormRef"
|
||||
:rules="rules"
|
||||
:model="form"
|
||||
label-position="top"
|
||||
require-asterisk-position="right"
|
||||
>
|
||||
<el-form-item :label="$t('views.system.email.smtpHost')" prop="email_host">
|
||||
<el-input
|
||||
v-model="form.email_host"
|
||||
:placeholder="$t('views.system.email.smtpHostPlaceholder')"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('views.system.email.smtpPort')" prop="email_port">
|
||||
<el-input
|
||||
v-model="form.email_port"
|
||||
:placeholder="$t('views.system.email.smtpPortPlaceholder')"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('views.system.email.smtpUser')" prop="email_host_user">
|
||||
<el-input
|
||||
v-model="form.email_host_user"
|
||||
:placeholder="$t('views.system.email.smtpUserPlaceholder')"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('views.system.email.sendEmail')" prop="from_email">
|
||||
<el-input
|
||||
v-model="form.from_email"
|
||||
:placeholder="$t('views.system.email.sendEmailPlaceholder')"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('views.system.password')" prop="email_host_password">
|
||||
<el-input
|
||||
v-model="form.email_host_password"
|
||||
:placeholder="$t('views.system.email.smtpPasswordPlaceholder')"
|
||||
show-password
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-checkbox v-model="form.email_use_ssl"
|
||||
>{{ $t('views.system.email.enableSSL') }}
|
||||
</el-checkbox>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-checkbox v-model="form.email_use_tls"
|
||||
>{{ $t('views.system.email.enableTLS') }}
|
||||
</el-checkbox>
|
||||
</el-form-item>
|
||||
<el-button @click="submit(emailFormRef, 'test')" :disabled="loading">
|
||||
{{ $t('views.system.test') }}
|
||||
</el-button>
|
||||
</el-form>
|
||||
|
||||
<div class="text-right">
|
||||
<el-button @click="submit(emailFormRef)" 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 emailApi from '@/api/systemSettings/email-setting'
|
||||
import type {FormInstance, FormRules} from 'element-plus'
|
||||
|
||||
import {MsgSuccess} from '@/utils/message'
|
||||
import {t} from '@/locales'
|
||||
|
||||
const form = ref<any>({
|
||||
email_host: '',
|
||||
email_port: '',
|
||||
email_host_user: '',
|
||||
email_host_password: '',
|
||||
email_use_tls: false,
|
||||
email_use_ssl: false,
|
||||
from_email: ''
|
||||
})
|
||||
|
||||
const emailFormRef = ref()
|
||||
|
||||
const loading = ref(false)
|
||||
|
||||
const rules = reactive<FormRules<any>>({
|
||||
email_host: [
|
||||
{required: true, message: t('views.system.email.smtpHostPlaceholder'), trigger: 'blur'}
|
||||
],
|
||||
email_port: [
|
||||
{required: true, message: t('views.system.email.smtpPortPlaceholder'), trigger: 'blur'}
|
||||
],
|
||||
email_host_user: [
|
||||
{required: true, message: t('views.system.email.smtpUserPlaceholder'), trigger: 'blur'}
|
||||
],
|
||||
email_host_password: [
|
||||
{required: true, message: t('views.system.email.smtpPasswordPlaceholder'), trigger: 'blur'}
|
||||
],
|
||||
from_email: [
|
||||
{required: true, message: t('views.system.email.sendEmailPlaceholder'), trigger: 'blur'}
|
||||
]
|
||||
})
|
||||
|
||||
const submit = async (formEl: FormInstance | undefined, test?: string) => {
|
||||
if (!formEl) return
|
||||
await formEl.validate((valid, fields) => {
|
||||
if (valid) {
|
||||
if (test) {
|
||||
emailApi.postTestEmail(form.value, loading).then((res) => {
|
||||
MsgSuccess(t('views.system.testSuccess'))
|
||||
})
|
||||
} else {
|
||||
emailApi.putEmailSetting(form.value, loading).then((res) => {
|
||||
MsgSuccess(t('common.saveSuccess'))
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function getDetail() {
|
||||
emailApi.getEmailSetting(loading).then((res: any) => {
|
||||
if (res.data && JSON.stringify(res.data) !== '{}') {
|
||||
form.value = res.data
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
getDetail()
|
||||
})
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.email-setting {
|
||||
width: 70%;
|
||||
margin: 0 auto;
|
||||
|
||||
:deep(.el-checkbox__label) {
|
||||
font-weight: 400;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
@ -1,8 +1,8 @@
|
|||
<template>
|
||||
<login-layout v-if="!loading" v-loading="loading">
|
||||
<LoginContainer :subTitle="theme.themeInfo?.slogan || $t('theme.defaultSlogan')">
|
||||
<h2 class="mb-24">{{ $t('views.login.title') }}</h2>
|
||||
<div>
|
||||
<h2 class="mb-24" v-if="!showQrCodeTab">{{ loginMode || $t('views.login.title') }}</h2>
|
||||
<div v-if="!showQrCodeTab">
|
||||
<el-form
|
||||
class="login-form"
|
||||
:rules="rules"
|
||||
|
|
@ -34,7 +34,7 @@
|
|||
</el-input>
|
||||
</el-form-item>
|
||||
</div>
|
||||
<div class="mb-24">
|
||||
<div class="mb-24" v-if="loginMode !== 'LDAP'">
|
||||
<el-form-item prop="captcha">
|
||||
<div class="flex-between w-full">
|
||||
<el-input
|
||||
|
|
@ -45,7 +45,8 @@
|
|||
>
|
||||
</el-input>
|
||||
|
||||
<img :src="identifyCode" alt="" height="38" class="ml-8 cursor border border-r-4" @click="makeCode" />
|
||||
<img :src="identifyCode" alt="" height="38" class="ml-8 cursor border border-r-4"
|
||||
@click="makeCode"/>
|
||||
</div>
|
||||
</el-form-item>
|
||||
</div>
|
||||
|
|
@ -72,24 +73,71 @@
|
|||
</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">
|
||||
<span>{{ $t('views.login.moreMethod') }}</span>
|
||||
</div>
|
||||
<div class="text-center mt-16">
|
||||
<template v-for="item in modeList">
|
||||
<el-button
|
||||
v-if="item !== '' && loginMode !== item && item !== 'QR_CODE'"
|
||||
circle
|
||||
:key="item"
|
||||
class="login-button-circle color-secondary"
|
||||
@click="changeMode(item)"
|
||||
>
|
||||
<span
|
||||
:style="{
|
||||
'font-size': item === 'OAUTH2' ? '8px' : '10px',
|
||||
color: user.themeInfo?.theme
|
||||
}"
|
||||
>{{ item }}</span
|
||||
>
|
||||
</el-button>
|
||||
<el-button
|
||||
v-if="item === 'QR_CODE' && loginMode !== item"
|
||||
circle
|
||||
:key="item"
|
||||
class="login-button-circle color-secondary"
|
||||
@click="changeMode('QR_CODE')"
|
||||
>
|
||||
<img src="@/assets/scan/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>
|
||||
</LoginContainer>
|
||||
</login-layout>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { onMounted, ref, onBeforeMount } from 'vue'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
import type { FormInstance, FormRules } from 'element-plus'
|
||||
import type { LoginRequest } from '@/api/type/login'
|
||||
import {onMounted, ref, onBeforeMount} from 'vue'
|
||||
import {useRoute, useRouter} from 'vue-router'
|
||||
import type {FormInstance, FormRules} from 'element-plus'
|
||||
import type {LoginRequest} from '@/api/type/login'
|
||||
import LoginContainer from '@/views/login/components/LoginContainer.vue'
|
||||
import LoginLayout from '@/views/login/components/LoginLayout.vue'
|
||||
import loginApi from '@/api/user/login'
|
||||
import { t, getBrowserLang } from '@/locales'
|
||||
import authApi from '@/api/systemSettings/auth-setting'
|
||||
import {t, getBrowserLang} from '@/locales'
|
||||
import useStore from '@/stores'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
import {useI18n} from 'vue-i18n'
|
||||
import QrCodeTab from "@/views/login/scanCompinents/QrCodeTab.vue";
|
||||
import {MsgConfirm, MsgError} from "@/utils/message.ts";
|
||||
import * as dd from 'dingtalk-jsapi'
|
||||
import { loadScript } from '@/utils/utils'
|
||||
const router = useRouter()
|
||||
const { login, user, theme } = useStore()
|
||||
const { locale } = useI18n({ useScope: 'global' })
|
||||
const {login, user, theme} = useStore()
|
||||
const {locale} = useI18n({useScope: 'global'})
|
||||
const loading = ref<boolean>(false)
|
||||
|
||||
const identifyCode = ref<string>('')
|
||||
|
|
@ -127,21 +175,271 @@ const rules = ref<FormRules<LoginRequest>>({
|
|||
|
||||
const loginHandle = () => {
|
||||
loginFormRef.value?.validate().then(() => {
|
||||
login.asyncLogin(loginForm.value, loading).then(() => {
|
||||
locale.value = localStorage.getItem('MaxKB-locale') || getBrowserLang() || 'en-US'
|
||||
router.push({ name: 'home' })
|
||||
})
|
||||
if (loginMode.value === 'LDAP') {
|
||||
login.asyncLdapLogin(loginForm.value, loading).then(() => {
|
||||
locale.value = localStorage.getItem('MaxKB-locale') || getBrowserLang() || 'en-US'
|
||||
router.push({name: 'home'})
|
||||
})
|
||||
} else {
|
||||
login.asyncLogin(loginForm.value, loading).then(() => {
|
||||
locale.value = localStorage.getItem('MaxKB-locale') || getBrowserLang() || 'en-US'
|
||||
router.push({name: 'home'})
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function makeCode() {
|
||||
loginApi.getCaptcha().then((res: any) => {
|
||||
identifyCode.value = res.data.captcha
|
||||
})
|
||||
}
|
||||
|
||||
onBeforeMount(() => {
|
||||
makeCode()
|
||||
})
|
||||
|
||||
onMounted(() => {})
|
||||
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 uuidv4() {
|
||||
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
|
||||
const r = (Math.random() * 16) | 0
|
||||
const v = c === 'x' ? r : (r & 0x3) | 0x8
|
||||
return v.toString(16)
|
||||
})
|
||||
}
|
||||
|
||||
function redirectAuth(authType: string) {
|
||||
if (authType === 'LDAP' || authType === '') {
|
||||
return
|
||||
}
|
||||
authApi.getAuthSetting(authType, loading).then((res: any) => {
|
||||
if (!res.data) {
|
||||
return
|
||||
}
|
||||
MsgConfirm(t('views.login.jump_tip'), '', {
|
||||
confirmButtonText: t('views.login.jump'),
|
||||
cancelButtonText: t('common.cancel'),
|
||||
confirmButtonClass: ''
|
||||
})
|
||||
.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
|
||||
if (url.indexOf('?') !== -1) {
|
||||
url = `${config.ldpUri}&service=${encodeURIComponent(redirectUrl)}`
|
||||
} else {
|
||||
url = `${config.ldpUri}?service=${encodeURIComponent(redirectUrl)}`
|
||||
}
|
||||
}
|
||||
if (authType === 'OIDC') {
|
||||
const scope = config.scope || 'openid+profile+email'
|
||||
url = `${config.authEndpoint}?client_id=${config.clientId}&redirect_uri=${redirectUrl}&response_type=code&scope=${scope}`
|
||||
if (config.state) {
|
||||
url += `&state=${config.state}`
|
||||
}
|
||||
}
|
||||
if (authType === 'OAuth2') {
|
||||
url =
|
||||
`${config.authEndpoint}?client_id=${config.clientId}&response_type=code` +
|
||||
`&redirect_uri=${redirectUrl}&state=${uuidv4()}`
|
||||
if (config.scope) {
|
||||
url += `&scope=${config.scope}`
|
||||
}
|
||||
}
|
||||
if (url) {
|
||||
window.location.href = url
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
function changeMode(val: string) {
|
||||
loginMode.value = val === 'LDAP' ? val : ''
|
||||
if (val === 'QR_CODE') {
|
||||
loginMode.value = val
|
||||
showQrCodeTab.value = true
|
||||
return
|
||||
}
|
||||
showQrCodeTab.value = false
|
||||
loginForm.value = {
|
||||
username: '',
|
||||
password: '',
|
||||
captcha: ''
|
||||
}
|
||||
redirectAuth(val)
|
||||
loginFormRef.value?.clearValidate()
|
||||
}
|
||||
|
||||
onBeforeMount(() => {
|
||||
loading.value = true
|
||||
user.asyncGetProfile().then((res) => {
|
||||
if (user.isEnterprise()) {
|
||||
user
|
||||
.getAuthType()
|
||||
.then((res) => {
|
||||
//如果结果包含LDAP,把LDAP放在第一个
|
||||
const ldapIndex = res.indexOf('LDAP')
|
||||
if (ldapIndex !== -1) {
|
||||
const [ldap] = res.splice(ldapIndex, 1)
|
||||
res.unshift(ldap)
|
||||
}
|
||||
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'
|
||||
? t('views.system.authentication.scanTheQRCode.wecom')
|
||||
: item === 'dingtalk'
|
||||
? t('views.system.authentication.scanTheQRCode.dingtalk')
|
||||
: t('views.system.authentication.scanTheQRCode.lark')
|
||||
})
|
||||
})
|
||||
}
|
||||
})
|
||||
.finally(() => (loading.value = false))
|
||||
} else {
|
||||
loading.value = false
|
||||
}
|
||||
})
|
||||
})
|
||||
declare const window: any
|
||||
|
||||
onMounted(() => {
|
||||
makeCode()
|
||||
const route = useRoute()
|
||||
const currentUrl = ref(route.fullPath)
|
||||
const params = new URLSearchParams(currentUrl.value.split('?')[1])
|
||||
const client = params.get('client')
|
||||
|
||||
const handleDingTalk = () => {
|
||||
const code = params.get('corpId')
|
||||
if (code) {
|
||||
dd.runtime.permission.requestAuthCode({ corpId: code }).then((res) => {
|
||||
console.log('DingTalk client request success:', res)
|
||||
user.dingOauth2Callback(res.code).then(() => {
|
||||
router.push({ name: 'home' })
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const handleLark = () => {
|
||||
const appId = params.get('appId')
|
||||
const callRequestAuthCode = () => {
|
||||
window.tt?.requestAuthCode({
|
||||
appId: appId,
|
||||
success: (res: any) => {
|
||||
user.larkCallback(res.code).then(() => {
|
||||
router.push({ name: 'home' })
|
||||
})
|
||||
},
|
||||
fail: (error: any) => {
|
||||
MsgError(error)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
loadScript('https://lf-scm-cn.feishucdn.com/lark/op/h5-js-sdk-1.5.35.js', {
|
||||
jsId: 'lark-sdk',
|
||||
forceReload: true
|
||||
})
|
||||
.then(() => {
|
||||
if (window.tt) {
|
||||
window.tt.requestAccess({
|
||||
appID: appId,
|
||||
scopeList: [],
|
||||
success: (res: any) => {
|
||||
user.larkCallback(res.code).then(() => {
|
||||
router.push({ name: 'home' })
|
||||
})
|
||||
},
|
||||
fail: (error: any) => {
|
||||
const { errno } = error
|
||||
if (errno === 103) {
|
||||
callRequestAuthCode()
|
||||
}
|
||||
}
|
||||
})
|
||||
} else {
|
||||
callRequestAuthCode()
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error('SDK 加载失败:', error)
|
||||
})
|
||||
}
|
||||
|
||||
switch (client) {
|
||||
case 'dingtalk':
|
||||
handleDingTalk()
|
||||
break
|
||||
case 'lark':
|
||||
handleLark()
|
||||
break
|
||||
default:
|
||||
break
|
||||
}
|
||||
})
|
||||
</script>
|
||||
<style lang="scss" scoped></style>
|
||||
<style lang="scss" scoped>
|
||||
.login-gradient-divider {
|
||||
position: relative;
|
||||
text-align: center;
|
||||
color: var(--el-color-info);
|
||||
|
||||
::before {
|
||||
content: '';
|
||||
width: 25%;
|
||||
height: 1px;
|
||||
background: linear-gradient(90deg, rgba(222, 224, 227, 0) 0%, #dee0e3 100%);
|
||||
position: absolute;
|
||||
left: 16px;
|
||||
top: 50%;
|
||||
}
|
||||
|
||||
::after {
|
||||
content: '';
|
||||
width: 25%;
|
||||
height: 1px;
|
||||
background: linear-gradient(90deg, #dee0e3 0%, rgba(222, 224, 227, 0) 100%);
|
||||
position: absolute;
|
||||
right: 16px;
|
||||
top: 50%;
|
||||
}
|
||||
}
|
||||
|
||||
.login-button-circle {
|
||||
padding: 20px !important;
|
||||
margin: 0 4px;
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
text-align: center;
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,74 @@
|
|||
<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 { onMounted, ref, defineAsyncComponent } from 'vue'
|
||||
import useStore from '@/stores'
|
||||
|
||||
interface Tab {
|
||||
key: string
|
||||
value: string
|
||||
}
|
||||
|
||||
interface PlatformConfig {
|
||||
app_key: string
|
||||
app_secret: string
|
||||
auth_type: 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('')
|
||||
const { user } = useStore()
|
||||
async function getPlatformInfo() {
|
||||
try {
|
||||
return await user.getQrSource()
|
||||
} 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.auth_type === 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,141 @@
|
|||
<template>
|
||||
<div class="flex-center mb-16">
|
||||
<img src="@/assets/scan/logo_dingtalk.svg" alt="" width="24px" class="mr-4" />
|
||||
<h2>{{ $t('views.system.authentication.scanTheQRCode.dingtalkQrCode') }}</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 { ref, watch } from 'vue'
|
||||
import useStore from '@/stores'
|
||||
import { MsgError } from '@/utils/message'
|
||||
// 声明 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
|
||||
corp_id: 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,
|
||||
corp_id: props.config.corp_id
|
||||
}
|
||||
|
||||
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 corpid',
|
||||
response_type: 'code',
|
||||
state: 'fit2cloud-ding-qr',
|
||||
prompt: 'consent',
|
||||
corpId: data.corp_id
|
||||
},
|
||||
(loginResult) => {
|
||||
const authCode = loginResult.authCode
|
||||
user.dingCallback(authCode).then(() => {
|
||||
router.push({ name: 'home' })
|
||||
})
|
||||
},
|
||||
(errorMsg: string) => {
|
||||
MsgError(errorMsg)
|
||||
}
|
||||
)
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
}
|
||||
}
|
||||
|
||||
watch(
|
||||
() => props.config,
|
||||
(newConfig) => {
|
||||
if (newConfig.app_key && newConfig.corp_id) {
|
||||
isConfigReady.value = true
|
||||
initActive()
|
||||
}
|
||||
},
|
||||
{ immediate: true }
|
||||
)
|
||||
</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/scan/logo_lark.svg " alt="" width="24px" class="mr-4" />
|
||||
<h2>{{ $t('views.system.authentication.scanTheQRCode.larkQrCode') }}</h2>
|
||||
</div>
|
||||
<div id="lark-qr" class="lark-qrName"></div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { useScriptTag } from '@vueuse/core'
|
||||
import { 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,79 @@
|
|||
<template>
|
||||
<div id="wecom-qr" class="wecom-qr" style="margin-left: 50px"></div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { useRouter } from 'vue-router'
|
||||
import * as ww from '@wecom/jssdk'
|
||||
import {
|
||||
WWLoginLangType,
|
||||
WWLoginPanelSizeType,
|
||||
WWLoginRedirectType,
|
||||
WWLoginType
|
||||
} from '@wecom/jssdk'
|
||||
import { ref, nextTick, defineProps } from 'vue'
|
||||
import { MsgError } from '@/utils/message'
|
||||
import useStore from '@/stores'
|
||||
import { getBrowserLang } from '@/locales'
|
||||
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 lang = localStorage.getItem('MaxKB-locale') || getBrowserLang() || 'en-US'
|
||||
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',
|
||||
lang: lang === 'zh-CN' || lang === 'zh-Hant' ? WWLoginLangType.zh : WWLoginLangType.en,
|
||||
redirect_type: WWLoginRedirectType.callback,
|
||||
panel_size: WWLoginPanelSizeType.small
|
||||
},
|
||||
onCheckWeComLogin: obj.value,
|
||||
async onLoginSuccess({ code }: any) {
|
||||
user.wecomCallback(code).then(() => {
|
||||
setTimeout(() => {
|
||||
router.push({ name: 'home' })
|
||||
})
|
||||
})
|
||||
},
|
||||
onLoginFail(err) {
|
||||
MsgError(`${err.errMsg}`)
|
||||
}
|
||||
})
|
||||
} catch (error) {
|
||||
console.error('Error initializing login panel:', error)
|
||||
}
|
||||
}
|
||||
|
||||
init()
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.wecom-qr {
|
||||
margin-top: -20px;
|
||||
height: 331px;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,115 @@
|
|||
<template>
|
||||
<div class="login-preview mr-16">
|
||||
<div class="header">
|
||||
<div class="tag flex-between">
|
||||
<div class="flex align-center">
|
||||
<img v-if="props.data.icon" :src="fileURL" alt="" height="20px" class="mr-8" />
|
||||
<img v-else src="@/assets/logo/logo.svg" height="24px" class="mr-8" />
|
||||
<span class="ellipsis">{{ data.title }}</span>
|
||||
</div>
|
||||
<el-icon><Close /></el-icon>
|
||||
</div>
|
||||
</div>
|
||||
<login-layout style="height: 530px" :lang="false">
|
||||
<LoginContainer :subTitle="data.slogan" class="login-container">
|
||||
<div class="mask"></div>
|
||||
<h2 class="mb-24">{{ $t('views.login.title') }}</h2>
|
||||
<el-form class="login-form">
|
||||
<div class="mb-24">
|
||||
<el-form-item>
|
||||
<el-input
|
||||
size="large"
|
||||
class="input-item"
|
||||
:placeholder="$t('views.userManage.form.username.placeholder')"
|
||||
>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
</div>
|
||||
<div class="mb-24">
|
||||
<el-form-item>
|
||||
<el-input
|
||||
type="password"
|
||||
size="large"
|
||||
class="input-item"
|
||||
:placeholder="$t('views.userManage.form.password.placeholder')"
|
||||
show-password
|
||||
>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
</div>
|
||||
</el-form>
|
||||
<el-button size="large" type="primary" class="w-full">{{
|
||||
$t('views.login.buttons.login')
|
||||
}}</el-button>
|
||||
<div class="operate-container flex-between mt-12">
|
||||
<el-button class="forgot-password" link type="primary">
|
||||
{{ $t('views.login.forgotPassword') }}?
|
||||
</el-button>
|
||||
</div>
|
||||
</LoginContainer>
|
||||
</login-layout>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed } from 'vue'
|
||||
import LoginLayout from "@/views/login/components/LoginLayout.vue";
|
||||
import LoginContainer from "@/views/login/components/LoginContainer.vue";
|
||||
|
||||
const props = defineProps({
|
||||
data: {
|
||||
type: Object,
|
||||
default: null
|
||||
}
|
||||
})
|
||||
|
||||
const fileURL = computed(() => {
|
||||
if (props.data.icon) {
|
||||
if (typeof props.data.icon === 'string') {
|
||||
return props.data.icon
|
||||
} else {
|
||||
return URL.createObjectURL(props.data.icon)
|
||||
}
|
||||
} else {
|
||||
return ''
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.login-preview {
|
||||
background: #ffffff;
|
||||
border-radius: 4px;
|
||||
transform-origin: center;
|
||||
.login-container {
|
||||
transform: translate(0, 0) scale(0.8);
|
||||
position: relative;
|
||||
.mask {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
z-index: 3;
|
||||
}
|
||||
}
|
||||
|
||||
.header {
|
||||
background: var(--el-disabled-bg-color);
|
||||
height: 38px;
|
||||
border-radius: 4px 4px 0 0;
|
||||
position: relative;
|
||||
.tag {
|
||||
width: 180px;
|
||||
height: 30px;
|
||||
background: #ffffff;
|
||||
box-shadow: var(-app-text-color-light-1);
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 8px;
|
||||
border-radius: 4px 4px 0 0;
|
||||
padding: 0 8px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,420 @@
|
|||
<template>
|
||||
<div class="theme-setting" v-loading="loading">
|
||||
<h4 class="p-16-24">{{ $t('views.system.theme.title') }}</h4>
|
||||
<el-scrollbar>
|
||||
<div class="p-24 pt-0">
|
||||
<div class="app-card p-24">
|
||||
<h5 class="mb-16">{{ $t('views.system.theme.platformDisplayTheme') }}</h5>
|
||||
<el-radio-group
|
||||
v-model="themeRadio"
|
||||
class="app-radio-button-group"
|
||||
@change="changeThemeHandle"
|
||||
>
|
||||
<template v-for="(item, index) in themeList" :key="index">
|
||||
<el-radio-button :label="item.label" :value="item.value"/>
|
||||
</template>
|
||||
<el-radio-button :label="$t('views.system.theme.custom')" value="custom"/>
|
||||
</el-radio-group>
|
||||
<div v-if="themeRadio === 'custom'">
|
||||
<h5 class="mt-16 mb-8">{{ $t('views.system.theme.customTheme') }}</h5>
|
||||
<el-color-picker v-model="customColor" @change="customColorHandle"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="app-card p-24 mt-16">
|
||||
<h5 class="mb-16">{{ $t('views.system.theme.platformLoginSettings') }}</h5>
|
||||
<el-card shadow="never" class="layout-bg">
|
||||
<div class="flex-between">
|
||||
<h5 class="mb-16">{{ $t('views.system.theme.pagePreview') }}</h5>
|
||||
<el-button type="primary" link @click="resetForm('login')">
|
||||
{{ $t('views.system.theme.restoreDefaults') }}
|
||||
</el-button>
|
||||
</div>
|
||||
<el-scrollbar>
|
||||
<div class="theme-preview">
|
||||
<el-row :gutter="8">
|
||||
<el-col :span="16">
|
||||
<LoginPreview :data="themeForm"/>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<div class="theme-form">
|
||||
<el-card shadow="never" class="mb-8">
|
||||
<div class="flex-between mb-8">
|
||||
<span class="lighter">{{ $t('views.system.theme.websiteLogo') }}</span>
|
||||
<el-upload
|
||||
ref="uploadRef"
|
||||
action="#"
|
||||
:auto-upload="false"
|
||||
:show-file-list="false"
|
||||
accept="image/jpeg, image/png, image/gif"
|
||||
:on-change="
|
||||
(file: any, fileList: any) => onChange(file, fileList, 'icon')
|
||||
"
|
||||
>
|
||||
<el-button size="small">
|
||||
{{ $t('views.system.theme.replacePicture') }}
|
||||
</el-button>
|
||||
</el-upload>
|
||||
</div>
|
||||
<el-text type="info" size="small"
|
||||
>{{ $t('views.system.theme.websiteLogoTip') }}
|
||||
</el-text>
|
||||
</el-card>
|
||||
<el-card shadow="never" class="mb-8">
|
||||
<div class="flex-between mb-8">
|
||||
<span class="lighter"> {{ $t('views.system.theme.loginLogo') }}</span>
|
||||
<el-upload
|
||||
ref="uploadRef"
|
||||
action="#"
|
||||
:auto-upload="false"
|
||||
:show-file-list="false"
|
||||
accept="image/jpeg, image/png, image/gif"
|
||||
:on-change="
|
||||
(file: any, fileList: any) => onChange(file, fileList, 'loginLogo')
|
||||
"
|
||||
>
|
||||
<el-button size="small">
|
||||
{{ $t('views.system.theme.replacePicture') }}
|
||||
</el-button>
|
||||
</el-upload>
|
||||
</div>
|
||||
<el-text type="info" size="small"
|
||||
>{{ $t('views.system.theme.loginLogoTip') }}
|
||||
</el-text>
|
||||
</el-card>
|
||||
<el-card shadow="never" class="mb-8">
|
||||
<div class="flex-between mb-8">
|
||||
<span class="lighter">{{
|
||||
$t('views.system.theme.loginBackground')
|
||||
}}</span>
|
||||
<el-upload
|
||||
ref="uploadRef"
|
||||
action="#"
|
||||
:auto-upload="false"
|
||||
:show-file-list="false"
|
||||
accept="image/jpeg, image/png, image/gif"
|
||||
:on-change="
|
||||
(file: any, fileList: any) => onChange(file, fileList, 'loginImage')
|
||||
"
|
||||
>
|
||||
<el-button size="small">
|
||||
{{ $t('views.system.theme.replacePicture') }}
|
||||
</el-button>
|
||||
</el-upload>
|
||||
</div>
|
||||
<el-text type="info" size="small">
|
||||
{{ $t('views.system.theme.loginBackgroundTip') }}
|
||||
</el-text>
|
||||
</el-card>
|
||||
|
||||
<el-form
|
||||
ref="themeFormRef"
|
||||
:model="themeForm"
|
||||
label-position="top"
|
||||
require-asterisk-position="right"
|
||||
:rules="rules"
|
||||
@submit.prevent
|
||||
>
|
||||
<el-form-item :label="$t('views.system.theme.websiteName')" prop="title">
|
||||
<el-input
|
||||
v-model="themeForm.title"
|
||||
:placeholder="$t('views.system.theme.websiteNamePlaceholder')"
|
||||
>
|
||||
</el-input>
|
||||
<el-text type="info">{{
|
||||
$t('views.system.theme.websiteNameTip')
|
||||
}}
|
||||
</el-text>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('views.system.theme.websiteSlogan')" prop="slogan">
|
||||
<el-input
|
||||
v-model="themeForm.slogan"
|
||||
:placeholder="$t('views.system.theme.websiteSloganPlaceholder')"
|
||||
maxlength="64"
|
||||
show-word-limit
|
||||
>
|
||||
</el-input>
|
||||
<el-text type="info">{{
|
||||
$t('views.system.theme.websiteSloganTip')
|
||||
}}
|
||||
</el-text>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
</el-scrollbar>
|
||||
<div class="mt-16">
|
||||
<el-text type="info">{{ $t('views.system.theme.logoDefaultTip') }}</el-text>
|
||||
</div>
|
||||
</el-card>
|
||||
</div>
|
||||
<div class="app-card p-24 mt-16">
|
||||
<h5 class="mb-16">{{ $t('views.system.theme.platformSetting') }}</h5>
|
||||
<el-card shadow="never" class="layout-bg">
|
||||
<div class="flex-between">
|
||||
<h5 class="mb-16">{{ $t('views.system.theme.pagePreview') }}</h5>
|
||||
<el-button type="primary" link @click="resetForm('platform')">
|
||||
{{ $t('views.system.theme.restoreDefaults') }}
|
||||
</el-button>
|
||||
</div>
|
||||
<el-scrollbar>
|
||||
<div class="theme-preview">
|
||||
<el-row :gutter="8">
|
||||
<el-col :span="16">
|
||||
<div class="theme-platform mr-16">
|
||||
<div
|
||||
class="theme-platform-header border-b flex-between"
|
||||
:class="!isDefaultTheme ? 'custom-header' : ''"
|
||||
>
|
||||
<div class="flex-center h-full">
|
||||
<div class="app-title-container cursor">
|
||||
<div class="logo flex-center">
|
||||
<LogoFull height="25px"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex-center">
|
||||
<AppIcon
|
||||
iconName="app-github"
|
||||
class="cursor color-secondary mr-8 ml-8"
|
||||
style="font-size: 20px"
|
||||
v-if="themeForm.showProject"
|
||||
></AppIcon>
|
||||
<AppIcon
|
||||
iconName="app-user-manual"
|
||||
class="cursor color-secondary mr-8 ml-8"
|
||||
style="font-size: 20px"
|
||||
v-if="themeForm.showUserManual"
|
||||
></AppIcon>
|
||||
<AppIcon
|
||||
iconName="app-help"
|
||||
class="cursor color-secondary ml-8"
|
||||
style="font-size: 20px"
|
||||
v-if="themeForm.showForum"
|
||||
></AppIcon>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<div class="theme-form">
|
||||
<div>
|
||||
<el-checkbox
|
||||
v-model="themeForm.showUserManual"
|
||||
:label="$t('views.system.theme.showUserManual')"
|
||||
/>
|
||||
<div class="ml-24">
|
||||
<el-input
|
||||
v-model="themeForm.userManualUrl"
|
||||
:placeholder="$t('views.system.theme.urlPlaceholder')"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-4">
|
||||
<el-checkbox
|
||||
v-model="themeForm.showForum"
|
||||
:label="$t('views.system.theme.showForum')"
|
||||
/>
|
||||
<div class="ml-24">
|
||||
<el-input
|
||||
v-model="themeForm.forumUrl"
|
||||
:placeholder="$t('views.system.theme.urlPlaceholder')"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-4">
|
||||
<el-checkbox
|
||||
v-model="themeForm.showProject"
|
||||
:label="$t('views.system.theme.showProject')"
|
||||
/>
|
||||
<div class="ml-24">
|
||||
<el-input
|
||||
v-model="themeForm.projectUrl"
|
||||
:placeholder="$t('views.system.theme.urlPlaceholder')"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
</el-scrollbar>
|
||||
<div class="mt-16">
|
||||
<el-text type="info">{{ $t('views.system.theme.defaultTip') }}</el-text>
|
||||
</div>
|
||||
</el-card>
|
||||
</div>
|
||||
</div>
|
||||
</el-scrollbar>
|
||||
<div class="theme-setting__operate w-full p-16-24">
|
||||
<el-button @click="resetTheme">{{ $t('views.system.theme.abandonUpdate') }}</el-button>
|
||||
<el-button type="primary" @click="updateTheme(themeFormRef)">
|
||||
{{ $t('views.system.theme.saveAndApply') }}
|
||||
</el-button
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import {ref, reactive, onMounted, computed} from 'vue'
|
||||
import {useRouter, onBeforeRouteLeave} from 'vue-router'
|
||||
import type {FormInstance, FormRules, UploadFiles} from 'element-plus'
|
||||
import {cloneDeep} from 'lodash'
|
||||
import LoginPreview from './LoginPreview.vue'
|
||||
import {themeList, defaultSetting, defaultPlatformSetting} from '@/utils/theme'
|
||||
import ThemeApi from '@/api/systemSettings/theme'
|
||||
import {MsgSuccess, MsgError} from '@/utils/message'
|
||||
import useStore from '@/stores'
|
||||
import {t} from '@/locales'
|
||||
|
||||
const {user} = useStore()
|
||||
const router = useRouter()
|
||||
|
||||
onBeforeRouteLeave((to, from) => {
|
||||
user.setTheme(cloneTheme.value)
|
||||
})
|
||||
|
||||
const themeInfo = computed(() => user.themeInfo)
|
||||
const isDefaultTheme = computed(() => {
|
||||
return user.isDefaultTheme()
|
||||
})
|
||||
|
||||
const themeFormRef = ref<FormInstance>()
|
||||
const loading = ref(false)
|
||||
const cloneTheme = ref(null)
|
||||
const themeForm = ref<any>({
|
||||
theme: '',
|
||||
icon: '',
|
||||
loginLogo: '',
|
||||
loginImage: '',
|
||||
title: 'MaxKB',
|
||||
slogan: t('views.system.theme.defaultSlogan'),
|
||||
...defaultPlatformSetting
|
||||
})
|
||||
const themeRadio = ref('')
|
||||
const customColor = ref('')
|
||||
|
||||
const rules = reactive<FormRules>({
|
||||
title: [
|
||||
{required: true, message: t('views.system.theme.websiteNamePlaceholder'), trigger: 'blur'}
|
||||
],
|
||||
slogan: [
|
||||
{required: true, message: t('views.system.theme.websiteSloganPlaceholder'), trigger: 'blur'}
|
||||
]
|
||||
})
|
||||
|
||||
const onChange = (file: any, fileList: UploadFiles, attr: string) => {
|
||||
const isLimit = file?.size / 1024 / 1024 < 10
|
||||
if (!isLimit) {
|
||||
// @ts-ignore
|
||||
MsgError(t('views.system.theme.fileMessageError'))
|
||||
return false
|
||||
} else {
|
||||
themeForm.value[attr] = file.raw
|
||||
}
|
||||
user.setTheme(themeForm.value)
|
||||
}
|
||||
|
||||
function changeThemeHandle(val: string) {
|
||||
if (val !== 'custom') {
|
||||
themeForm.value.theme = val
|
||||
user.setTheme(themeForm.value)
|
||||
}
|
||||
}
|
||||
|
||||
function customColorHandle(val: string) {
|
||||
themeForm.value.theme = val
|
||||
user.setTheme(themeForm.value)
|
||||
}
|
||||
|
||||
function resetTheme() {
|
||||
user.setTheme(cloneTheme.value)
|
||||
themeForm.value = cloneDeep(themeInfo.value)
|
||||
}
|
||||
|
||||
function resetForm(val: string) {
|
||||
themeForm.value =
|
||||
val === 'login'
|
||||
? {
|
||||
...themeForm.value,
|
||||
theme: themeForm.value.theme,
|
||||
...defaultSetting
|
||||
}
|
||||
: {
|
||||
...themeForm.value,
|
||||
theme: themeForm.value.theme,
|
||||
...defaultPlatformSetting
|
||||
}
|
||||
|
||||
user.setTheme(themeForm.value)
|
||||
}
|
||||
|
||||
const updateTheme = async (formEl: FormInstance | undefined, test?: string) => {
|
||||
if (!formEl) return
|
||||
await formEl.validate((valid, fields) => {
|
||||
if (valid) {
|
||||
const fd = new FormData()
|
||||
Object.keys(themeForm.value).map((item) => {
|
||||
fd.append(item, themeForm.value[item])
|
||||
})
|
||||
ThemeApi.postThemeInfo(fd, loading).then((res) => {
|
||||
user.theme()
|
||||
cloneTheme.value = cloneDeep(themeForm.value)
|
||||
MsgSuccess(t('views.system.theme.saveSuccess'))
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
// if (user.isExpire()) {
|
||||
// router.push({path: `/application`})
|
||||
// }
|
||||
if (themeInfo.value) {
|
||||
themeRadio.value = themeList.some((v) => v.value === themeInfo.value.theme)
|
||||
? themeInfo.value.theme
|
||||
: 'custom'
|
||||
customColor.value = themeInfo.value.theme
|
||||
themeForm.value = cloneDeep(themeInfo.value)
|
||||
cloneTheme.value = cloneDeep(themeInfo.value)
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.theme-setting {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
box-sizing: border-box;
|
||||
position: relative;
|
||||
padding-bottom: 64px;
|
||||
|
||||
&__operate {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
left: 0;
|
||||
background: #ffffff;
|
||||
text-align: right;
|
||||
box-sizing: border-box;
|
||||
box-shadow: 0px -2px 4px 0px rgba(31, 35, 41, 0.08);
|
||||
}
|
||||
|
||||
.theme-preview {
|
||||
min-width: 1000px;
|
||||
}
|
||||
|
||||
.theme-platform {
|
||||
background: #ffffff;
|
||||
height: 220px;
|
||||
|
||||
.theme-platform-header {
|
||||
padding: 10px 20px;
|
||||
background: var(--app-header-bg-color);
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
Loading…
Reference in New Issue