feat(系统设置): 参数设置-界面设置-自定义平台风格&主题
|
@ -18,7 +18,7 @@ export default defineConfig({
|
|||
configArcoStyleImportPlugin(),
|
||||
createSvgIconsPlugin({
|
||||
// 指定需要缓存的图标文件夹
|
||||
iconDirs: [resolve(process.cwd(), 'src/assets/svg')], // 与本地储存地址一致
|
||||
iconDirs: [resolve(process.cwd(), 'src/assets/svg'), resolve(process.cwd(), 'public/images')], // 与本地储存地址一致
|
||||
// 指定symbolId格式
|
||||
symbolId: 'icon-[name]',
|
||||
}),
|
||||
|
|
|
@ -2,11 +2,7 @@
|
|||
<html lang="zh">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link
|
||||
rel="shortcut icon"
|
||||
type="image/x-icon"
|
||||
href="/src/assets/favicon.ico"
|
||||
/>
|
||||
<link rel="shortcut icon" type="image/x-icon" href="./favicon.ico" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>MeterSphere</title>
|
||||
</head>
|
||||
|
|
|
@ -40,6 +40,7 @@
|
|||
"@arco-design/web-vue": "^2.49.2",
|
||||
"@arco-themes/vue-ms-theme-default": "^0.0.25",
|
||||
"@form-create/arco-design": "^3.1.21",
|
||||
"@types/color": "^3.0.3",
|
||||
"@vueuse/core": "^10.2.1",
|
||||
"ace-builds": "^1.22.0",
|
||||
"axios": "^0.24.0",
|
||||
|
@ -79,8 +80,11 @@
|
|||
"@vitest/coverage-c8": "^0.31.4",
|
||||
"@vue/babel-plugin-jsx": "^1.1.1",
|
||||
"@vue/test-utils": "^2.3.2",
|
||||
"@zougt/some-loader-utils": "^1.4.3",
|
||||
"@zougt/vite-plugin-theme-preprocessor": "^1.4.8",
|
||||
"autoprefixer": "^10.4.14",
|
||||
"axios-mock-adapter": "^1.21.5",
|
||||
"color": "^4.2.3",
|
||||
"consola": "^2.15.3",
|
||||
"cross-env": "^7.0.3",
|
||||
"eslint": "^8.42.0",
|
||||
|
|
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 1.6 KiB |
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 10 KiB |
Before Width: | Height: | Size: 46 KiB After Width: | Height: | Size: 46 KiB |
Before Width: | Height: | Size: 6.6 KiB After Width: | Height: | Size: 6.6 KiB |
|
@ -14,6 +14,7 @@
|
|||
import { saveBaseInfo, getBaseInfo } from '@/api/modules/setting/config';
|
||||
import { getLocalStorage, setLocalStorage } from '@/utils/local-storage';
|
||||
import useAppStore from '@/store/modules/app';
|
||||
import { watchStyle, watchTheme } from '@/utils/theme';
|
||||
|
||||
const appStore = useAppStore();
|
||||
|
||||
|
@ -29,6 +30,11 @@
|
|||
}
|
||||
});
|
||||
|
||||
// 初始化平台风格和主题色
|
||||
watchStyle(appStore.pageConfig.style, appStore.pageConfig);
|
||||
watchTheme(appStore.pageConfig.theme, appStore.pageConfig);
|
||||
window.document.title = appStore.pageConfig.title;
|
||||
|
||||
onBeforeMount(async () => {
|
||||
try {
|
||||
appStore.initSystemversion(); // 初始化系统版本
|
||||
|
|
|
@ -7,7 +7,12 @@ import {
|
|||
GetEmailInfoUrl,
|
||||
SavePageConfigUrl,
|
||||
GetPageConfigUrl,
|
||||
GetAuthListUrl,
|
||||
GetAuthDetailUrl,
|
||||
UpdateAuthUrl,
|
||||
AddAuthUrl,
|
||||
} from '@/api/requrls/setting/config';
|
||||
import { TableQueryParams } from '@/models/common';
|
||||
|
||||
import type {
|
||||
SaveInfoParams,
|
||||
|
@ -16,6 +21,8 @@ import type {
|
|||
BaseConfig,
|
||||
SavePageConfigParams,
|
||||
PageConfigReturns,
|
||||
AuthItem,
|
||||
AuthParams,
|
||||
} from '@/models/setting/config';
|
||||
|
||||
// 测试邮箱连接
|
||||
|
@ -52,3 +59,23 @@ export function savePageConfig(data: SavePageConfigParams) {
|
|||
export function getPageConfig() {
|
||||
return MSR.get<PageConfigReturns>({ url: GetPageConfigUrl });
|
||||
}
|
||||
|
||||
// 获取认证源列表
|
||||
export function getAuthList(data: TableQueryParams) {
|
||||
return MSR.post<AuthItem[]>({ url: GetAuthListUrl, data });
|
||||
}
|
||||
|
||||
// 获取认证源详情
|
||||
export function getAuthDetail(id: string) {
|
||||
return MSR.get<AuthItem>({ url: GetAuthDetailUrl, params: { id } });
|
||||
}
|
||||
|
||||
// 添加认证源
|
||||
export function addAuth(data: AuthParams) {
|
||||
return MSR.post({ url: AddAuthUrl, data });
|
||||
}
|
||||
|
||||
// 更新认证源
|
||||
export function updateAuth(data: AuthParams) {
|
||||
return MSR.post({ url: UpdateAuthUrl, data });
|
||||
}
|
||||
|
|
|
@ -5,3 +5,7 @@ export const GetEmailInfoUrl = '/system/parameter/get/email-info';
|
|||
export const GetBaseInfoUrl = '/system/parameter/get/base-info';
|
||||
export const SavePageConfigUrl = '/display/save';
|
||||
export const GetPageConfigUrl = '/display/info';
|
||||
export const UpdateAuthUrl = '/system/authsource/update';
|
||||
export const GetAuthListUrl = '/system/authsource/list';
|
||||
export const AddAuthUrl = '/system/authsource/add';
|
||||
export const GetAuthDetailUrl = '/system/authsource/get';
|
||||
|
|
Before Width: | Height: | Size: 4.5 KiB |
|
@ -1,5 +1,5 @@
|
|||
<template>
|
||||
<ColorPicker v-model:pureColor="innerPureColor" :z-index="1" is-widget picker-type="chrome" round-history />
|
||||
<ColorPicker v-model:pureColor="innerPureColor" :z-index="1" picker-type="chrome" is-widget round-history />
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
|
@ -19,6 +19,9 @@
|
|||
() => props.pureColor,
|
||||
(val) => {
|
||||
innerPureColor.value = val;
|
||||
},
|
||||
{
|
||||
immediate: true,
|
||||
}
|
||||
);
|
||||
|
||||
|
@ -30,4 +33,18 @@
|
|||
);
|
||||
</script>
|
||||
|
||||
<style lang="less"></style>
|
||||
<style lang="less">
|
||||
.color-cube {
|
||||
overflow: hidden;
|
||||
border-radius: var(--border-radius-small);
|
||||
}
|
||||
.vc-transparent {
|
||||
background-image: none !important;
|
||||
}
|
||||
.color-list {
|
||||
width: 100% !important;
|
||||
}
|
||||
.color-item:not(:last-child) {
|
||||
margin-right: 2px !important;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -37,6 +37,9 @@
|
|||
<a-button :disabled="props.okLoading" @click="handleCancel">
|
||||
{{ t(props.cancelText || 'ms.drawer.cancel') }}
|
||||
</a-button>
|
||||
<a-button v-if="showContinue" type="secondary" :loading="props.okLoading" @click="handleContinue">
|
||||
{{ t(props.saveContinueText || 'ms.drawer.saveContinue') }}
|
||||
</a-button>
|
||||
<a-button type="primary" :loading="props.okLoading" @click="handleOk">
|
||||
{{ t(props.okText || 'ms.drawer.ok') }}
|
||||
</a-button>
|
||||
|
@ -66,6 +69,8 @@
|
|||
okLoading?: boolean;
|
||||
okText?: string;
|
||||
cancelText?: string;
|
||||
saveContinueText?: string;
|
||||
showContinue?: boolean;
|
||||
width: number;
|
||||
}
|
||||
|
||||
|
@ -73,8 +78,9 @@
|
|||
footer: true,
|
||||
mask: true,
|
||||
showSkeleton: false,
|
||||
showContinue: false,
|
||||
});
|
||||
const emit = defineEmits(['update:visible', 'confirm', 'cancel']);
|
||||
const emit = defineEmits(['update:visible', 'confirm', 'cancel', 'continue']);
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
|
@ -87,6 +93,10 @@
|
|||
}
|
||||
);
|
||||
|
||||
const handleContinue = () => {
|
||||
emit('continue');
|
||||
};
|
||||
|
||||
const handleOk = () => {
|
||||
emit('confirm');
|
||||
};
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
export default {
|
||||
'ms.drawer.cancel': 'Cancel',
|
||||
'ms.drawer.ok': 'Confirm',
|
||||
'ms.drawer.saveContinue': 'Save & Continue',
|
||||
};
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
export default {
|
||||
'ms.drawer.cancel': '取消',
|
||||
'ms.drawer.ok': '确认',
|
||||
'ms.drawer.saveContinue': '保存并继续添加',
|
||||
};
|
||||
|
|
|
@ -414,6 +414,7 @@ export default defineComponent({
|
|||
'jumper-prepend': slots['jumper-prepend'],
|
||||
'jumper-append': slots['jumper-append'],
|
||||
}}
|
||||
simple
|
||||
disabled={props.disabled}
|
||||
current={computedCurrent.value}
|
||||
pages={pages.value}
|
||||
|
|
|
@ -191,7 +191,7 @@
|
|||
import TopMenu from '@/components/bussiness/ms-top-menu/index.vue';
|
||||
import MessageBox from '../message-box/index.vue';
|
||||
import { NOT_SHOW_PROJECT_SELECT_MODULE } from '@/router/constants';
|
||||
import { getProjectList } from '@/api/modules/setting/project';
|
||||
// import { getProjectList } from '@/api/modules/setting/project';
|
||||
import { useI18n } from '@/hooks/useI18n';
|
||||
|
||||
import type { ProjectListItem } from '@/models/setting/project';
|
||||
|
@ -210,12 +210,12 @@
|
|||
const projectList: Ref<ProjectListItem[]> = ref([]);
|
||||
|
||||
onBeforeMount(async () => {
|
||||
try {
|
||||
const res = await getProjectList(appStore.getCurrentOrgId);
|
||||
projectList.value = res;
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
// try {
|
||||
// const res = await getProjectList(appStore.getCurrentOrgId);
|
||||
// projectList.value = res;
|
||||
// } catch (error) {
|
||||
// console.log(error);
|
||||
// }
|
||||
});
|
||||
|
||||
const showProjectSelect = computed(() => {
|
||||
|
@ -279,9 +279,7 @@
|
|||
|
||||
<style scoped lang="less">
|
||||
.navbar {
|
||||
@apply flex h-full justify-between;
|
||||
|
||||
background-color: var(--color-bg-3);
|
||||
@apply flex h-full justify-between bg-transparent;
|
||||
}
|
||||
.left-side {
|
||||
@apply flex items-center;
|
||||
|
|
|
@ -11,5 +11,6 @@ export enum TableKeyEnum {
|
|||
USERGROUPUSER = 'userGroupUser',
|
||||
SYSTEM_USER = 'systemUser',
|
||||
SYSTEM_RESOURCEPOOL = 'systemResourcePool',
|
||||
SYSTEM_AUTH = 'systemResourcePool',
|
||||
ORGANNATIONMEMBER = 'member',
|
||||
}
|
||||
|
|
|
@ -96,8 +96,10 @@
|
|||
const route = useRoute();
|
||||
const permission = usePermission();
|
||||
|
||||
const innerLogo = computed(() => innerProps.value.logo || appStore.pageConfig.logoPlatform[0]?.url);
|
||||
const innerName = computed(() => innerProps.value.name || appStore.pageConfig.platformName);
|
||||
const innerLogo = computed(() =>
|
||||
props.isPreview ? innerProps.value.logo : appStore.pageConfig.logoPlatform[0]?.url
|
||||
);
|
||||
const innerName = computed(() => (props.isPreview ? innerProps.value.name : appStore.pageConfig.platformName));
|
||||
|
||||
const navbarHeight = `56px`;
|
||||
const navbar = computed(() => appStore.navbar);
|
||||
|
@ -175,7 +177,7 @@
|
|||
}
|
||||
}
|
||||
:deep(.arco-menu-light) {
|
||||
background-color: var(--color-bg-3) !important;
|
||||
background-color: transparent !important;
|
||||
.arco-menu-item {
|
||||
:hover {
|
||||
background-color: var(--color-bg-6);
|
||||
|
@ -200,4 +202,7 @@
|
|||
min-height: 500px;
|
||||
}
|
||||
}
|
||||
.arco-layout-sider-light {
|
||||
@apply bg-transparent;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -43,7 +43,7 @@ export interface TestEmailParams {
|
|||
// 界面配置入参
|
||||
export interface SavePageConfigParams {
|
||||
fileList: (File | undefined)[];
|
||||
request: Recordable[];
|
||||
request: (Recordable | undefined)[];
|
||||
}
|
||||
|
||||
interface FileParamItem extends ParamItem {
|
||||
|
@ -54,12 +54,17 @@ interface FileParamItem extends ParamItem {
|
|||
// 页面配置返回参数
|
||||
export type PageConfigReturns = FileParamItem[];
|
||||
|
||||
// 主题配置对象
|
||||
// 平台风格
|
||||
export type Style = 'default' | 'custom' | 'follow';
|
||||
|
||||
// 主题
|
||||
export type Theme = 'default' | 'custom';
|
||||
|
||||
// 主题配置对象
|
||||
export interface ThemeConfig {
|
||||
style: string;
|
||||
style: Style;
|
||||
customStyle: string;
|
||||
theme: string;
|
||||
theme: Theme;
|
||||
customTheme: string;
|
||||
}
|
||||
|
||||
|
@ -83,3 +88,36 @@ export interface PlatformConfig {
|
|||
export interface PageConfig extends ThemeConfig, LoginConfig, PlatformConfig {}
|
||||
|
||||
export type PageConfigKeys = keyof PageConfig;
|
||||
|
||||
// 认证源配置列表项对象
|
||||
export interface AuthItem {
|
||||
id: string;
|
||||
enable: boolean;
|
||||
createTime: number;
|
||||
updateTime: number;
|
||||
description: string;
|
||||
name: string;
|
||||
type: string;
|
||||
configuration: string;
|
||||
}
|
||||
|
||||
// 认证源配置对象
|
||||
export type AuthConfig = Omit<AuthItem, 'id'>;
|
||||
|
||||
// 认证源配置表单对象
|
||||
export interface AuthForm {
|
||||
id?: string;
|
||||
enable: boolean;
|
||||
description: string;
|
||||
name: string;
|
||||
type: string;
|
||||
configuration: Recordable;
|
||||
}
|
||||
|
||||
// 认证源配置接口入参
|
||||
export type AuthParams = Omit<AuthForm, 'configuration'> & {
|
||||
configuration: string;
|
||||
};
|
||||
|
||||
// 认证源配置详情对象
|
||||
export type AuthDetail = AuthForm & Omit<AuthItem, 'configuration'>;
|
||||
|
|
|
@ -7,18 +7,19 @@ import { useI18n } from '@/hooks/useI18n';
|
|||
import { cloneDeep } from 'lodash-es';
|
||||
import { getPageConfig } from '@/api/modules/setting/config';
|
||||
import { setFavicon } from '@/utils';
|
||||
import { watchStyle, watchTheme } from '@/utils/theme';
|
||||
|
||||
import type { NotificationReturn } from '@arco-design/web-vue/es/notification/interface';
|
||||
import type { RouteRecordNormalized, RouteRecordRaw } from 'vue-router';
|
||||
import type { AppState } from './types';
|
||||
import type { BreadcrumbItem } from '@/components/bussiness/ms-breadcrumb/types';
|
||||
import type { PageConfig, PageConfigKeys } from '@/models/setting/config';
|
||||
import type { PageConfig, PageConfigKeys, Style, Theme } from '@/models/setting/config';
|
||||
|
||||
const defaultThemeConfig = {
|
||||
style: 'default',
|
||||
customStyle: '',
|
||||
theme: 'default',
|
||||
customTheme: '',
|
||||
style: 'default' as Style,
|
||||
customStyle: '#f9f9fe',
|
||||
theme: 'default' as Theme,
|
||||
customTheme: '#811fa3',
|
||||
};
|
||||
const defaultLoginConfig = {
|
||||
title: 'MeterSphere',
|
||||
|
@ -211,6 +212,8 @@ const useAppStore = defineStore('app', {
|
|||
try {
|
||||
const res = await getPageConfig();
|
||||
if (Array.isArray(res) && res.length > 0) {
|
||||
let hasStyleChange = false;
|
||||
let hasThemeChange = false;
|
||||
res.forEach((e) => {
|
||||
const key = e.paramKey.split('ui.')[1] as PageConfigKeys; // 参数名前缀ui.去掉
|
||||
if (['icon', 'loginLogo', 'loginImage', 'logoPlatform'].includes(key)) {
|
||||
|
@ -222,6 +225,19 @@ const useAppStore = defineStore('app', {
|
|||
},
|
||||
] as any;
|
||||
} else {
|
||||
if (key === 'style') {
|
||||
// 风格是否更改,先判断自定义风格的值是否相等,再判断非自定义的俩值是否相等
|
||||
hasStyleChange = !['default', 'follow'].includes(e.paramValue)
|
||||
? this.pageConfig.customStyle !== e.paramValue
|
||||
: this.pageConfig.style !== e.paramValue;
|
||||
}
|
||||
if (key === 'theme') {
|
||||
// 主题是否更改,先判断自定义主题的值是否相等,再判断非自定义的俩值是否相等
|
||||
hasThemeChange =
|
||||
e.paramValue !== 'default'
|
||||
? this.pageConfig.customTheme !== e.paramValue
|
||||
: this.pageConfig.theme !== e.paramValue;
|
||||
}
|
||||
this.pageConfig[key] = e.paramValue as any;
|
||||
}
|
||||
});
|
||||
|
@ -229,16 +245,29 @@ const useAppStore = defineStore('app', {
|
|||
// 判断是否选择了自定义主题色
|
||||
this.pageConfig.customTheme = this.pageConfig.theme;
|
||||
this.pageConfig.theme = 'custom';
|
||||
} else {
|
||||
// 非自定义则需要重置自定义主题色为空,避免本地缓存与接口配置不一致
|
||||
this.pageConfig.customTheme = defaultThemeConfig.customTheme;
|
||||
}
|
||||
if (!['default', 'follow'].includes(this.pageConfig.style)) {
|
||||
// 判断是否选择了自定义平台风格
|
||||
this.pageConfig.customStyle = this.pageConfig.style;
|
||||
this.pageConfig.style = 'custom';
|
||||
} else {
|
||||
// 非自定义则需要重置自定义风格,避免本地缓存与接口配置不一致
|
||||
this.pageConfig.customStyle = defaultThemeConfig.customStyle;
|
||||
}
|
||||
if (this.pageConfig.icon[0]?.url) {
|
||||
// 设置网站 favicon
|
||||
setFavicon(this.pageConfig.icon[0].url);
|
||||
}
|
||||
// 如果风格和主题有变化,则初始化一下主题和风格;没有变化则不需要在此初始化,在 App.vue 中初始化过了
|
||||
if (hasStyleChange) {
|
||||
watchStyle(this.pageConfig.style, this.pageConfig);
|
||||
}
|
||||
if (hasThemeChange) {
|
||||
watchTheme(this.pageConfig.theme, this.pageConfig);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
|
|
|
@ -1,4 +1,2 @@
|
|||
.theme-defalut {
|
||||
--primary-6: #811fa3;
|
||||
--primary-7: #6e1a8b;
|
||||
}
|
||||
@primary-6: #811fa3;
|
||||
@primary-7: #6e1a8b;
|
||||
|
|
|
@ -4,6 +4,11 @@ export interface ScrollToViewOptions {
|
|||
inline?: 'start' | 'center' | 'end' | 'nearest';
|
||||
}
|
||||
|
||||
/**
|
||||
* 将指定元素滚动至视图区域内
|
||||
* @param targetRef 目标 ref 或 DOM
|
||||
* @param options 滚动配置
|
||||
*/
|
||||
export function scrollIntoView(targetRef: HTMLElement | Element | null, options: ScrollToViewOptions = {}) {
|
||||
const scrollOptions: ScrollToViewOptions = {
|
||||
behavior: options.behavior || 'smooth',
|
||||
|
|
|
@ -0,0 +1,134 @@
|
|||
import Color from 'color';
|
||||
|
||||
import type { PageConfig, Style, Theme } from '@/models/setting/config';
|
||||
|
||||
/**
|
||||
* 获取颜色对象的 rgb 色值
|
||||
* @param color Color对象
|
||||
* @returns 颜色值
|
||||
*/
|
||||
export function getRGBinnerVal(color: Color) {
|
||||
return color
|
||||
.rgb()
|
||||
.toString()
|
||||
.replace(/rgba?\(|\)/g, '');
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置自定义颜色的主题色
|
||||
* @param primaryColor 主题色
|
||||
*/
|
||||
export function setCustomTheme(primaryColor: string) {
|
||||
const styleTag = document.createElement('style');
|
||||
styleTag.id = 'MS-CUSTOM-THEME';
|
||||
const primary = new Color(primaryColor);
|
||||
const white = Color('#fff');
|
||||
const P = primary.toString().replace(/rgba?\(|\)/g, '');
|
||||
const P1 = getRGBinnerVal(primary.mix(white, 0.95));
|
||||
const P2 = getRGBinnerVal(primary.mix(white, 0.8));
|
||||
const P3 = getRGBinnerVal(primary.mix(white, 0.7));
|
||||
const P4 = getRGBinnerVal(primary.mix(white, 0.15));
|
||||
const P7 = getRGBinnerVal(primary.mix(Color('#000'), 0.15));
|
||||
const P9 = getRGBinnerVal(primary.mix(white, 0.9));
|
||||
styleTag.innerHTML = `
|
||||
body{
|
||||
--primary-1: ${P1};
|
||||
--primary-2: ${P2};
|
||||
--primary-3: ${P3};
|
||||
--primary-4: ${P4};
|
||||
--primary-5: ${P};
|
||||
--primary-6: ${P};
|
||||
--primary-7: ${P7};
|
||||
--primary-9: ${P9};
|
||||
}
|
||||
`;
|
||||
// 移除之前的 style 标签(如果有)
|
||||
const prevStyleTag = document.getElementById('MS-CUSTOM-THEME');
|
||||
if (prevStyleTag) {
|
||||
prevStyleTag.remove();
|
||||
}
|
||||
document.body.appendChild(styleTag);
|
||||
}
|
||||
|
||||
/**
|
||||
* 主题重置为默认主题
|
||||
*/
|
||||
export function resetTheme() {
|
||||
const prevStyleTag = document.getElementById('MS-CUSTOM-THEME');
|
||||
if (prevStyleTag) {
|
||||
prevStyleTag.remove();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置平台色
|
||||
* @param color 平台色
|
||||
*/
|
||||
export function setPlatformColor(color: string, isFollow = false) {
|
||||
const styleTag = document.createElement('style');
|
||||
styleTag.id = 'MS-CUSTOM-STYLE';
|
||||
const white = Color('#fff');
|
||||
// 跟随主题色,设置为P1
|
||||
const platformColor = isFollow ? new Color(color).mix(white, 0.95) : new Color(color);
|
||||
styleTag.innerHTML = `
|
||||
body{
|
||||
--color-bg-3: ${platformColor};
|
||||
--color-text-n9: ${platformColor};
|
||||
}
|
||||
`;
|
||||
// 移除之前的 style 标签(如果有)
|
||||
const prevStyleTag = document.getElementById('MS-CUSTOM-STYLE');
|
||||
if (prevStyleTag) {
|
||||
prevStyleTag.remove();
|
||||
}
|
||||
document.body.appendChild(styleTag);
|
||||
}
|
||||
|
||||
/**
|
||||
* 平台风格重置为默认平台风格
|
||||
*/
|
||||
export function resetStyle() {
|
||||
const prevStyleTag = document.getElementById('MS-CUSTOM-STYLE');
|
||||
if (prevStyleTag) {
|
||||
prevStyleTag.remove();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 检测风格变化
|
||||
* @param val 风格
|
||||
* @param pageConfig 页面配置对象
|
||||
*/
|
||||
export function watchStyle(val: Style, pageConfig: PageConfig) {
|
||||
if (val === 'default') {
|
||||
// 默认就是系统自带的颜色
|
||||
resetStyle();
|
||||
} else if (val === 'custom') {
|
||||
// 自定义风格颜色
|
||||
setPlatformColor(pageConfig.customStyle);
|
||||
} else {
|
||||
// 跟随主题色
|
||||
setPlatformColor(pageConfig.customTheme, true);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 检测主题色变化
|
||||
* @param val 主题色
|
||||
* @param pageConfig 页面配置对象
|
||||
*/
|
||||
export function watchTheme(val: Theme, pageConfig: PageConfig) {
|
||||
if (val === 'default') {
|
||||
resetTheme();
|
||||
if (pageConfig.style === 'follow') {
|
||||
// 若平台风格跟随主题色
|
||||
resetStyle();
|
||||
}
|
||||
} else {
|
||||
setCustomTheme(pageConfig.customTheme);
|
||||
if (pageConfig.style === 'follow') {
|
||||
// 若平台风格跟随主题色
|
||||
setPlatformColor(pageConfig.customTheme, true);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -7,7 +7,6 @@
|
|||
<script lang="ts" setup>
|
||||
import { computed } from 'vue';
|
||||
import useAppStore from '@/store/modules/app';
|
||||
import defaultBanner from '@/assets/images/login-banner.jpg';
|
||||
|
||||
const props = defineProps<{
|
||||
isPreview?: boolean;
|
||||
|
@ -16,6 +15,7 @@
|
|||
|
||||
const appStore = useAppStore();
|
||||
|
||||
const defaultBanner = `${import.meta.env.BASE_URL}images/login-banner.jpg`;
|
||||
const innerBanner = computed(() => {
|
||||
return props.banner || appStore.pageConfig.loginImage[0]?.url || defaultBanner;
|
||||
});
|
||||
|
|
|
@ -76,11 +76,11 @@
|
|||
}>();
|
||||
|
||||
const innerLogo = computed(() => {
|
||||
return props.logo || appStore.pageConfig.loginLogo[0]?.url;
|
||||
return props.isPreview ? props.logo : appStore.pageConfig.loginLogo[0]?.url;
|
||||
});
|
||||
|
||||
const innerSlogan = computed(() => {
|
||||
return props.slogan || appStore.pageConfig.slogan;
|
||||
return props.isPreview ? props.slogan : appStore.pageConfig.slogan;
|
||||
});
|
||||
|
||||
const errorMessage = ref('');
|
||||
|
|
|
@ -0,0 +1,809 @@
|
|||
<template>
|
||||
<div>
|
||||
<MsCard :loading="loading" simple>
|
||||
<div class="mb-4 flex items-center justify-between">
|
||||
<a-button type="primary" @click="createAuth">
|
||||
{{ t('system.config.auth.add') }}
|
||||
</a-button>
|
||||
</div>
|
||||
<ms-base-table v-bind="propsRes" no-disable v-on="propsEvent">
|
||||
<template #name="{ record }">
|
||||
<a-button type="text" @click="openAuthDetail(record)">{{ record.name }}</a-button>
|
||||
</template>
|
||||
<template #enable="{ record }">
|
||||
<div v-if="record.enable" class="flex items-center">
|
||||
<icon-check-circle-fill class="mr-[2px] text-[rgb(var(--success-6))]" />
|
||||
{{ t('system.config.auth.enable') }}
|
||||
</div>
|
||||
<div v-else class="flex items-center text-[var(--color-text-4)]">
|
||||
<icon-stop class="mr-[2px]" />
|
||||
{{ t('system.config.auth.disable') }}
|
||||
</div>
|
||||
</template>
|
||||
<template #action="{ record }">
|
||||
<MsButton @click="editAuth(record)">{{ t('system.config.auth.edit') }}</MsButton>
|
||||
<MsButton v-if="record.enable" @click="disabledAuth(record)">
|
||||
{{ t('system.config.auth.disable') }}
|
||||
</MsButton>
|
||||
<MsButton v-else @click="enableAuth(record)">{{ t('system.config.auth.enable') }}</MsButton>
|
||||
<MsTableMoreAction :list="tableActions" @select="handleSelect($event, record)"></MsTableMoreAction>
|
||||
</template>
|
||||
</ms-base-table>
|
||||
</MsCard>
|
||||
<MsDrawer
|
||||
v-model:visible="showDetailDrawer"
|
||||
:width="480"
|
||||
:title="activeAuthDetail.name"
|
||||
:title-tag="activeAuthDetail.enable ? t('system.config.auth.enable') : t('system.config.auth.disable')"
|
||||
:title-tag-color="activeAuthDetail.enable ? 'green' : 'gray'"
|
||||
:descriptions="activeAuthDesc"
|
||||
:footer="false"
|
||||
:mask="false"
|
||||
:show-skeleton="detailDrawerLoading"
|
||||
show-description
|
||||
>
|
||||
<template #tbutton>
|
||||
<a-button type="outline" size="mini" :disabled="detailDrawerLoading" @click="editAuth(activeAuthDetail)">
|
||||
{{ t('system.config.auth.edit') }}
|
||||
</a-button>
|
||||
</template>
|
||||
</MsDrawer>
|
||||
<MsDrawer
|
||||
v-model:visible="showDrawer"
|
||||
:title="t(isEdit ? 'system.config.auth.updateTitle' : 'system.config.auth.add')"
|
||||
:ok-text="t(isEdit ? 'system.config.auth.update' : 'system.config.auth.drawerAdd')"
|
||||
:ok-loading="drawerLoading"
|
||||
:width="680"
|
||||
show-continue
|
||||
@confirm="handleDrawerConfirm"
|
||||
@continue="handleDrawerConfirm(true)"
|
||||
@cancel="handleDrawerCancel"
|
||||
>
|
||||
<a-form ref="authFormRef" :model="activeAuthForm" layout="vertical">
|
||||
<a-form-item
|
||||
:label="t('system.config.auth.name')"
|
||||
field="url"
|
||||
asterisk-position="end"
|
||||
:rules="[{ required: true, message: t('system.config.auth.nameRequired') }]"
|
||||
required
|
||||
>
|
||||
<a-input
|
||||
v-model:model-value="activeAuthForm.name"
|
||||
:max-length="250"
|
||||
:placeholder="t('system.config.auth.namePlaceholder')"
|
||||
allow-clear
|
||||
></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item :label="t('system.config.auth.desc')" field="url" asterisk-position="end">
|
||||
<a-textarea
|
||||
v-model:model-value="activeAuthForm.description"
|
||||
:max-length="250"
|
||||
:placeholder="t('system.config.auth.descPlaceholder')"
|
||||
allow-clear
|
||||
></a-textarea>
|
||||
</a-form-item>
|
||||
<a-form-item :label="t('system.config.auth.addResource')" field="url" asterisk-position="end">
|
||||
<a-radio-group v-model:model-value="activeAuthForm.type" type="button">
|
||||
<a-radio v-for="item of authTypeList" :key="item" :value="item">{{ item }}</a-radio>
|
||||
</a-radio-group>
|
||||
</a-form-item>
|
||||
<template v-if="activeAuthForm.type === 'CAS'">
|
||||
<a-form-item
|
||||
:label="t('system.config.auth.serviceUrl')"
|
||||
field="url"
|
||||
asterisk-position="end"
|
||||
:rules="[{ required: true, message: t('system.config.auth.serviceUrlRequired') }]"
|
||||
required
|
||||
>
|
||||
<a-input
|
||||
v-model:model-value="activeAuthForm.configuration.serviceUrl"
|
||||
:max-length="250"
|
||||
:placeholder="t('system.config.auth.serviceUrlPlaceholder')"
|
||||
allow-clear
|
||||
></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
:label="t('system.config.auth.loginUrl')"
|
||||
field="url"
|
||||
asterisk-position="end"
|
||||
:rules="[{ required: true, message: t('system.config.auth.loginUrlRequired') }]"
|
||||
required
|
||||
>
|
||||
<a-input
|
||||
v-model:model-value="activeAuthForm.configuration.loginUrl"
|
||||
:max-length="250"
|
||||
:placeholder="t('system.config.auth.loginUrlPlaceholder')"
|
||||
allow-clear
|
||||
></a-input>
|
||||
<MsFormItemSub :text="t('system.config.auth.loginUrlTip')" :show-fill-icon="false" />
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
:label="t('system.config.auth.callbackUrl')"
|
||||
field="url"
|
||||
asterisk-position="end"
|
||||
:rules="[{ required: true, message: t('system.config.auth.callbackUrlRequired') }]"
|
||||
required
|
||||
>
|
||||
<a-input
|
||||
v-model:model-value="activeAuthForm.configuration.callbackUrl"
|
||||
:max-length="250"
|
||||
:placeholder="t('system.config.auth.callbackUrlPlaceholder')"
|
||||
allow-clear
|
||||
></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
:label="t('system.config.auth.verifyUrl')"
|
||||
field="url"
|
||||
asterisk-position="end"
|
||||
:rules="[{ required: true, message: t('system.config.auth.verifyUrlRequired') }]"
|
||||
required
|
||||
>
|
||||
<a-input
|
||||
v-model:model-value="activeAuthForm.configuration.verifyUrl"
|
||||
:max-length="250"
|
||||
:placeholder="t('system.config.auth.verifyUrlPlaceholder')"
|
||||
allow-clear
|
||||
></a-input>
|
||||
<MsFormItemSub :text="t('system.config.auth.verifyUrlTip')" :show-fill-icon="false" />
|
||||
</a-form-item>
|
||||
</template>
|
||||
<template v-else-if="activeAuthForm.type === 'OIDC'">
|
||||
<a-form-item
|
||||
:label="t('system.config.auth.authUrl')"
|
||||
field="url"
|
||||
asterisk-position="end"
|
||||
:rules="[{ required: true, message: t('system.config.auth.authUrlRequired') }]"
|
||||
required
|
||||
>
|
||||
<a-input
|
||||
v-model:model-value="activeAuthForm.configuration.authUrl"
|
||||
:max-length="250"
|
||||
:placeholder="t('system.config.auth.authUrlPlaceholder')"
|
||||
allow-clear
|
||||
></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
:label="t('system.config.auth.tokenUrl')"
|
||||
field="url"
|
||||
asterisk-position="end"
|
||||
:rules="[{ required: true, message: t('system.config.auth.tokenUrlRequired') }]"
|
||||
required
|
||||
>
|
||||
<a-input
|
||||
v-model:model-value="activeAuthForm.configuration.tokenUrl"
|
||||
:max-length="250"
|
||||
:placeholder="t('system.config.auth.tokenUrlPlaceholder')"
|
||||
allow-clear
|
||||
></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
:label="t('system.config.auth.userInfoUrl')"
|
||||
field="url"
|
||||
asterisk-position="end"
|
||||
:rules="[{ required: true, message: t('system.config.auth.userInfoUrlRequired') }]"
|
||||
required
|
||||
>
|
||||
<a-input
|
||||
v-model:model-value="activeAuthForm.configuration.userInfoUrl"
|
||||
:max-length="250"
|
||||
:placeholder="t('system.config.auth.userInfoUrlPlaceholder')"
|
||||
allow-clear
|
||||
></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
:label="t('system.config.auth.callbackUrl')"
|
||||
field="url"
|
||||
asterisk-position="end"
|
||||
:rules="[{ required: true, message: t('system.config.auth.callbackUrlRequired') }]"
|
||||
required
|
||||
>
|
||||
<a-input
|
||||
v-model:model-value="activeAuthForm.configuration.callbackUrl"
|
||||
:max-length="250"
|
||||
:placeholder="t('system.config.auth.OIDCCallbackUrlPlaceholder')"
|
||||
allow-clear
|
||||
></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
:label="t('system.config.auth.clientId')"
|
||||
field="url"
|
||||
asterisk-position="end"
|
||||
:rules="[{ required: true, message: t('system.config.auth.clientIdRequired') }]"
|
||||
required
|
||||
>
|
||||
<a-input
|
||||
v-model:model-value="activeAuthForm.configuration.clientId"
|
||||
:max-length="250"
|
||||
:placeholder="t('system.config.auth.clientIdPlaceholder')"
|
||||
allow-clear
|
||||
></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
:label="t('system.config.auth.clientSecret')"
|
||||
field="url"
|
||||
asterisk-position="end"
|
||||
:rules="[{ required: true, message: t('system.config.auth.clientSecretRequired') }]"
|
||||
required
|
||||
>
|
||||
<a-input
|
||||
v-model:model-value="activeAuthForm.configuration.clientSecret"
|
||||
:max-length="250"
|
||||
:placeholder="t('system.config.auth.clientSecretPlaceholder')"
|
||||
allow-clear
|
||||
></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
:label="t('system.config.auth.logoutSessionUrl')"
|
||||
field="url"
|
||||
asterisk-position="end"
|
||||
:rules="[{ required: true, message: t('system.config.auth.logoutSessionUrlRequired') }]"
|
||||
required
|
||||
>
|
||||
<a-input
|
||||
v-model:model-value="activeAuthForm.configuration.logoutSessionUrl"
|
||||
:max-length="250"
|
||||
:placeholder="t('system.config.auth.logoutSessionUrlPlaceholder')"
|
||||
allow-clear
|
||||
></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item :label="t('system.config.auth.loginUrl')" field="url" asterisk-position="end">
|
||||
<a-input
|
||||
v-model:model-value="activeAuthForm.configuration.loginUrl"
|
||||
:max-length="250"
|
||||
:placeholder="t('system.config.auth.loginUrlPlaceholder')"
|
||||
allow-clear
|
||||
></a-input>
|
||||
<MsFormItemSub :text="t('system.config.auth.loginUrlTip')" :show-fill-icon="false" />
|
||||
</a-form-item>
|
||||
</template>
|
||||
<template v-else-if="activeAuthForm.type === 'OAuth2'">
|
||||
<a-form-item
|
||||
:label="t('system.config.auth.authUrl')"
|
||||
field="url"
|
||||
asterisk-position="end"
|
||||
:rules="[{ required: true, message: t('system.config.auth.authUrlRequired') }]"
|
||||
required
|
||||
>
|
||||
<a-input
|
||||
v-model:model-value="activeAuthForm.configuration.authUrl"
|
||||
:max-length="250"
|
||||
:placeholder="t('system.config.auth.authUrlPlaceholder')"
|
||||
allow-clear
|
||||
></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
:label="t('system.config.auth.tokenUrl')"
|
||||
field="url"
|
||||
asterisk-position="end"
|
||||
:rules="[{ required: true, message: t('system.config.auth.tokenUrlRequired') }]"
|
||||
required
|
||||
>
|
||||
<a-input
|
||||
v-model:model-value="activeAuthForm.configuration.tokenUrl"
|
||||
:max-length="250"
|
||||
:placeholder="t('system.config.auth.tokenUrlPlaceholder')"
|
||||
allow-clear
|
||||
></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
:label="t('system.config.auth.userInfoUrl')"
|
||||
field="url"
|
||||
asterisk-position="end"
|
||||
:rules="[{ required: true, message: t('system.config.auth.userInfoUrlRequired') }]"
|
||||
required
|
||||
>
|
||||
<a-input
|
||||
v-model:model-value="activeAuthForm.configuration.userInfoUrl"
|
||||
:max-length="250"
|
||||
:placeholder="t('system.config.auth.userInfoUrlPlaceholder')"
|
||||
allow-clear
|
||||
></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
:label="t('system.config.auth.callbackUrl')"
|
||||
field="url"
|
||||
asterisk-position="end"
|
||||
:rules="[{ required: true, message: t('system.config.auth.callbackUrlRequired') }]"
|
||||
required
|
||||
>
|
||||
<a-input
|
||||
v-model:model-value="activeAuthForm.configuration.callbackUrl"
|
||||
:max-length="250"
|
||||
:placeholder="t('system.config.auth.OIDCCallbackUrlPlaceholder')"
|
||||
allow-clear
|
||||
></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
:label="t('system.config.auth.clientId')"
|
||||
field="url"
|
||||
asterisk-position="end"
|
||||
:rules="[{ required: true, message: t('system.config.auth.clientIdRequired') }]"
|
||||
required
|
||||
>
|
||||
<a-input
|
||||
v-model:model-value="activeAuthForm.configuration.clientId"
|
||||
:max-length="250"
|
||||
:placeholder="t('system.config.auth.clientIdPlaceholder')"
|
||||
allow-clear
|
||||
></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
:label="t('system.config.auth.clientSecret')"
|
||||
field="url"
|
||||
asterisk-position="end"
|
||||
:rules="[{ required: true, message: t('system.config.auth.clientSecretRequired') }]"
|
||||
required
|
||||
>
|
||||
<a-input
|
||||
v-model:model-value="activeAuthForm.configuration.clientSecret"
|
||||
:max-length="250"
|
||||
:placeholder="t('system.config.auth.clientSecretPlaceholder')"
|
||||
allow-clear
|
||||
></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
:label="t('system.config.auth.propertyMap')"
|
||||
field="url"
|
||||
asterisk-position="end"
|
||||
:rules="[{ required: true, message: t('system.config.auth.propertyMapRequired') }]"
|
||||
required
|
||||
>
|
||||
<a-input
|
||||
v-model:model-value="activeAuthForm.configuration.propertyMap"
|
||||
:max-length="250"
|
||||
:placeholder="t('system.config.auth.propertyMapPlaceholder')"
|
||||
allow-clear
|
||||
></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item :label="t('system.config.auth.logoutSessionUrl')" field="url" asterisk-position="end">
|
||||
<a-input
|
||||
v-model:model-value="activeAuthForm.configuration.logoutSessionUrl"
|
||||
:max-length="250"
|
||||
:placeholder="t('system.config.auth.logoutSessionUrlPlaceholder')"
|
||||
allow-clear
|
||||
></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item :label="t('system.config.auth.linkRange')" field="url" asterisk-position="end">
|
||||
<a-input
|
||||
v-model:model-value="activeAuthForm.configuration.linkRange"
|
||||
:max-length="250"
|
||||
:placeholder="t('system.config.auth.linkRangePlaceholder')"
|
||||
allow-clear
|
||||
></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item :label="t('system.config.auth.loginUrl')" field="url" asterisk-position="end">
|
||||
<a-input
|
||||
v-model:model-value="activeAuthForm.configuration.loginUrl"
|
||||
:max-length="250"
|
||||
:placeholder="t('system.config.auth.loginUrlPlaceholder')"
|
||||
allow-clear
|
||||
></a-input>
|
||||
<MsFormItemSub :text="t('system.config.auth.loginUrlTip')" :show-fill-icon="false" />
|
||||
</a-form-item>
|
||||
</template>
|
||||
<template v-else-if="activeAuthForm.type === 'LDAP'">
|
||||
<a-form-item
|
||||
:label="t('system.config.auth.LDAPUrl')"
|
||||
field="url"
|
||||
asterisk-position="end"
|
||||
:rules="[{ required: true, message: t('system.config.auth.LDAPUrlRequired') }]"
|
||||
required
|
||||
>
|
||||
<a-input
|
||||
v-model:model-value="activeAuthForm.configuration.LDAPUrl"
|
||||
:max-length="250"
|
||||
:placeholder="t('system.config.auth.LDAPUrlPlaceholder')"
|
||||
allow-clear
|
||||
></a-input>
|
||||
<MsFormItemSub :text="t('system.config.auth.LDAPUrlTip')" :show-fill-icon="false" />
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
:label="t('system.config.auth.DN')"
|
||||
field="url"
|
||||
asterisk-position="end"
|
||||
:rules="[{ required: true, message: t('system.config.auth.DNRequired') }]"
|
||||
required
|
||||
>
|
||||
<a-input
|
||||
v-model:model-value="activeAuthForm.configuration.DN"
|
||||
:max-length="250"
|
||||
:placeholder="t('system.config.auth.DNPlaceholder')"
|
||||
allow-clear
|
||||
></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
:label="t('system.config.auth.password')"
|
||||
field="url"
|
||||
asterisk-position="end"
|
||||
:rules="[{ required: true, message: t('system.config.auth.passwordRequired') }]"
|
||||
required
|
||||
>
|
||||
<a-input
|
||||
v-model:model-value="activeAuthForm.configuration.password"
|
||||
:max-length="250"
|
||||
:placeholder="t('system.config.auth.passwordPlaceholder')"
|
||||
allow-clear
|
||||
></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
:label="t('system.config.auth.OU')"
|
||||
field="url"
|
||||
asterisk-position="end"
|
||||
:rules="[{ required: true, message: t('system.config.auth.OURequired') }]"
|
||||
required
|
||||
>
|
||||
<a-input
|
||||
v-model:model-value="activeAuthForm.configuration.OU"
|
||||
:max-length="250"
|
||||
:placeholder="t('system.config.auth.OUPlaceholder')"
|
||||
allow-clear
|
||||
></a-input>
|
||||
<MsFormItemSub :text="t('system.config.auth.OUTip')" :show-fill-icon="false" />
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
:label="t('system.config.auth.userFilter')"
|
||||
field="url"
|
||||
asterisk-position="end"
|
||||
:rules="[{ required: true, message: t('system.config.auth.userFilterRequired') }]"
|
||||
required
|
||||
>
|
||||
<a-input
|
||||
v-model:model-value="activeAuthForm.configuration.userFilter"
|
||||
:max-length="250"
|
||||
:placeholder="t('system.config.auth.userFilterPlaceholder')"
|
||||
allow-clear
|
||||
></a-input>
|
||||
<MsFormItemSub :text="t('system.config.auth.userFilterTip')" :show-fill-icon="false" />
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
:label="t('system.config.auth.LDAPPropertyMap')"
|
||||
field="url"
|
||||
asterisk-position="end"
|
||||
:rules="[{ required: true, message: t('system.config.auth.LDAPPropertyMapRequired') }]"
|
||||
required
|
||||
>
|
||||
<a-input
|
||||
v-model:model-value="activeAuthForm.configuration.LDAPPropertyMap"
|
||||
:max-length="250"
|
||||
:placeholder="t('system.config.auth.LDAPPropertyMapPlaceholder')"
|
||||
allow-clear
|
||||
></a-input>
|
||||
<MsFormItemSub :text="t('system.config.auth.LDAPPropertyMapTip')" :show-fill-icon="false" />
|
||||
</a-form-item>
|
||||
<div>
|
||||
<a-button type="outline" class="mr-[16px]" @click="testLink">
|
||||
{{ t('system.config.auth.testLink') }}
|
||||
</a-button>
|
||||
<a-button type="outline" @click="testLogin">{{ t('system.config.auth.testLogin') }}</a-button>
|
||||
</div>
|
||||
</template>
|
||||
</a-form>
|
||||
</MsDrawer>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, onBeforeMount, ref } from 'vue';
|
||||
import { Message } from '@arco-design/web-vue';
|
||||
import { useI18n } from '@/hooks/useI18n';
|
||||
import { useTableStore } from '@/store';
|
||||
import { TableKeyEnum } from '@/enums/tableEnum';
|
||||
import useTable from '@/components/pure/ms-table/useTable';
|
||||
import MsCard from '@/components/pure/ms-card/index.vue';
|
||||
import MsBaseTable from '@/components/pure/ms-table/base-table.vue';
|
||||
import MsButton from '@/components/pure/ms-button/index.vue';
|
||||
import MsTableMoreAction from '@/components/pure/ms-table-more-action/index.vue';
|
||||
import MsDrawer from '@/components/pure/ms-drawer/index.vue';
|
||||
import useModal from '@/hooks/useModal';
|
||||
import { getAuthList, getAuthDetail, addAuth, updateAuth } from '@/api/modules/setting/config';
|
||||
import MsFormItemSub from '@/components/bussiness/ms-form-item-sub/index.vue';
|
||||
import { scrollIntoView } from '@/utils/dom';
|
||||
|
||||
import type { FormInstance, ValidatedError } from '@arco-design/web-vue';
|
||||
import type { MsTableColumn } from '@/components/pure/ms-table/type';
|
||||
import type { ActionsItem } from '@/components/pure/ms-table-more-action/types';
|
||||
import type { AuthDetail, AuthForm, AuthItem } from '@/models/setting/config';
|
||||
|
||||
const { t } = useI18n();
|
||||
const { openModal } = useModal();
|
||||
const loading = ref(false);
|
||||
|
||||
const tableActions: ActionsItem[] = [
|
||||
{
|
||||
label: 'system.config.auth.delete',
|
||||
eventTag: 'delete',
|
||||
danger: true,
|
||||
},
|
||||
];
|
||||
const columns: MsTableColumn = [
|
||||
{
|
||||
title: 'system.config.auth.name',
|
||||
slotName: 'name',
|
||||
dataIndex: 'name',
|
||||
width: 200,
|
||||
showInTable: true,
|
||||
},
|
||||
{
|
||||
title: 'system.config.auth.status',
|
||||
slotName: 'enable',
|
||||
dataIndex: 'enable',
|
||||
showInTable: true,
|
||||
},
|
||||
{
|
||||
title: 'system.config.auth.desc',
|
||||
dataIndex: 'description',
|
||||
showInTable: true,
|
||||
},
|
||||
{
|
||||
title: 'system.config.auth.createTime',
|
||||
dataIndex: 'createTime',
|
||||
showInTable: true,
|
||||
},
|
||||
{
|
||||
title: 'system.config.auth.updateTime',
|
||||
dataIndex: 'updateTime',
|
||||
showInTable: true,
|
||||
},
|
||||
{
|
||||
title: 'system.config.auth.action',
|
||||
slotName: 'action',
|
||||
fixed: 'right',
|
||||
width: 120,
|
||||
showInTable: true,
|
||||
},
|
||||
];
|
||||
const tableStore = useTableStore();
|
||||
tableStore.initColumn(TableKeyEnum.SYSTEM_AUTH, columns, 'drawer');
|
||||
const { propsRes, propsEvent, loadList } = useTable(getAuthList, {
|
||||
tableKey: TableKeyEnum.SYSTEM_AUTH,
|
||||
columns,
|
||||
scroll: { y: 'auto' },
|
||||
selectable: false,
|
||||
showSelectAll: false,
|
||||
});
|
||||
|
||||
onBeforeMount(() => {
|
||||
loadList();
|
||||
});
|
||||
|
||||
const enableLoading = ref(false);
|
||||
/**
|
||||
* 启用认证源
|
||||
*/
|
||||
async function enableAuth(record: any) {
|
||||
openModal({
|
||||
type: 'warning',
|
||||
title: t('system.config.auth.enableTipTitle', { name: record.name }),
|
||||
content: t('system.config.auth.enableTipContent'),
|
||||
okText: t('system.config.auth.enableConfirm'),
|
||||
cancelText: t('system.config.auth.cancel'),
|
||||
cancelButtonProps: {
|
||||
disabled: enableLoading.value,
|
||||
},
|
||||
okLoading: enableLoading.value,
|
||||
maskClosable: false,
|
||||
onBeforeOk: async () => {
|
||||
try {
|
||||
enableLoading.value = true;
|
||||
// await togglePoolStatus(record.id);
|
||||
Message.success(t('system.config.auth.enableSuccess'));
|
||||
loadList();
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
return false;
|
||||
} finally {
|
||||
enableLoading.value = false;
|
||||
}
|
||||
},
|
||||
hideCancel: false,
|
||||
});
|
||||
}
|
||||
|
||||
const disableLoading = ref(false);
|
||||
/**
|
||||
* 禁用认证源
|
||||
*/
|
||||
function disabledAuth(record: any) {
|
||||
openModal({
|
||||
type: 'warning',
|
||||
title: t('system.config.auth.disableTipTitle', { name: record.name }),
|
||||
content: t('system.config.auth.disableTipContent'),
|
||||
okText: t('system.config.auth.disableConfirm'),
|
||||
cancelText: t('system.config.auth.cancel'),
|
||||
cancelButtonProps: {
|
||||
disabled: disableLoading.value,
|
||||
},
|
||||
okLoading: disableLoading.value,
|
||||
maskClosable: false,
|
||||
onBeforeOk: async () => {
|
||||
try {
|
||||
disableLoading.value = true;
|
||||
// await togglePoolStatus(record.id);
|
||||
Message.success(t('system.config.auth.disableSuccess'));
|
||||
loadList();
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
return false;
|
||||
} finally {
|
||||
disableLoading.value = false;
|
||||
}
|
||||
},
|
||||
hideCancel: false,
|
||||
});
|
||||
}
|
||||
|
||||
const delLoading = ref(false);
|
||||
/**
|
||||
* 删除认证源
|
||||
*/
|
||||
function deleteAuth(record: any) {
|
||||
openModal({
|
||||
type: 'warning',
|
||||
title: t('system.config.auth.deleteTipTitle', { name: record.name }),
|
||||
content: t('system.config.auth.deleteTipContent'),
|
||||
okText: t('system.config.auth.deleteConfirm'),
|
||||
cancelText: t('system.config.auth.cancel'),
|
||||
okButtonProps: {
|
||||
status: 'danger',
|
||||
},
|
||||
cancelButtonProps: {
|
||||
disabled: delLoading.value,
|
||||
},
|
||||
maskClosable: false,
|
||||
okLoading: delLoading.value,
|
||||
onBeforeOk: async () => {
|
||||
try {
|
||||
delLoading.value = true;
|
||||
// await delPoolInfo(record.id);
|
||||
Message.success(t('system.config.auth.deleteSuccess'));
|
||||
loadList();
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
return false;
|
||||
} finally {
|
||||
delLoading.value = false;
|
||||
}
|
||||
},
|
||||
hideCancel: false,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理表格更多按钮事件
|
||||
* @param item
|
||||
*/
|
||||
function handleSelect(item: ActionsItem, record: any) {
|
||||
switch (item.eventTag) {
|
||||
case 'delete':
|
||||
deleteAuth(record);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
const showDetailDrawer = ref(false);
|
||||
const detailDrawerLoading = ref(false);
|
||||
const activeAuthDesc = ref([]);
|
||||
const activeAuthDetail = ref<AuthDetail>({
|
||||
id: '',
|
||||
enable: true,
|
||||
description: '',
|
||||
name: '',
|
||||
type: '',
|
||||
updateTime: 0,
|
||||
createTime: 0,
|
||||
configuration: {},
|
||||
});
|
||||
|
||||
/**
|
||||
* 查看认证源
|
||||
* @param record 表格项
|
||||
*/
|
||||
async function openAuthDetail(record: AuthItem) {
|
||||
try {
|
||||
showDetailDrawer.value = true;
|
||||
detailDrawerLoading.value = true;
|
||||
const res = await getAuthDetail(record.id);
|
||||
activeAuthDetail.value = { ...res, configuration: JSON.parse(res.configuration || '{}') };
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
} finally {
|
||||
detailDrawerLoading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
const drawerTitle = ref('');
|
||||
const showDrawer = ref(false);
|
||||
const drawerLoading = ref(false);
|
||||
const authFormRef = ref<FormInstance>();
|
||||
const authTypeList = ['CAS', 'OIDC', 'OAuth2', 'LDAP'];
|
||||
const defaultAuth = {
|
||||
id: '',
|
||||
enable: true,
|
||||
description: '',
|
||||
name: '',
|
||||
type: 'CAS',
|
||||
configuration: {},
|
||||
};
|
||||
const activeAuthForm = ref<AuthForm>({
|
||||
...defaultAuth,
|
||||
});
|
||||
const isEdit = computed(() => !!activeAuthForm.value.id);
|
||||
|
||||
/**
|
||||
* 编辑认证源
|
||||
* @param record 表格项
|
||||
*/
|
||||
function editAuth(record: AuthItem | AuthDetail) {
|
||||
drawerTitle.value = t('system.config.auth.update');
|
||||
showDrawer.value = true;
|
||||
activeAuthForm.value = {
|
||||
...record,
|
||||
configuration:
|
||||
typeof record.configuration === 'string' ? JSON.parse(record.configuration || '{}') : record.configuration,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加认证源
|
||||
*/
|
||||
function createAuth() {
|
||||
drawerTitle.value = t('system.config.auth.add');
|
||||
showDrawer.value = true;
|
||||
activeAuthForm.value = { ...defaultAuth };
|
||||
}
|
||||
|
||||
async function testLink() {}
|
||||
async function testLogin() {}
|
||||
|
||||
/**
|
||||
* 保存认证信息
|
||||
* @param isContinue 是否继续添加
|
||||
*/
|
||||
async function saveAuth(isContinue: boolean) {
|
||||
try {
|
||||
drawerLoading.value = true;
|
||||
const params = {
|
||||
...activeAuthForm.value,
|
||||
configuration: JSON.stringify(activeAuthForm.value.configuration),
|
||||
};
|
||||
if (isEdit.value) {
|
||||
await updateAuth(params);
|
||||
Message.success(t('system.config.auth.updateSuccess'));
|
||||
} else {
|
||||
await addAuth(params);
|
||||
Message.success(t('system.config.auth.addSuccess'));
|
||||
if (isContinue) {
|
||||
authFormRef.value?.resetFields();
|
||||
} else {
|
||||
showDrawer.value = false;
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
} finally {
|
||||
drawerLoading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
function handleDrawerConfirm(isContinue: boolean) {
|
||||
authFormRef.value?.validate(async (errors: Record<string, ValidatedError> | undefined) => {
|
||||
if (!errors) {
|
||||
saveAuth(isContinue);
|
||||
} else {
|
||||
scrollIntoView(document.querySelector('.arco-form-item-message'), { block: 'center' });
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function handleDrawerCancel() {
|
||||
showDrawer.value = false;
|
||||
authFormRef.value?.resetFields();
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped></style>
|
|
@ -3,20 +3,6 @@
|
|||
<!-- 风格、主题色配置 -->
|
||||
<MsCard class="mb-[16px]" :loading="pageloading" simple auto-height>
|
||||
<div class="config-title">
|
||||
{{ t('system.config.page.style') }}
|
||||
<a-tooltip :content="t('system.config.page.styleTip')" position="tl" mini>
|
||||
<icon-question-circle class="ml-[4px] text-[var(--color-text-4)] hover:text-[rgb(var(--primary-6))]" />
|
||||
</a-tooltip>
|
||||
</div>
|
||||
<a-radio-group v-model:model-value="pageConfig.style" type="button" class="mb-[4px]">
|
||||
<a-radio v-for="item of styleList" :key="item.value" :value="item.value">
|
||||
{{ item.label }}
|
||||
</a-radio>
|
||||
</a-radio-group>
|
||||
<div v-if="pageConfig.style === 'custom'" class="ml-[4px]">
|
||||
<MsColorSelect v-model:pure-color="pageConfig.customStyle" />
|
||||
</div>
|
||||
<div class="config-title mt-[16px]">
|
||||
{{ t('system.config.page.theme') }}
|
||||
<a-tooltip :content="t('system.config.page.themeTip')" position="tl" mini>
|
||||
<icon-question-circle class="ml-[4px] text-[var(--color-text-4)] hover:text-[rgb(var(--primary-6))]" />
|
||||
|
@ -27,8 +13,22 @@
|
|||
{{ item.label }}
|
||||
</a-radio>
|
||||
</a-radio-group>
|
||||
<div v-if="pageConfig.theme === 'custom'" class="mb-[4px] ml-[4px]">
|
||||
<MsColorSelect v-model:pure-color="pageConfig.customTheme" />
|
||||
<div v-if="pageConfig.theme === 'custom'" class="ml-[4px]">
|
||||
<MsColorSelect key="customTheme" v-model:pure-color="pageConfig.customTheme" />
|
||||
</div>
|
||||
<div class="config-title mt-[16px]">
|
||||
{{ t('system.config.page.style') }}
|
||||
<a-tooltip :content="t('system.config.page.styleTip')" position="tl" mini>
|
||||
<icon-question-circle class="ml-[4px] text-[var(--color-text-4)] hover:text-[rgb(var(--primary-6))]" />
|
||||
</a-tooltip>
|
||||
</div>
|
||||
<a-radio-group v-model:model-value="pageConfig.style" type="button" class="mb-[4px]">
|
||||
<a-radio v-for="item of styleList" :key="item.value" :value="item.value">
|
||||
{{ item.label }}
|
||||
</a-radio>
|
||||
</a-radio-group>
|
||||
<div v-if="pageConfig.style === 'custom'" class="mb-[4px] ml-[4px]">
|
||||
<MsColorSelect key="customStyle" v-model:pure-color="pageConfig.customStyle" />
|
||||
</div>
|
||||
</MsCard>
|
||||
<!-- 登录页配置 -->
|
||||
|
@ -60,7 +60,7 @@
|
|||
</div>
|
||||
<!-- 登录页预览实际渲染 DOM,按三种屏幕尺寸缩放 -->
|
||||
<div :class="['page-preview', isLoginPageFullscreen ? 'full-preview' : 'normal-preview']">
|
||||
<banner :banner="pageConfig.loginImage[0]?.url" is-preview />
|
||||
<banner :banner="pageConfig.loginImage[0]?.url || defaultBanner" is-preview />
|
||||
<loginForm :slogan="pageConfig.slogan" :logo="pageConfig.loginLogo[0]?.url" is-preview />
|
||||
</div>
|
||||
</div>
|
||||
|
@ -195,12 +195,12 @@
|
|||
</div>
|
||||
<!-- 平台主页预览盒子 -->
|
||||
<div class="config-preview !h-[290px]">
|
||||
<div ref="loginPageFullRef" class="login-preview">
|
||||
<div ref="platformPageFullRef" class="login-preview">
|
||||
<div
|
||||
class="absolute right-[18px] top-[16px] z-10 w-[96px] cursor-pointer text-right !text-[var(--color-text-4)]"
|
||||
@click="loginFullscreenToggle"
|
||||
class="absolute right-[18px] top-[16px] z-[999] w-[96px] cursor-pointer text-right !text-[var(--color-text-4)]"
|
||||
@click="platformFullscreenToggle"
|
||||
>
|
||||
<MsIcon v-if="isLoginPageFullscreen" type="icon-icon_off_screen" />
|
||||
<MsIcon v-if="isPlatformPageFullscreen" type="icon-icon_off_screen" />
|
||||
<MsIcon v-else type="icon-icon_full_screen_one" />
|
||||
</div>
|
||||
<!-- 平台主页预览实际渲染 DOM,按三种屏幕尺寸缩放 -->
|
||||
|
@ -209,7 +209,7 @@
|
|||
'page-preview',
|
||||
'platform-preview',
|
||||
'!h-[550px]',
|
||||
isLoginPageFullscreen ? 'full-preview' : 'normal-preview',
|
||||
isPlatformPageFullscreen ? 'full-preview' : 'normal-preview',
|
||||
]"
|
||||
>
|
||||
<defaultLayout
|
||||
|
@ -298,7 +298,7 @@
|
|||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, ref } from 'vue';
|
||||
import { computed, ref, watch, onBeforeUnmount } from 'vue';
|
||||
import { useFullscreen } from '@vueuse/core';
|
||||
import { Message } from '@arco-design/web-vue';
|
||||
import { useI18n } from '@/hooks/useI18n';
|
||||
|
@ -312,10 +312,12 @@
|
|||
import MsUpload from '@/components/pure/ms-upload/index.vue';
|
||||
import defaultLayout from '@/layout/default-layout.vue';
|
||||
import { scrollIntoView } from '@/utils/dom';
|
||||
import { setCustomTheme, setPlatformColor, watchStyle, watchTheme } from '@/utils/theme';
|
||||
import { savePageConfig } from '@/api/modules/setting/config';
|
||||
|
||||
import type { FormInstance, ValidatedError } from '@arco-design/web-vue';
|
||||
|
||||
const defaultBanner = `${import.meta.env.BASE_URL}images/login-banner.jpg`;
|
||||
const { t } = useI18n();
|
||||
const appStore = useAppStore();
|
||||
const collapsedWidth = 86;
|
||||
|
@ -325,7 +327,10 @@
|
|||
const pageloading = ref(false);
|
||||
const pageConfig = ref({ ...appStore.pageConfig });
|
||||
const loginPageFullRef = ref<HTMLElement | null>(null);
|
||||
const platformPageFullRef = ref<HTMLElement | null>(null);
|
||||
const { isFullscreen: isLoginPageFullscreen, toggle: loginFullscreenToggle } = useFullscreen(loginPageFullRef);
|
||||
const { isFullscreen: isPlatformPageFullscreen, toggle: platformFullscreenToggle } =
|
||||
useFullscreen(platformPageFullRef);
|
||||
const loginConfigFormRef = ref<FormInstance>();
|
||||
const platformConfigFormRef = ref<FormInstance>();
|
||||
|
||||
|
@ -355,6 +360,42 @@
|
|||
},
|
||||
];
|
||||
|
||||
watch(
|
||||
() => pageConfig.value.style,
|
||||
(val) => {
|
||||
watchStyle(val, pageConfig.value);
|
||||
}
|
||||
);
|
||||
|
||||
watch(
|
||||
() => pageConfig.value.customStyle,
|
||||
(val) => {
|
||||
if (val && pageConfig.value.style === 'custom') {
|
||||
setPlatformColor(val);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
watch(
|
||||
() => pageConfig.value.theme,
|
||||
(val) => {
|
||||
watchTheme(val, pageConfig.value);
|
||||
}
|
||||
);
|
||||
|
||||
watch(
|
||||
() => pageConfig.value.customTheme,
|
||||
(val) => {
|
||||
if (val && pageConfig.value.theme === 'custom') {
|
||||
setCustomTheme(val);
|
||||
if (pageConfig.value.style === 'follow') {
|
||||
// 若平台风格跟随主题色
|
||||
setPlatformColor(pageConfig.value.customTheme, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
function resetLoginPageConfig() {
|
||||
pageConfig.value = {
|
||||
...pageConfig.value,
|
||||
|
@ -383,51 +424,72 @@
|
|||
paramValue: pageConfig.value.icon[0]?.url,
|
||||
type: 'file',
|
||||
fileName: pageConfig.value.icon[0]?.name,
|
||||
isDefault: pageConfig.value.icon.length === 0, // 是否为默认值
|
||||
hasFile: pageConfig.value.icon[0]?.file, // 是否是上传了文件
|
||||
},
|
||||
{
|
||||
paramKey: 'ui.loginLogo',
|
||||
paramValue: pageConfig.value.loginLogo[0]?.url,
|
||||
type: 'file',
|
||||
fileName: pageConfig.value.loginLogo[0]?.name,
|
||||
isDefault: pageConfig.value.loginLogo.length === 0,
|
||||
hasFile: pageConfig.value.loginLogo[0]?.file,
|
||||
},
|
||||
{
|
||||
paramKey: 'ui.loginImage',
|
||||
paramValue: pageConfig.value.loginImage[0]?.url,
|
||||
type: 'file',
|
||||
fileName: pageConfig.value.loginImage[0]?.name,
|
||||
isDefault: pageConfig.value.loginImage.length === 0,
|
||||
hasFile: pageConfig.value.loginImage[0]?.file,
|
||||
},
|
||||
{
|
||||
paramKey: 'ui.logoPlatform',
|
||||
paramValue: pageConfig.value.logoPlatform[0]?.url,
|
||||
type: 'file',
|
||||
fileName: pageConfig.value.logoPlatform[0]?.name,
|
||||
isDefault: pageConfig.value.logoPlatform.length === 0,
|
||||
hasFile: pageConfig.value.logoPlatform[0]?.file,
|
||||
},
|
||||
{ paramKey: 'ui.slogan', paramValue: pageConfig.value.slogan, type: 'text' },
|
||||
{ paramKey: 'ui.title', paramValue: pageConfig.value.title, type: 'text' },
|
||||
{ paramKey: 'ui.style', paramValue: pageConfig.value.customStyle || pageConfig.value.style, type: 'text' },
|
||||
{ paramKey: 'ui.theme', paramValue: pageConfig.value.customTheme || pageConfig.value.theme, type: 'text' },
|
||||
{
|
||||
paramKey: 'ui.style',
|
||||
paramValue: pageConfig.value.style === 'custom' ? pageConfig.value.customStyle : pageConfig.value.style,
|
||||
type: 'text',
|
||||
},
|
||||
{
|
||||
paramKey: 'ui.theme',
|
||||
paramValue: pageConfig.value.theme === 'custom' ? pageConfig.value.customTheme : pageConfig.value.theme,
|
||||
type: 'text',
|
||||
},
|
||||
{ paramKey: 'ui.helpDoc', paramValue: pageConfig.value.helpDoc, type: 'text' },
|
||||
{ paramKey: 'ui.platformName', paramValue: pageConfig.value.platformName, type: 'text' },
|
||||
];
|
||||
].filter((e) => {
|
||||
if (e.type === 'file') {
|
||||
return e.hasFile || e.isDefault;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
const fileList = [
|
||||
pageConfig.value.icon[0].file
|
||||
pageConfig.value.icon[0]?.file
|
||||
? new File([pageConfig.value.icon[0].file as File], `ui.icon,${pageConfig.value.icon[0].file?.name}`)
|
||||
: undefined,
|
||||
pageConfig.value.loginLogo[0].file
|
||||
pageConfig.value.loginLogo[0]?.file
|
||||
? new File(
|
||||
[pageConfig.value.loginLogo[0].file as File],
|
||||
`ui.loginLogo,${pageConfig.value.loginLogo[0].file?.name}`
|
||||
)
|
||||
: undefined,
|
||||
pageConfig.value.loginImage[0].file
|
||||
pageConfig.value.loginImage[0]?.file
|
||||
? new File(
|
||||
[pageConfig.value.loginImage[0].file as File],
|
||||
`ui.loginImage,${pageConfig.value.loginImage[0].file?.name}`
|
||||
)
|
||||
: undefined,
|
||||
pageConfig.value.logoPlatform[0].file
|
||||
pageConfig.value.logoPlatform[0]?.file
|
||||
? new File(
|
||||
[pageConfig.value.logoPlatform[0].file as File],
|
||||
[pageConfig.value.logoPlatform[0]?.file as File],
|
||||
`ui.logoPlatform,${pageConfig.value.logoPlatform[0].file?.name}`
|
||||
)
|
||||
: undefined,
|
||||
|
@ -435,6 +497,8 @@
|
|||
return { request, fileList };
|
||||
}
|
||||
|
||||
const isSave = ref(false); // 是否保存,没有保存但是更改了主题配置需要重置一下。保存了就不需要重置了。
|
||||
|
||||
/**
|
||||
* 保存并应用
|
||||
*/
|
||||
|
@ -444,6 +508,7 @@
|
|||
await savePageConfig(makeParams());
|
||||
Message.success(t('system.config.page.saveSuccess'));
|
||||
appStore.initPageConfig(); // 初始化页面配置
|
||||
isSave.value = true;
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
} finally {
|
||||
|
@ -470,9 +535,27 @@
|
|||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
const errDom = document.querySelector('.arco-input-error');
|
||||
const errDom = document.querySelector('.arco-form-item-message');
|
||||
scrollIntoView(errDom, { block: 'center' });
|
||||
}
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
if (isSave.value === false) {
|
||||
// 离开前未保存,需要判断是否更改了主题和风格,改了的话需要重置回来
|
||||
if (
|
||||
pageConfig.value.style !== appStore.pageConfig.style &&
|
||||
pageConfig.value.customStyle !== appStore.pageConfig.style
|
||||
) {
|
||||
watchStyle(appStore.pageConfig.style, appStore.pageConfig);
|
||||
}
|
||||
if (
|
||||
pageConfig.value.theme !== appStore.pageConfig.theme &&
|
||||
pageConfig.value.customTheme !== appStore.pageConfig.theme
|
||||
) {
|
||||
watchTheme(appStore.pageConfig.theme, appStore.pageConfig);
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
|
@ -512,7 +595,8 @@
|
|||
width: 100vw;
|
||||
}
|
||||
.login-preview {
|
||||
position: relative;
|
||||
@apply relative bg-white;
|
||||
|
||||
width: 740px;
|
||||
@media screen and (min-width: 1600px) {
|
||||
width: 882px;
|
||||
|
@ -532,7 +616,7 @@
|
|||
.page-preview {
|
||||
@apply relative flex flex-1;
|
||||
|
||||
width: 1470px;
|
||||
width: 1480px;
|
||||
height: 916px;
|
||||
transform-origin: center;
|
||||
@media screen and (min-width: 1800px) {
|
||||
|
@ -547,6 +631,7 @@
|
|||
}
|
||||
.full-preview {
|
||||
width: 100vw;
|
||||
height: 100vh !important;
|
||||
transform: none;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -14,19 +14,34 @@
|
|||
</a-tabs>
|
||||
</MsCard>
|
||||
<baseConfig v-show="activeTab === 'baseConfig'" />
|
||||
<pageConfig v-show="activeTab === 'pageConfig'" />
|
||||
<pageConfig v-if="isInitedPageConfig" v-show="activeTab === 'pageConfig'" />
|
||||
<authConfig v-if="isInitedAuthConfig" v-show="activeTab === 'authConfig'" />
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue';
|
||||
import { ref, watch } from 'vue';
|
||||
import MsCard from '@/components/pure/ms-card/index.vue';
|
||||
import { useI18n } from '@/hooks/useI18n';
|
||||
import baseConfig from './components/baseConfig.vue';
|
||||
import pageConfig from './components/pageConfig.vue';
|
||||
import authConfig from './components/authConfig.vue';
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
const activeTab = ref('pageConfig');
|
||||
const activeTab = ref('authConfig');
|
||||
const isInitedPageConfig = ref(activeTab.value === 'pageConfig');
|
||||
const isInitedAuthConfig = ref(activeTab.value === 'authConfig');
|
||||
|
||||
watch(
|
||||
() => activeTab.value,
|
||||
(val) => {
|
||||
if (val === 'pageConfig' && !isInitedPageConfig.value) {
|
||||
isInitedPageConfig.value = true;
|
||||
} else if (val === 'authConfig' && !isInitedAuthConfig.value) {
|
||||
isInitedAuthConfig.value = true;
|
||||
}
|
||||
}
|
||||
);
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
|
|
|
@ -90,4 +90,107 @@ export default {
|
|||
'system.config.page.save': '保存并应用',
|
||||
'system.config.page.unsave': '未保存',
|
||||
'system.config.page.saveSuccess': '保存成功',
|
||||
'system.config.auth.add': '添加认证',
|
||||
'system.config.auth.enable': '启用',
|
||||
'system.config.auth.enableSuccess': '启用成功',
|
||||
'system.config.auth.enableTipTitle': '启用认证 {name}',
|
||||
'system.config.auth.enableTipContent': '启用后可通过该认证方式登录',
|
||||
'system.config.auth.enableConfirm': '确认启用',
|
||||
'system.config.auth.disable': '禁用',
|
||||
'system.config.auth.disableSuccess': '禁用成功',
|
||||
'system.config.auth.disableTipTitle': '禁用认证 {name}',
|
||||
'system.config.auth.disableTipContent': '禁用后不支持该认证方式登录',
|
||||
'system.config.auth.disableConfirm': '确认禁用',
|
||||
'system.config.auth.updateSuccess': '更新成功',
|
||||
'system.config.auth.addSuccess': '添加成功',
|
||||
'system.config.auth.cancel': '取消',
|
||||
'system.config.auth.edit': '编辑',
|
||||
'system.config.auth.name': '名称',
|
||||
'system.config.auth.status': '状态',
|
||||
'system.config.auth.desc': '描述',
|
||||
'system.config.auth.createTime': '创建时间',
|
||||
'system.config.auth.updateTime': '更新时间',
|
||||
'system.config.auth.action': '操作',
|
||||
'system.config.auth.delete': '删除',
|
||||
'system.config.auth.deleteTipTitle': '确认删除 {name} 这个认证吗?',
|
||||
'system.config.auth.deleteTipContent': '删除后不可恢复,请谨慎操作!',
|
||||
'system.config.auth.deleteConfirm': '确认删除',
|
||||
'system.config.auth.deleteSuccess': '删除成功',
|
||||
'system.config.auth.updateTitle': '更新认证',
|
||||
'system.config.auth.drawerAdd': '添加',
|
||||
'system.config.auth.update': '更新',
|
||||
'system.config.auth.nameRequired': '认证源名称不能为空',
|
||||
'system.config.auth.namePlaceholder': '请输入认证源名称',
|
||||
'system.config.auth.descPlaceholder': '请对该认证源进行描述',
|
||||
'system.config.auth.addResource': '添加资源',
|
||||
'system.config.auth.serviceUrl': '服务端地址',
|
||||
'system.config.auth.serviceUrlRequired': '服务端地址不能为空',
|
||||
'system.config.auth.serviceUrlPlaceholder': '例如:http://<casurl>',
|
||||
'system.config.auth.loginUrl': '登录地址',
|
||||
'system.config.auth.loginUrlRequired': '登录地址不能为空',
|
||||
'system.config.auth.loginUrlPlaceholder': '例如:http://<casurl>/login',
|
||||
'system.config.auth.loginUrlTip': '当身份认证失败,会重新定向到该登录页',
|
||||
'system.config.auth.verifyUrl': '验证地址',
|
||||
'system.config.auth.verifyUrlRequired': '验证地址不能为空',
|
||||
'system.config.auth.verifyUrlPlaceholder': '例如:http://<casurl>/serviceValidate',
|
||||
'system.config.auth.verifyUrlTip': '用于验证登录的信息是否正确',
|
||||
'system.config.auth.callbackUrl': '回调地址',
|
||||
'system.config.auth.callbackUrlRequired': '回调地址不能为空',
|
||||
// eslint-disable-next-line no-template-curly-in-string
|
||||
'system.config.auth.callbackUrlPlaceholder': '例如:http://<meteresphere-endpoint>/sso/callback/cas/suthld',
|
||||
'system.config.auth.OIDCCallbackUrlPlaceholder':
|
||||
// eslint-disable-next-line no-template-curly-in-string
|
||||
'例如:http://<metersphere-endpoint>/sso/callback or http://<metersphere-endpoint>/sso/callback/authld',
|
||||
'system.config.auth.authUrl': '授权端地址',
|
||||
'system.config.auth.authUrlRequired': '授权端地址不能为空',
|
||||
'system.config.auth.authUrlPlaceholder':
|
||||
'例如:http://<keyclock>auth/realms/<metersphere>/protocol/openid-connect/auth',
|
||||
'system.config.auth.tokenUrl': 'Token 端点地址',
|
||||
'system.config.auth.tokenUrlRequired': 'Token 端点地址不能为空',
|
||||
'system.config.auth.tokenUrlPlaceholder':
|
||||
'例如:http://<keyclock>auth/realms/<metersphere>/protocol/openid-connect/token',
|
||||
'system.config.auth.userInfoUrl': '用户信息端点地址',
|
||||
'system.config.auth.userInfoUrlRequired': '用户信息端点地址不能为空',
|
||||
'system.config.auth.userInfoUrlPlaceholder':
|
||||
'例如:http://<keyclock>auth/realms/<metersphere>/protocol/openid-connect/userinfo',
|
||||
'system.config.auth.clientId': '客户端 ID',
|
||||
'system.config.auth.clientIdRequired': '客户端 ID不能为空',
|
||||
'system.config.auth.clientIdPlaceholder': '例如:metersphere',
|
||||
'system.config.auth.clientSecret': '客户端密钥',
|
||||
'system.config.auth.clientSecretRequired': '客户端密钥不能为空',
|
||||
'system.config.auth.clientSecretPlaceholder': 'OIDC client secret',
|
||||
'system.config.auth.logoutSessionUrl': '注销会话端地址',
|
||||
'system.config.auth.logoutSessionUrlRequired': '注销会话端地址不能为空',
|
||||
'system.config.auth.logoutSessionUrlPlaceholder':
|
||||
'例如:http://<keyclock>/auth/realms/<metersphere>/protocol/openid-connect/logout',
|
||||
'system.config.auth.password': '密码',
|
||||
'system.config.auth.passwordRequired': '密码不能为空',
|
||||
'system.config.auth.passwordPlaceholder': 'OIDC client secret',
|
||||
'system.config.auth.LDAPPasswordPlaceholder': '请输入',
|
||||
'system.config.auth.linkRange': '连接范围',
|
||||
'system.config.auth.linkRangePlaceholder': 'openid profile email',
|
||||
'system.config.auth.propertyMap': '属性映射',
|
||||
'system.config.auth.propertyMapRequired': '属性映射不能为空',
|
||||
'system.config.auth.propertyMapPlaceholder': '{"userid":"login","username":"name","email":"email"}',
|
||||
'system.config.auth.LDAPUrl': 'LDAP 地址',
|
||||
'system.config.auth.LDAPUrlRequired': 'LDAP 地址不能为空',
|
||||
'system.config.auth.LDAPUrlPlaceholder': '请输入',
|
||||
'system.config.auth.LDAPUrlTip': '设置服务端地址',
|
||||
'system.config.auth.DN': '绑定 DN',
|
||||
'system.config.auth.DNRequired': 'DN 不能为空',
|
||||
'system.config.auth.DNPlaceholder': '请输入',
|
||||
'system.config.auth.OU': '用户 OU',
|
||||
'system.config.auth.OURequired': 'OU 不能为空',
|
||||
'system.config.auth.OUPlaceholder': '请输入',
|
||||
'system.config.auth.OUTip': '多个 OU 用 "I" 分隔',
|
||||
'system.config.auth.userFilter': '用户过滤器',
|
||||
'system.config.auth.userFilterRequired': '用户过滤器不能为空',
|
||||
'system.config.auth.userFilterPlaceholder': '请输入',
|
||||
'system.config.auth.userFilterTip': 'cn or uid or sAMAccountName=%(user)s',
|
||||
'system.config.auth.LDAPPropertyMap': 'LDAP 属性映射',
|
||||
'system.config.auth.LDAPPropertyMapRequired': 'LDAP 属性映射不能为空',
|
||||
'system.config.auth.LDAPPropertyMapPlaceholder': '请输入',
|
||||
'system.config.auth.LDAPPropertyMapTip': '左侧键为 MeterSphere 用户属性,右侧值为认证平台用户属性',
|
||||
'system.config.auth.testLink': '测试连接',
|
||||
'system.config.auth.testLogin': '测试登录',
|
||||
};
|
||||
|
|