feat: improve theme configuration
@@ -1,2 +1,2 @@
|
|||||||
registry=https://registry.npmmirror.com/
|
registry=https://registry.npmmirror.com/
|
||||||
|
public-hoist-pattern[]=umi
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { RequestConfig } from '@@/plugin-request/request';
|
import { RequestConfig } from '@@/plugin-request/request';
|
||||||
import { Modal, theme as antdTheme } from 'antd';
|
import { Modal, theme as antdTheme } from 'antd';
|
||||||
import React, { useEffect, useRef } from 'react';
|
import React, { useEffect, useRef, useState } from 'react';
|
||||||
import { history, useAntdConfigSetter } from 'umi';
|
import { history, useAntdConfigSetter } from 'umi';
|
||||||
import { SUCCESS_CODE } from './constants/codes.constants';
|
import { SUCCESS_CODE } from './constants/codes.constants';
|
||||||
import { ACCESS_TOKEN } from './constants/home.constants';
|
import { ACCESS_TOKEN } from './constants/home.constants';
|
||||||
@@ -8,7 +8,11 @@ import {
|
|||||||
applyOpenAppThemeToDocument,
|
applyOpenAppThemeToDocument,
|
||||||
getInitialOpenAppTheme,
|
getInitialOpenAppTheme,
|
||||||
} from './constants/openAppTheme.constants';
|
} from './constants/openAppTheme.constants';
|
||||||
import { darkThemeTokens, themeTokens } from './constants/theme.constants';
|
import {
|
||||||
|
darkThemeTokens,
|
||||||
|
getComponentThemes,
|
||||||
|
themeTokens,
|
||||||
|
} from './constants/theme.constants';
|
||||||
import { APP_NAME, APP_VERSION } from './constants/version';
|
import { APP_NAME, APP_VERSION } from './constants/version';
|
||||||
import useEventPolling from './hooks/useEventPolling';
|
import useEventPolling from './hooks/useEventPolling';
|
||||||
import { request as requestCommon } from './services/common';
|
import { request as requestCommon } from './services/common';
|
||||||
@@ -68,6 +72,59 @@ const GlobalEventPolling: React.FC = () => {
|
|||||||
return contextHolder; // 返回 contextHolder 以支持 Modal 的动态主题
|
return contextHolder; // 返回 contextHolder 以支持 Modal 的动态主题
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const GlobalThemeEffects: React.FC = () => {
|
||||||
|
const [themeData, setThemeData] = useState(() =>
|
||||||
|
unifiedThemeService.getCurrentData(),
|
||||||
|
);
|
||||||
|
const [progressVisible, setProgressVisible] = useState(false);
|
||||||
|
const progressTimerRef = useRef<number>();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const handleThemeChange = () => {
|
||||||
|
setThemeData(unifiedThemeService.getCurrentData());
|
||||||
|
};
|
||||||
|
|
||||||
|
unifiedThemeService.addListener(handleThemeChange);
|
||||||
|
return () => {
|
||||||
|
unifiedThemeService.removeListener(handleThemeChange);
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!themeData.basicConfig.showNprogress) return;
|
||||||
|
|
||||||
|
const unlisten = history.listen(() => {
|
||||||
|
setProgressVisible(true);
|
||||||
|
window.clearTimeout(progressTimerRef.current);
|
||||||
|
progressTimerRef.current = window.setTimeout(() => {
|
||||||
|
setProgressVisible(false);
|
||||||
|
}, 420);
|
||||||
|
});
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
unlisten();
|
||||||
|
window.clearTimeout(progressTimerRef.current);
|
||||||
|
};
|
||||||
|
}, [themeData.basicConfig.showNprogress]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{themeData.basicConfig.showNprogress && (
|
||||||
|
<div
|
||||||
|
className={`xagi-top-progress ${
|
||||||
|
progressVisible ? 'xagi-top-progress-active' : ''
|
||||||
|
}`}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{themeData.basicConfig.watermarkVisible && (
|
||||||
|
<div className="xagi-global-watermark" aria-hidden="true">
|
||||||
|
<span>{APP_NAME}</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
const AppContainer: React.FC<{ children: React.ReactElement }> = ({
|
const AppContainer: React.FC<{ children: React.ReactElement }> = ({
|
||||||
children,
|
children,
|
||||||
}) => {
|
}) => {
|
||||||
@@ -222,6 +279,7 @@ const AppContainer: React.FC<{ children: React.ReactElement }> = ({
|
|||||||
|
|
||||||
const signature = JSON.stringify({
|
const signature = JSON.stringify({
|
||||||
mode: darkMode ? 'dark' : 'light',
|
mode: darkMode ? 'dark' : 'light',
|
||||||
|
language: data.language || getCurrentLang(),
|
||||||
tokens,
|
tokens,
|
||||||
});
|
});
|
||||||
if (signature === lastAppliedRef.current) return;
|
if (signature === lastAppliedRef.current) return;
|
||||||
@@ -231,11 +289,10 @@ const AppContainer: React.FC<{ children: React.ReactElement }> = ({
|
|||||||
theme: {
|
theme: {
|
||||||
algorithm,
|
algorithm,
|
||||||
token: tokens as any,
|
token: tokens as any,
|
||||||
components: {
|
components: getComponentThemes({
|
||||||
Segmented: {
|
darkMode,
|
||||||
itemSelectedColor: data.primaryColor,
|
primaryColor: data.primaryColor,
|
||||||
},
|
}),
|
||||||
},
|
|
||||||
cssVar: { prefix: 'xagi' },
|
cssVar: { prefix: 'xagi' },
|
||||||
},
|
},
|
||||||
locale: getAntdLocale(data.language || getCurrentLang()),
|
locale: getAntdLocale(data.language || getCurrentLang()),
|
||||||
@@ -252,6 +309,22 @@ const AppContainer: React.FC<{ children: React.ReactElement }> = ({
|
|||||||
'data-nav-style',
|
'data-nav-style',
|
||||||
data.navigationStyle === 'style1' ? 'compact' : 'expanded',
|
data.navigationStyle === 'style1' ? 'compact' : 'expanded',
|
||||||
);
|
);
|
||||||
|
document.documentElement.setAttribute(
|
||||||
|
'data-menu-layout',
|
||||||
|
data.menuLayout,
|
||||||
|
);
|
||||||
|
document.documentElement.setAttribute(
|
||||||
|
'data-menu-style',
|
||||||
|
data.menuStyle,
|
||||||
|
);
|
||||||
|
document.documentElement.setAttribute(
|
||||||
|
'data-tab-style',
|
||||||
|
data.basicConfig.tabStyle,
|
||||||
|
);
|
||||||
|
document.documentElement.setAttribute(
|
||||||
|
'data-page-transition',
|
||||||
|
data.basicConfig.pageTransition || 'none',
|
||||||
|
);
|
||||||
|
|
||||||
unifiedThemeService.updateData(data, {
|
unifiedThemeService.updateData(data, {
|
||||||
immediate: true,
|
immediate: true,
|
||||||
@@ -294,6 +367,7 @@ const AppContainer: React.FC<{ children: React.ReactElement }> = ({
|
|||||||
<>
|
<>
|
||||||
{/* 只有用户已登录时才启动事件轮询 */}
|
{/* 只有用户已登录时才启动事件轮询 */}
|
||||||
<GlobalEventPolling />
|
<GlobalEventPolling />
|
||||||
|
<GlobalThemeEffects />
|
||||||
{children}
|
{children}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|||||||
BIN
qiming/src/assets/images/settings/menu_layouts/dual_column.png
Normal file
|
After Width: | Height: | Size: 514 B |
BIN
qiming/src/assets/images/settings/menu_layouts/horizontal.png
Normal file
|
After Width: | Height: | Size: 409 B |
BIN
qiming/src/assets/images/settings/menu_layouts/mixed.png
Normal file
|
After Width: | Height: | Size: 431 B |
BIN
qiming/src/assets/images/settings/menu_layouts/vertical.png
Normal file
|
After Width: | Height: | Size: 439 B |
BIN
qiming/src/assets/images/settings/menu_styles/dark.png
Normal file
|
After Width: | Height: | Size: 292 B |
BIN
qiming/src/assets/images/settings/menu_styles/design.png
Normal file
|
After Width: | Height: | Size: 286 B |
BIN
qiming/src/assets/images/settings/menu_styles/light.png
Normal file
|
After Width: | Height: | Size: 293 B |
BIN
qiming/src/assets/images/settings/theme_styles/dark.png
Normal file
|
After Width: | Height: | Size: 448 B |
BIN
qiming/src/assets/images/settings/theme_styles/light.png
Normal file
|
After Width: | Height: | Size: 416 B |
BIN
qiming/src/assets/images/settings/theme_styles/system.png
Normal file
|
After Width: | Height: | Size: 509 B |
@@ -16,36 +16,56 @@
|
|||||||
|
|
||||||
// 默认状态
|
// 默认状态
|
||||||
&:not(.selected) {
|
&:not(.selected) {
|
||||||
background-color: @colorBgContainer;
|
border: none !important;
|
||||||
border: none;
|
background-color: var(--xagi-layout-bg-input, #fff) !important;
|
||||||
color: @colorText;
|
color: var(--xagi-layout-text-primary, #1f2337) !important;
|
||||||
box-shadow: none !important;
|
box-shadow: none !important;
|
||||||
|
|
||||||
&:hover:not(.disabled) {
|
&:hover:not(.disabled) {
|
||||||
background-color: @colorBgTextHover;
|
border-color: transparent !important;
|
||||||
border-color: @colorPrimaryHover;
|
background-color: rgba(93, 135, 255, 0.1) !important;
|
||||||
color: @colorPrimaryHover;
|
background-color: color-mix(
|
||||||
|
in srgb,
|
||||||
|
var(--xagi-color-primary, #5d87ff) 10%,
|
||||||
|
#fff
|
||||||
|
) !important;
|
||||||
|
color: var(--xagi-color-primary, #5d87ff) !important;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 选中状态
|
// 选中状态
|
||||||
&.selected {
|
&.selected {
|
||||||
background-color: @colorPrimaryBg;
|
border: none !important;
|
||||||
border: none;
|
background-color: rgba(93, 135, 255, 0.12) !important;
|
||||||
color: @colorPrimary;
|
background-color: color-mix(
|
||||||
|
in srgb,
|
||||||
|
var(--xagi-color-primary, #5d87ff) 16%,
|
||||||
|
#fff
|
||||||
|
) !important;
|
||||||
|
color: var(--xagi-color-primary, #5d87ff) !important;
|
||||||
font-weight: @fontWeightStrong;
|
font-weight: @fontWeightStrong;
|
||||||
box-shadow: none;
|
box-shadow: none !important;
|
||||||
|
|
||||||
&:hover:not(.disabled) {
|
&:hover:not(.disabled) {
|
||||||
background-color: @colorPrimaryBg;
|
border: none !important;
|
||||||
border: none;
|
background-color: rgba(93, 135, 255, 0.16) !important;
|
||||||
color: @colorPrimaryHover;
|
background-color: color-mix(
|
||||||
|
in srgb,
|
||||||
|
var(--xagi-color-primary, #5d87ff) 20%,
|
||||||
|
#fff
|
||||||
|
) !important;
|
||||||
|
color: var(--xagi-color-primary, #5d87ff) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
&:focus:not(.disabled) {
|
&:focus:not(.disabled) {
|
||||||
background-color: @colorPrimaryBg;
|
border: none !important;
|
||||||
border: none;
|
background-color: rgba(93, 135, 255, 0.16) !important;
|
||||||
color: @colorPrimaryActive;
|
background-color: color-mix(
|
||||||
|
in srgb,
|
||||||
|
var(--xagi-color-primary, #5d87ff) 20%,
|
||||||
|
#fff
|
||||||
|
) !important;
|
||||||
|
color: var(--xagi-color-primary, #5d87ff) !important;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,12 +2,15 @@
|
|||||||
@import '@/styles/token';
|
@import '@/styles/token';
|
||||||
|
|
||||||
.container {
|
.container {
|
||||||
|
min-height: 100%;
|
||||||
|
background: var(--xagi-layout-bg-secondary, #f7f8fb);
|
||||||
|
|
||||||
.title {
|
.title {
|
||||||
line-height: 32px;
|
line-height: 60px;
|
||||||
font-size: @fontSizeLg;
|
font-size: 16px;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
color: @BLANK04;
|
color: var(--xagi-layout-text-primary, #1f2337);
|
||||||
margin-right: 20px;
|
margin: 0 20px 0 0;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 4px;
|
gap: 4px;
|
||||||
@@ -16,16 +19,22 @@
|
|||||||
.tips-icon {
|
.tips-icon {
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
transition: color 0.3s;
|
color: #8b92a6;
|
||||||
|
transition: color 0.24s ease;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
color: @BLANK04;
|
color: @colorPrimary;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon-back {
|
.icon-back {
|
||||||
color: @BLANK17;
|
color: #8b92a6;
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
|
transition: color 0.24s ease;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
color: @colorPrimary;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.header-area {
|
.header-area {
|
||||||
@@ -35,7 +44,12 @@
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
gap: 12px;
|
gap: 12px;
|
||||||
padding: 15px 15px 10px 24px;
|
min-height: 60px;
|
||||||
|
padding: 0 20px 0 24px;
|
||||||
|
border-bottom: 1px solid
|
||||||
|
var(--xagi-layout-border-secondary, rgba(91, 105, 135, 0.1));
|
||||||
|
background: var(--xagi-layout-bg-container, #fff);
|
||||||
|
box-shadow: 0 1px 0 rgba(32, 45, 84, 0.02);
|
||||||
|
|
||||||
.header-left {
|
.header-left {
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -80,9 +94,41 @@
|
|||||||
.content {
|
.content {
|
||||||
overflow-x: hidden;
|
overflow-x: hidden;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
height: 100%;
|
flex: 1;
|
||||||
padding: 0 16px 16px 24px;
|
min-height: 0;
|
||||||
background-color: transparent;
|
height: auto;
|
||||||
|
padding: 16px 18px 18px 24px;
|
||||||
|
background: var(--xagi-layout-bg-secondary, #f7f8fb);
|
||||||
|
|
||||||
|
:global {
|
||||||
|
.ant-card,
|
||||||
|
.ant-pro-card,
|
||||||
|
.ant-table-wrapper,
|
||||||
|
.ant-form,
|
||||||
|
.ant-pro-table {
|
||||||
|
border-radius: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-card,
|
||||||
|
.ant-pro-card,
|
||||||
|
.ant-table-wrapper .ant-table,
|
||||||
|
.ant-pro-table .ant-pro-table-search {
|
||||||
|
border: 1px solid
|
||||||
|
var(--xagi-layout-border-secondary, rgba(91, 105, 135, 0.1));
|
||||||
|
background: var(--xagi-layout-bg-container, #fff);
|
||||||
|
box-shadow: 0 8px 18px rgba(32, 45, 84, 0.04);
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-table-thead > tr > th {
|
||||||
|
background: #f4f7fb !important;
|
||||||
|
color: #5d6378 !important;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-btn-primary {
|
||||||
|
box-shadow: 0 8px 16px rgba(93, 135, 255, 0.18);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// hideScroll:内容区占满页头以下剩余高度,由页面内部自行分配滚动
|
// hideScroll:内容区占满页头以下剩余高度,由页面内部自行分配滚动
|
||||||
&.scroll-container-hide {
|
&.scroll-container-hide {
|
||||||
@@ -94,6 +140,57 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.extra-container {
|
.extra-container {
|
||||||
padding: 0 16px 16px 24px;
|
padding: 0 18px 18px 24px;
|
||||||
|
background: var(--xagi-layout-bg-secondary, #f7f8fb);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
:global(html[data-theme='dark']) {
|
||||||
|
.container {
|
||||||
|
background: var(--xagi-layout-bg-primary, #080a0f);
|
||||||
|
|
||||||
|
.tips-icon,
|
||||||
|
.icon-back {
|
||||||
|
color: var(--xagi-layout-text-tertiary, rgba(255, 255, 255, 0.65));
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-area {
|
||||||
|
border-bottom-color: var(
|
||||||
|
--xagi-layout-border-secondary,
|
||||||
|
rgba(255, 255, 255, 0.08)
|
||||||
|
);
|
||||||
|
background: var(--xagi-layout-bg-container, #111620);
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content {
|
||||||
|
background: var(--xagi-layout-bg-primary, #080a0f);
|
||||||
|
|
||||||
|
:global {
|
||||||
|
.ant-card,
|
||||||
|
.ant-pro-card,
|
||||||
|
.ant-table-wrapper .ant-table,
|
||||||
|
.ant-pro-table .ant-pro-table-search {
|
||||||
|
border-color: var(
|
||||||
|
--xagi-layout-border-secondary,
|
||||||
|
rgba(255, 255, 255, 0.08)
|
||||||
|
);
|
||||||
|
background: var(--xagi-layout-bg-container, #111620);
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-table-thead > tr > th {
|
||||||
|
background: rgba(255, 255, 255, 0.04) !important;
|
||||||
|
color: var(
|
||||||
|
--xagi-layout-text-secondary,
|
||||||
|
rgba(255, 255, 255, 0.85)
|
||||||
|
) !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.extra-container {
|
||||||
|
background: var(--xagi-layout-bg-primary, #080a0f);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,32 +2,44 @@
|
|||||||
@import '@/styles/token';
|
@import '@/styles/token';
|
||||||
|
|
||||||
.row {
|
.row {
|
||||||
padding: @paddingXs;
|
min-height: 42px;
|
||||||
|
padding: 0 12px;
|
||||||
cursor: default;
|
cursor: default;
|
||||||
color: @navSecondMenuTextSecondary;
|
color: var(
|
||||||
|
--xagi-layout-second-menu-text-color-secondary,
|
||||||
|
@navSecondMenuTextSecondary
|
||||||
|
);
|
||||||
|
border-radius: 6px;
|
||||||
|
transition: transform 0.24s ease, background 0.24s ease, color 0.24s ease,
|
||||||
|
box-shadow 0.24s ease;
|
||||||
|
|
||||||
&.menuItem {
|
&.menuItem {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
|
||||||
.name {
|
.name {
|
||||||
color: @navSecondMenuTextSecondary;
|
color: var(
|
||||||
font-size: @fontSize;
|
--xagi-layout-second-menu-text-color-secondary,
|
||||||
line-height: @fontHeightLg;
|
@navSecondMenuTextSecondary
|
||||||
|
);
|
||||||
|
font-size: 14px;
|
||||||
|
line-height: 42px;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.has-icon {
|
&.has-icon {
|
||||||
.name {
|
.name {
|
||||||
margin-left: @marginXs;
|
margin-left: 12px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&.active {
|
&.active {
|
||||||
background-color: @colorBgContainer;
|
background-color: var(--xagi-menu-item-active-bg, #fff);
|
||||||
color: @colorPrimary;
|
color: @colorPrimary;
|
||||||
border-radius: @borderRadius;
|
box-shadow: var(
|
||||||
box-shadow: @boxShadowTertiary;
|
--xagi-menu-item-active-shadow,
|
||||||
|
0 10px 20px rgba(32, 45, 84, 0.1)
|
||||||
|
);
|
||||||
|
|
||||||
.name {
|
.name {
|
||||||
color: @colorPrimary;
|
color: @colorPrimary;
|
||||||
@@ -35,13 +47,16 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
background-color: @colorBgContainer;
|
background-color: var(--xagi-menu-item-hover-bg, rgba(255, 255, 255, 0.72));
|
||||||
border-radius: @borderRadius;
|
box-shadow: var(
|
||||||
box-shadow: @boxShadowTertiary;
|
--xagi-menu-item-hover-shadow,
|
||||||
color: @colorTextSecondary;
|
0 8px 16px rgba(32, 45, 84, 0.06)
|
||||||
|
);
|
||||||
|
color: @colorPrimary;
|
||||||
|
transform: translateX(2px);
|
||||||
|
|
||||||
.name {
|
.name {
|
||||||
color: @colorTextSecondary;
|
color: @colorPrimary;
|
||||||
}
|
}
|
||||||
|
|
||||||
.collectIcon {
|
.collectIcon {
|
||||||
@@ -52,9 +67,13 @@
|
|||||||
|
|
||||||
.icon-box {
|
.icon-box {
|
||||||
font-size: 20px;
|
font-size: 20px;
|
||||||
border-radius: @borderRadiusSm;
|
border-radius: 6px;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
|
color: var(
|
||||||
|
--xagi-layout-second-menu-text-color-secondary,
|
||||||
|
@navSecondMenuTextSecondary
|
||||||
|
);
|
||||||
|
|
||||||
.icon-image {
|
.icon-image {
|
||||||
width: 20px;
|
width: 20px;
|
||||||
@@ -63,7 +82,10 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.collectIcon {
|
.collectIcon {
|
||||||
color: @navSecondMenuTextSecondary;
|
color: var(
|
||||||
|
--xagi-layout-second-menu-text-color-secondary,
|
||||||
|
@navSecondMenuTextSecondary
|
||||||
|
);
|
||||||
visibility: hidden;
|
visibility: hidden;
|
||||||
transition: background-color ease-in-out 0.2s;
|
transition: background-color ease-in-out 0.2s;
|
||||||
|
|
||||||
|
|||||||
@@ -2,13 +2,24 @@
|
|||||||
@import '@/styles/token';
|
@import '@/styles/token';
|
||||||
|
|
||||||
.row {
|
.row {
|
||||||
padding: @paddingXs;
|
min-height: 42px;
|
||||||
|
padding: 0 12px;
|
||||||
cursor: default;
|
cursor: default;
|
||||||
color: @navSecondMenuTextSecondary;
|
color: var(
|
||||||
|
--xagi-layout-second-menu-text-color-secondary,
|
||||||
|
@navSecondMenuTextSecondary
|
||||||
|
);
|
||||||
|
border-radius: 6px;
|
||||||
|
transition: transform 0.24s ease, background 0.24s ease, color 0.24s ease,
|
||||||
|
box-shadow 0.24s ease;
|
||||||
|
|
||||||
.name {
|
.name {
|
||||||
color: @navSecondMenuTextSecondary;
|
color: var(
|
||||||
font-size: @fontSize !important;
|
--xagi-layout-second-menu-text-color-secondary,
|
||||||
|
@navSecondMenuTextSecondary
|
||||||
|
);
|
||||||
|
font-size: 14px !important;
|
||||||
|
line-height: 42px;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -17,18 +28,24 @@
|
|||||||
|
|
||||||
:global {
|
:global {
|
||||||
.ant-typography {
|
.ant-typography {
|
||||||
margin-left: @marginXs;
|
color: var(
|
||||||
font-size: @fontSizeLg;
|
--xagi-layout-second-menu-text-color-secondary,
|
||||||
line-height: @fontHeightLg;
|
@navSecondMenuTextSecondary
|
||||||
|
);
|
||||||
|
margin-left: 12px;
|
||||||
|
font-size: 14px;
|
||||||
|
line-height: 42px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&.active {
|
&.active {
|
||||||
background-color: @colorBgContainer;
|
background-color: var(--xagi-menu-item-active-bg, #fff);
|
||||||
color: @colorPrimary;
|
color: @colorPrimary;
|
||||||
border-radius: @borderRadius;
|
box-shadow: var(
|
||||||
box-shadow: @boxShadowTertiary;
|
--xagi-menu-item-active-shadow,
|
||||||
|
0 10px 20px rgba(32, 45, 84, 0.1)
|
||||||
|
);
|
||||||
|
|
||||||
:global {
|
:global {
|
||||||
.ant-typography {
|
.ant-typography {
|
||||||
@@ -37,19 +54,22 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.icon-dropdown {
|
.icon-dropdown {
|
||||||
color: @colorTextSecondary;
|
color: var(--xagi-layout-text-tertiary, @colorTextSecondary);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
background-color: @colorBgContainer;
|
background-color: var(--xagi-menu-item-hover-bg, rgba(255, 255, 255, 0.72));
|
||||||
border-radius: @borderRadius;
|
box-shadow: var(
|
||||||
box-shadow: @boxShadowTertiary;
|
--xagi-menu-item-hover-shadow,
|
||||||
color: @colorTextSecondary;
|
0 8px 16px rgba(32, 45, 84, 0.06)
|
||||||
|
);
|
||||||
|
color: @colorPrimary;
|
||||||
|
transform: translateX(2px);
|
||||||
|
|
||||||
:global {
|
:global {
|
||||||
.ant-typography {
|
.ant-typography {
|
||||||
color: @colorTextSecondary;
|
color: @colorPrimary;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -58,7 +78,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.icon-dropdown {
|
.icon-dropdown {
|
||||||
color: @colorTextSecondary;
|
color: var(--xagi-layout-text-tertiary, @colorTextSecondary);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -68,9 +88,13 @@
|
|||||||
|
|
||||||
.icon-box {
|
.icon-box {
|
||||||
font-size: 20px;
|
font-size: 20px;
|
||||||
border-radius: @borderRadiusSm;
|
border-radius: 6px;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
|
color: var(
|
||||||
|
--xagi-layout-second-menu-text-color-secondary,
|
||||||
|
@navSecondMenuTextSecondary
|
||||||
|
);
|
||||||
|
|
||||||
.icon-image {
|
.icon-image {
|
||||||
width: 20px;
|
width: 20px;
|
||||||
@@ -80,7 +104,11 @@
|
|||||||
|
|
||||||
.icon-dropdown {
|
.icon-dropdown {
|
||||||
font-size: @fontSizeSm;
|
font-size: @fontSizeSm;
|
||||||
color: @navSecondMenuTextSecondary;
|
color: var(
|
||||||
|
--xagi-layout-second-menu-text-color-secondary,
|
||||||
|
@navSecondMenuTextSecondary
|
||||||
|
);
|
||||||
|
transition: transform 0.24s ease, color 0.24s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.open {
|
&.open {
|
||||||
@@ -90,7 +118,10 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.collectIcon {
|
.collectIcon {
|
||||||
color: @colorTextSecondary;
|
color: var(
|
||||||
|
--xagi-layout-second-menu-text-color-secondary,
|
||||||
|
@colorTextSecondary
|
||||||
|
);
|
||||||
visibility: hidden;
|
visibility: hidden;
|
||||||
transition: background-color ease-in-out 0.2s;
|
transition: background-color ease-in-out 0.2s;
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,28 @@
|
|||||||
// 仅选择器(rootClassName 挂在这层):默认宽度;外部传入的 className 也合并在此,只影响触发区域
|
// 仅选择器(rootClassName 挂在这层):默认宽度;外部传入的 className 也合并在此,只影响触发区域
|
||||||
.container {
|
.container {
|
||||||
width: 120px;
|
width: 120px;
|
||||||
|
|
||||||
|
:global(.ant-select-selector) {
|
||||||
|
border-color: var(
|
||||||
|
--xagi-layout-border-primary,
|
||||||
|
rgba(91, 105, 135, 0.14)
|
||||||
|
) !important;
|
||||||
|
background: var(
|
||||||
|
--xagi-layout-bg-input,
|
||||||
|
rgba(255, 255, 255, 0.88)
|
||||||
|
) !important;
|
||||||
|
color: var(--xagi-layout-text-primary, #1f2337) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
:global(.ant-select-selection-item) {
|
||||||
|
color: var(--xagi-layout-text-primary, #1f2337) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
:global(.ant-select-selection-placeholder),
|
||||||
|
:global(.ant-select-arrow),
|
||||||
|
:global(.ant-select-clear) {
|
||||||
|
color: var(--xagi-layout-text-tertiary, rgba(31, 35, 55, 0.52)) !important;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 以下类名用在 portal 内的 optionRender 上,不能嵌套在 .container 下否则选择器匹配不到
|
// 以下类名用在 portal 内的 optionRender 上,不能嵌套在 .container 下否则选择器匹配不到
|
||||||
@@ -64,10 +86,38 @@
|
|||||||
// 解绑选择器宽度:rc-select 在 popupMatchSelectWidth={false} 时仍会用 rc-trigger 把下拉的 min-width 对齐到选择器;
|
// 解绑选择器宽度:rc-select 在 popupMatchSelectWidth={false} 时仍会用 rc-trigger 把下拉的 min-width 对齐到选择器;
|
||||||
// 用 max-content 覆盖,使下拉最小宽度由选项内容决定,而不随 styles.container / className 的宽度变化
|
// 用 max-content 覆盖,使下拉最小宽度由选项内容决定,而不随 styles.container / className 的宽度变化
|
||||||
min-width: max-content !important;
|
min-width: max-content !important;
|
||||||
|
background: var(--xagi-layout-bg-container, #fff) !important;
|
||||||
|
|
||||||
:global(.ant-select-item-option-content) {
|
:global(.ant-select-item-option-content) {
|
||||||
// 避免 flex 默认 min-width:auto 卡住子项宽度测量
|
// 避免 flex 默认 min-width:auto 卡住子项宽度测量
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
width: max-content;
|
width: max-content;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
:global(.ant-select-item) {
|
||||||
|
color: var(--xagi-layout-text-primary, #1f2337) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
:global(
|
||||||
|
.ant-select-item-option-active:not(.ant-select-item-option-disabled)
|
||||||
|
) {
|
||||||
|
background: rgba(93, 135, 255, 0.08) !important;
|
||||||
|
background: color-mix(
|
||||||
|
in srgb,
|
||||||
|
var(--xagi-color-primary, #5d87ff) 8%,
|
||||||
|
#fff
|
||||||
|
) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
:global(
|
||||||
|
.ant-select-item-option-selected:not(.ant-select-item-option-disabled)
|
||||||
|
) {
|
||||||
|
background: rgba(93, 135, 255, 0.12) !important;
|
||||||
|
background: color-mix(
|
||||||
|
in srgb,
|
||||||
|
var(--xagi-color-primary, #5d87ff) 14%,
|
||||||
|
#fff
|
||||||
|
) !important;
|
||||||
|
color: var(--xagi-color-primary, #5d87ff) !important;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,12 +8,12 @@ import { ThemeNavigationStyleType } from '@/types/enums/theme';
|
|||||||
export const NAVIGATION_LAYOUT_SIZES = {
|
export const NAVIGATION_LAYOUT_SIZES = {
|
||||||
// 一级菜单宽度配置
|
// 一级菜单宽度配置
|
||||||
FIRST_MENU_WIDTH: {
|
FIRST_MENU_WIDTH: {
|
||||||
STYLE1: 60, // 紧凑模式:无文字导航
|
STYLE1: 88, // Art 风格:窄图标栏
|
||||||
STYLE2: 80, // 展开模式:有文字导航
|
STYLE2: 112, // Art 风格:图标+文字栏
|
||||||
},
|
},
|
||||||
|
|
||||||
// 二级菜单宽度配置
|
// 二级菜单宽度配置
|
||||||
SECOND_MENU_WIDTH: 240,
|
SECOND_MENU_WIDTH: 230,
|
||||||
|
|
||||||
// 菜单总宽度计算
|
// 菜单总宽度计算
|
||||||
getTotalMenuWidth: (navigationStyle: string) => {
|
getTotalMenuWidth: (navigationStyle: string) => {
|
||||||
@@ -26,13 +26,13 @@ export const NAVIGATION_LAYOUT_SIZES = {
|
|||||||
|
|
||||||
// 页面容器边距配置
|
// 页面容器边距配置
|
||||||
PAGE_CONTAINER_MARGIN: {
|
PAGE_CONTAINER_MARGIN: {
|
||||||
STYLE1: 16, // 紧凑模式:有外边距
|
STYLE1: 0, // Art 风格:内容区贴合主布局
|
||||||
STYLE2: 0, // 展开模式:无外边距
|
STYLE2: 0, // 展开模式:无外边距
|
||||||
},
|
},
|
||||||
|
|
||||||
// 页面容器圆角配置
|
// 页面容器圆角配置
|
||||||
PAGE_CONTAINER_BORDER_RADIUS: {
|
PAGE_CONTAINER_BORDER_RADIUS: {
|
||||||
STYLE1: 12, // 紧凑模式:有圆角
|
STYLE1: 0, // Art 风格:由内容卡片控制圆角
|
||||||
STYLE2: 0, // 展开模式:无圆角
|
STYLE2: 0, // 展开模式:无圆角
|
||||||
},
|
},
|
||||||
} as const;
|
} as const;
|
||||||
@@ -75,7 +75,7 @@ export const getTotalMenuWidth = (
|
|||||||
// 常量定义
|
// 常量定义
|
||||||
export const MOBILE_BREAKPOINT = 768; // 移动端断点
|
export const MOBILE_BREAKPOINT = 768; // 移动端断点
|
||||||
export const ANIMATION_DURATION = 300; // 动画持续时间
|
export const ANIMATION_DURATION = 300; // 动画持续时间
|
||||||
export const MOBILE_MENU_TOP_PADDING = 32; // 移动端菜单顶部间距
|
export const MOBILE_MENU_TOP_PADDING = 20; // 移动端菜单顶部间距
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 通用表格全局样式:主要用于弹窗中 Table 的间距边距
|
* 通用表格全局样式:主要用于弹窗中 Table 的间距边距
|
||||||
|
|||||||
@@ -4,7 +4,13 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { dict } from '@/services/i18nRuntime';
|
import { dict } from '@/services/i18nRuntime';
|
||||||
import { ThemeLayoutColorStyle } from '@/types/enums/theme';
|
import {
|
||||||
|
ThemeLayoutColorStyle,
|
||||||
|
ThemeMenuLayoutType,
|
||||||
|
ThemeMenuStyleType,
|
||||||
|
ThemePageTransitionType,
|
||||||
|
ThemeTabStyleType,
|
||||||
|
} from '@/types/enums/theme';
|
||||||
import {
|
import {
|
||||||
ThemeBackgroundConfig,
|
ThemeBackgroundConfig,
|
||||||
ThemeStyleConfig,
|
ThemeStyleConfig,
|
||||||
@@ -21,19 +27,17 @@ import { FIRST_MENU_WIDTH_STYLE2 } from './layout.constants';
|
|||||||
*/
|
*/
|
||||||
export const THEME_COLOR_CONFIGS = [
|
export const THEME_COLOR_CONFIGS = [
|
||||||
{
|
{
|
||||||
color: '#006569',
|
color: '#5D87FF',
|
||||||
name: '国网绿',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
color: '#5147ff',
|
|
||||||
name: dict('PC.Constants.Theme.colorBlue'),
|
name: dict('PC.Constants.Theme.colorBlue'),
|
||||||
isDefault: true,
|
isDefault: true,
|
||||||
},
|
},
|
||||||
|
{ color: '#B48DF3', name: dict('PC.Constants.Theme.colorPurple') },
|
||||||
|
{ color: '#1D84FF', name: dict('PC.Constants.Theme.colorBlue') },
|
||||||
|
{ color: '#60C041', name: dict('PC.Constants.Theme.colorGreen') },
|
||||||
|
{ color: '#38C0FC', name: dict('PC.Constants.Theme.colorBlue') },
|
||||||
|
{ color: '#F9901F', name: dict('PC.Constants.Theme.colorOrange') },
|
||||||
|
{ color: '#FF80C8', name: dict('PC.Constants.Theme.colorPink') },
|
||||||
{ color: '#ff4d4f', name: dict('PC.Constants.Theme.colorRed') },
|
{ color: '#ff4d4f', name: dict('PC.Constants.Theme.colorRed') },
|
||||||
{ color: '#fa8c16', name: dict('PC.Constants.Theme.colorOrange') },
|
|
||||||
{ color: '#52c41a', name: dict('PC.Constants.Theme.colorGreen') },
|
|
||||||
{ color: '#722ed1', name: dict('PC.Constants.Theme.colorPurple') },
|
|
||||||
{ color: '#eb2f96', name: dict('PC.Constants.Theme.colorPink') },
|
|
||||||
] as const;
|
] as const;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -59,12 +63,30 @@ export const STORAGE_KEYS = {
|
|||||||
* 统一管理所有默认值,确保各模块间的一致性
|
* 统一管理所有默认值,确保各模块间的一致性
|
||||||
*/
|
*/
|
||||||
export const DEFAULT_THEME_CONFIG = {
|
export const DEFAULT_THEME_CONFIG = {
|
||||||
PRIMARY_COLOR: '#5147ff',
|
PRIMARY_COLOR: '#5D87FF',
|
||||||
BACKGROUND_ID: '',
|
BACKGROUND_ID: '',
|
||||||
NAVIGATION_STYLE: 'style1',
|
NAVIGATION_STYLE: 'style1',
|
||||||
|
MENU_LAYOUT: ThemeMenuLayoutType.DUAL,
|
||||||
|
MENU_STYLE: ThemeMenuStyleType.DESIGN,
|
||||||
LAYOUT_STYLE: 'light',
|
LAYOUT_STYLE: 'light',
|
||||||
THEME: 'light',
|
THEME: 'light',
|
||||||
LANGUAGE: 'zh-CN',
|
LANGUAGE: 'zh-CN',
|
||||||
|
BASIC_CONFIG: {
|
||||||
|
showWorkTab: true,
|
||||||
|
uniqueOpened: true,
|
||||||
|
showMenuButton: true,
|
||||||
|
showFastEnter: true,
|
||||||
|
showRefreshButton: true,
|
||||||
|
showCrumbs: true,
|
||||||
|
showLanguage: true,
|
||||||
|
showNprogress: false,
|
||||||
|
colorWeak: false,
|
||||||
|
watermarkVisible: false,
|
||||||
|
menuOpenWidth: 180,
|
||||||
|
tabStyle: ThemeTabStyleType.DEFAULT,
|
||||||
|
pageTransition: ThemePageTransitionType.NONE,
|
||||||
|
customRadius: '0.75',
|
||||||
|
},
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -139,25 +161,25 @@ export const STYLE_CONFIGS: Record<string, ThemeStyleConfig> = {
|
|||||||
'light-style1': {
|
'light-style1': {
|
||||||
layout: {
|
layout: {
|
||||||
'--xagi-layout-text-primary': '#000000',
|
'--xagi-layout-text-primary': '#000000',
|
||||||
'--xagi-layout-text-secondary': 'rgba(0, 0, 0, 0.65)',
|
'--xagi-layout-text-secondary': 'rgba(31, 35, 55, 0.72)',
|
||||||
'--xagi-layout-text-tertiary': 'rgba(0, 0, 0, 0.65)',
|
'--xagi-layout-text-tertiary': 'rgba(31, 35, 55, 0.52)',
|
||||||
'--xagi-layout-text-disabled': 'rgba(0, 0, 0, 0.25)',
|
'--xagi-layout-text-disabled': 'rgba(0, 0, 0, 0.25)',
|
||||||
'--xagi-layout-second-menu-text-color': 'rgba(0, 0, 0, 0.88)',
|
'--xagi-layout-second-menu-text-color': '#1f2337',
|
||||||
'--xagi-layout-second-menu-text-color-secondary': 'rgba(0, 0, 0, 0.65)',
|
'--xagi-layout-second-menu-text-color-secondary': '#5d6378',
|
||||||
'--xagi-layout-bg-primary': 'rgba(255, 255, 255, 0.95)',
|
'--xagi-layout-bg-primary': '#f4f7fb',
|
||||||
'--xagi-layout-bg-secondary': 'rgba(255, 255, 255, 0.85)',
|
'--xagi-layout-bg-secondary': '#f2f5fb',
|
||||||
'--xagi-layout-bg-card': 'rgba(255, 255, 255, 0.65)',
|
'--xagi-layout-bg-card': 'rgba(255, 255, 255, 0.92)',
|
||||||
'--xagi-layout-bg-input': 'rgba(255, 255, 255, 0.45)',
|
'--xagi-layout-bg-input': 'rgba(255, 255, 255, 0.88)',
|
||||||
'--xagi-layout-border-primary': 'rgba(0, 0, 0, 0.15)',
|
'--xagi-layout-border-primary': 'rgba(91, 105, 135, 0.14)',
|
||||||
'--xagi-layout-border-secondary': 'rgba(0, 0, 0, 0.1)',
|
'--xagi-layout-border-secondary': 'rgba(91, 105, 135, 0.1)',
|
||||||
'--xagi-layout-shadow': 'rgba(0, 0, 0, 0.1)',
|
'--xagi-layout-shadow': '0 8px 20px rgba(32, 45, 84, 0.08)',
|
||||||
'--xagi-layout-overlay': 'rgba(255, 255, 255, 0.7)',
|
'--xagi-layout-overlay': 'rgba(255, 255, 255, 0.78)',
|
||||||
'--xagi-layout-bg-container': 'rgba(255, 255, 255, 0.95)',
|
'--xagi-layout-bg-container': '#ffffff',
|
||||||
},
|
},
|
||||||
navigation: {
|
navigation: {
|
||||||
'--xagi-nav-first-menu-width': '60px',
|
'--xagi-nav-first-menu-width': '88px',
|
||||||
'--xagi-page-container-margin': '12px',
|
'--xagi-page-container-margin': '0',
|
||||||
'--xagi-page-container-border-radius': '12px',
|
'--xagi-page-container-border-radius': '0',
|
||||||
'--xagi-page-container-border-color': 'transparent',
|
'--xagi-page-container-border-color': 'transparent',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -165,20 +187,20 @@ export const STYLE_CONFIGS: Record<string, ThemeStyleConfig> = {
|
|||||||
'light-style2': {
|
'light-style2': {
|
||||||
layout: {
|
layout: {
|
||||||
'--xagi-layout-text-primary': '#000000',
|
'--xagi-layout-text-primary': '#000000',
|
||||||
'--xagi-layout-text-secondary': 'rgba(0, 0, 0, 0.65)',
|
'--xagi-layout-text-secondary': 'rgba(31, 35, 55, 0.72)',
|
||||||
'--xagi-layout-text-tertiary': 'rgba(0, 0, 0, 0.65)',
|
'--xagi-layout-text-tertiary': 'rgba(31, 35, 55, 0.52)',
|
||||||
'--xagi-layout-text-disabled': 'rgba(0, 0, 0, 0.25)',
|
'--xagi-layout-text-disabled': 'rgba(0, 0, 0, 0.25)',
|
||||||
'--xagi-layout-second-menu-text-color': 'rgba(0, 0, 0, 0.88)',
|
'--xagi-layout-second-menu-text-color': '#1f2337',
|
||||||
'--xagi-layout-second-menu-text-color-secondary': 'rgba(0, 0, 0, 0.65)',
|
'--xagi-layout-second-menu-text-color-secondary': '#5d6378',
|
||||||
'--xagi-layout-bg-primary': 'rgba(255, 255, 255, 0.95)',
|
'--xagi-layout-bg-primary': '#f4f7fb',
|
||||||
'--xagi-layout-bg-secondary': 'rgba(255, 255, 255, 0.85)',
|
'--xagi-layout-bg-secondary': '#f2f5fb',
|
||||||
'--xagi-layout-bg-card': 'rgba(255, 255, 255, 0.65)',
|
'--xagi-layout-bg-card': 'rgba(255, 255, 255, 0.92)',
|
||||||
'--xagi-layout-bg-input': 'rgba(255, 255, 255, 0.45)',
|
'--xagi-layout-bg-input': 'rgba(255, 255, 255, 0.88)',
|
||||||
'--xagi-layout-border-primary': 'rgba(0, 0, 0, 0.15)',
|
'--xagi-layout-border-primary': 'rgba(91, 105, 135, 0.14)',
|
||||||
'--xagi-layout-border-secondary': 'rgba(0, 0, 0, 0.1)',
|
'--xagi-layout-border-secondary': 'rgba(91, 105, 135, 0.1)',
|
||||||
'--xagi-layout-shadow': 'rgba(0, 0, 0, 0.1)',
|
'--xagi-layout-shadow': '0 8px 20px rgba(32, 45, 84, 0.08)',
|
||||||
'--xagi-layout-overlay': 'rgba(255, 255, 255, 0.7)',
|
'--xagi-layout-overlay': 'rgba(255, 255, 255, 0.78)',
|
||||||
'--xagi-layout-bg-container': 'rgba(255, 255, 255, 0.95)',
|
'--xagi-layout-bg-container': '#ffffff',
|
||||||
},
|
},
|
||||||
navigation: {
|
navigation: {
|
||||||
'--xagi-nav-first-menu-width': `${FIRST_MENU_WIDTH_STYLE2}px`,
|
'--xagi-nav-first-menu-width': `${FIRST_MENU_WIDTH_STYLE2}px`,
|
||||||
@@ -197,20 +219,20 @@ export const STYLE_CONFIGS: Record<string, ThemeStyleConfig> = {
|
|||||||
'--xagi-layout-second-menu-text-color': 'rgba(255, 255, 255, 1)',
|
'--xagi-layout-second-menu-text-color': 'rgba(255, 255, 255, 1)',
|
||||||
'--xagi-layout-second-menu-text-color-secondary':
|
'--xagi-layout-second-menu-text-color-secondary':
|
||||||
'rgba(255, 255, 255, 0.8)',
|
'rgba(255, 255, 255, 0.8)',
|
||||||
'--xagi-layout-bg-primary': 'rgba(0, 0, 0, 0.85)',
|
'--xagi-layout-bg-primary': '#080a0f',
|
||||||
'--xagi-layout-bg-secondary': 'rgba(0, 0, 0, 0.65)',
|
'--xagi-layout-bg-secondary': '#0d1117',
|
||||||
'--xagi-layout-bg-card': 'rgba(0, 0, 0, 0.45)',
|
'--xagi-layout-bg-card': 'rgba(20, 24, 33, 0.9)',
|
||||||
'--xagi-layout-bg-input': 'rgba(0, 0, 0, 0.25)',
|
'--xagi-layout-bg-input': 'rgba(255, 255, 255, 0.06)',
|
||||||
'--xagi-layout-border-primary': 'rgba(255, 255, 255, 0.12)',
|
'--xagi-layout-border-primary': 'rgba(255, 255, 255, 0.12)',
|
||||||
'--xagi-layout-border-secondary': 'rgba(255, 255, 255, 0.08)',
|
'--xagi-layout-border-secondary': 'rgba(255, 255, 255, 0.08)',
|
||||||
'--xagi-layout-shadow': 'rgba(0, 0, 0, 0.6)',
|
'--xagi-layout-shadow': '0 12px 32px rgba(0, 0, 0, 0.45)',
|
||||||
'--xagi-layout-overlay': 'rgba(0, 0, 0, 0.7)',
|
'--xagi-layout-overlay': 'rgba(0, 0, 0, 0.7)',
|
||||||
'--xagi-layout-bg-container': '#ffffff',
|
'--xagi-layout-bg-container': '#111620',
|
||||||
},
|
},
|
||||||
navigation: {
|
navigation: {
|
||||||
'--xagi-nav-first-menu-width': '60px',
|
'--xagi-nav-first-menu-width': '88px',
|
||||||
'--xagi-page-container-margin': '12px',
|
'--xagi-page-container-margin': '0',
|
||||||
'--xagi-page-container-border-radius': '12px',
|
'--xagi-page-container-border-radius': '0',
|
||||||
'--xagi-page-container-border-color': 'transparent',
|
'--xagi-page-container-border-color': 'transparent',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -221,17 +243,18 @@ export const STYLE_CONFIGS: Record<string, ThemeStyleConfig> = {
|
|||||||
'--xagi-layout-text-secondary': 'rgba(255, 255, 255, 0.85)',
|
'--xagi-layout-text-secondary': 'rgba(255, 255, 255, 0.85)',
|
||||||
'--xagi-layout-text-tertiary': 'rgba(255, 255, 255, 0.65)',
|
'--xagi-layout-text-tertiary': 'rgba(255, 255, 255, 0.65)',
|
||||||
'--xagi-layout-text-disabled': 'rgba(255, 255, 255, 0.25)',
|
'--xagi-layout-text-disabled': 'rgba(255, 255, 255, 0.25)',
|
||||||
'--xagi-layout-second-menu-text-color': 'rgba(0, 0, 0, 0.88)',
|
'--xagi-layout-second-menu-text-color': 'rgba(255, 255, 255, 0.95)',
|
||||||
'--xagi-layout-second-menu-text-color-secondary': 'rgba(0, 0, 0, 0.65)',
|
'--xagi-layout-second-menu-text-color-secondary':
|
||||||
'--xagi-layout-bg-primary': 'rgba(0, 0, 0, 0.85)',
|
'rgba(255, 255, 255, 0.78)',
|
||||||
'--xagi-layout-bg-secondary': 'rgba(0, 0, 0, 0.85)',
|
'--xagi-layout-bg-primary': '#080a0f',
|
||||||
'--xagi-layout-bg-card': 'rgba(0, 0, 0, 0.45)',
|
'--xagi-layout-bg-secondary': '#0d1117',
|
||||||
'--xagi-layout-bg-input': 'rgba(0, 0, 0, 0.25)',
|
'--xagi-layout-bg-card': 'rgba(20, 24, 33, 0.9)',
|
||||||
|
'--xagi-layout-bg-input': 'rgba(255, 255, 255, 0.06)',
|
||||||
'--xagi-layout-border-primary': 'rgba(255, 255, 255, 0.12)',
|
'--xagi-layout-border-primary': 'rgba(255, 255, 255, 0.12)',
|
||||||
'--xagi-layout-border-secondary': 'rgba(255, 255, 255, 0.08)',
|
'--xagi-layout-border-secondary': 'rgba(255, 255, 255, 0.08)',
|
||||||
'--xagi-layout-shadow': 'rgba(0, 0, 0, 0.6)',
|
'--xagi-layout-shadow': '0 12px 32px rgba(0, 0, 0, 0.45)',
|
||||||
'--xagi-layout-overlay': 'rgba(0, 0, 0, 0.7)',
|
'--xagi-layout-overlay': 'rgba(0, 0, 0, 0.7)',
|
||||||
'--xagi-layout-bg-container': '#ffffff',
|
'--xagi-layout-bg-container': '#111620',
|
||||||
},
|
},
|
||||||
navigation: {
|
navigation: {
|
||||||
'--xagi-nav-first-menu-width': `${FIRST_MENU_WIDTH_STYLE2}px`,
|
'--xagi-nav-first-menu-width': `${FIRST_MENU_WIDTH_STYLE2}px`,
|
||||||
@@ -268,7 +291,7 @@ export type StyleConfigKey = keyof typeof STYLE_CONFIGS;
|
|||||||
*/
|
*/
|
||||||
export const themeTokens: Partial<AliasToken> = {
|
export const themeTokens: Partial<AliasToken> = {
|
||||||
// 品牌主色 - 项目主色调
|
// 品牌主色 - 项目主色调
|
||||||
colorPrimary: '#5147ff',
|
colorPrimary: '#5D87FF',
|
||||||
|
|
||||||
// 功能色
|
// 功能色
|
||||||
colorSuccess: '#3bb346',
|
colorSuccess: '#3bb346',
|
||||||
@@ -281,7 +304,7 @@ export const themeTokens: Partial<AliasToken> = {
|
|||||||
colorBgBase: '#ffffff',
|
colorBgBase: '#ffffff',
|
||||||
|
|
||||||
// 超链接颜色
|
// 超链接颜色
|
||||||
colorLink: '#5147ff',
|
colorLink: '#5D87FF',
|
||||||
|
|
||||||
// 字体配置
|
// 字体配置
|
||||||
fontFamily:
|
fontFamily:
|
||||||
@@ -332,12 +355,12 @@ export const themeTokens: Partial<AliasToken> = {
|
|||||||
motion: true,
|
motion: true,
|
||||||
|
|
||||||
// 预设颜色
|
// 预设颜色
|
||||||
blue: '#1890ff',
|
blue: '#5D87FF',
|
||||||
purple: '#722ed1',
|
purple: '#B48DF3',
|
||||||
cyan: '#13c2c2',
|
cyan: '#38C0FC',
|
||||||
green: '#52c41a',
|
green: '#60C041',
|
||||||
magenta: '#eb2f96',
|
magenta: '#FF80C8',
|
||||||
pink: '#eb2f96',
|
pink: '#FF80C8',
|
||||||
red: '#f5222d',
|
red: '#f5222d',
|
||||||
orange: '#fa8c16',
|
orange: '#fa8c16',
|
||||||
yellow: '#fadb14',
|
yellow: '#fadb14',
|
||||||
@@ -357,8 +380,13 @@ export const themeTokens: Partial<AliasToken> = {
|
|||||||
|
|
||||||
export const darkThemeTokens = {
|
export const darkThemeTokens = {
|
||||||
...themeTokens,
|
...themeTokens,
|
||||||
colorBgBase: '#000',
|
colorBgBase: '#080a0f',
|
||||||
colorTextBase: '#fff',
|
colorTextBase: '#fff',
|
||||||
|
colorBgLayout: '#080a0f',
|
||||||
|
colorBgContainer: '#111620',
|
||||||
|
colorBgElevated: '#171c26',
|
||||||
|
colorBorder: 'rgba(255, 255, 255, 0.12)',
|
||||||
|
colorBorderSecondary: 'rgba(255, 255, 255, 0.08)',
|
||||||
// TODO 填充颜色 深色 缺少
|
// TODO 填充颜色 深色 缺少
|
||||||
|
|
||||||
// 导航深色主题适配
|
// 导航深色主题适配
|
||||||
@@ -413,7 +441,7 @@ export const componentThemes: ThemeConfig['components'] = {
|
|||||||
paddingBlock: 4,
|
paddingBlock: 4,
|
||||||
activeBorderColor: themeTokens.colorPrimary,
|
activeBorderColor: themeTokens.colorPrimary,
|
||||||
hoverBorderColor: '#7B6EFF',
|
hoverBorderColor: '#7B6EFF',
|
||||||
activeShadow: '0 0 0 2px rgba(81, 71, 255, 0.2)',
|
activeShadow: '0 0 0 2px rgba(93, 135, 255, 0.18)',
|
||||||
errorActiveShadow: '0 0 0 2px rgba(255, 77, 79, 0.2)',
|
errorActiveShadow: '0 0 0 2px rgba(255, 77, 79, 0.2)',
|
||||||
warningActiveShadow: '0 0 0 2px rgba(255, 140, 0, 0.2)',
|
warningActiveShadow: '0 0 0 2px rgba(255, 140, 0, 0.2)',
|
||||||
} as InputToken,
|
} as InputToken,
|
||||||
@@ -424,8 +452,8 @@ export const componentThemes: ThemeConfig['components'] = {
|
|||||||
borderRadius: themeTokens.borderRadius,
|
borderRadius: themeTokens.borderRadius,
|
||||||
controlHeight: themeTokens.controlHeight,
|
controlHeight: themeTokens.controlHeight,
|
||||||
fontSize: themeTokens.fontSize,
|
fontSize: themeTokens.fontSize,
|
||||||
optionSelectedBg: 'rgba(81, 71, 255, 0.1)',
|
optionSelectedBg: 'rgba(93, 135, 255, 0.12)',
|
||||||
optionActiveBg: 'rgba(81, 71, 255, 0.05)',
|
optionActiveBg: 'rgba(93, 135, 255, 0.07)',
|
||||||
optionSelectedColor: themeTokens.colorPrimary,
|
optionSelectedColor: themeTokens.colorPrimary,
|
||||||
optionPadding: '5px 12px',
|
optionPadding: '5px 12px',
|
||||||
showArrowPaddingInlineEnd: 18,
|
showArrowPaddingInlineEnd: 18,
|
||||||
@@ -440,9 +468,9 @@ export const componentThemes: ThemeConfig['components'] = {
|
|||||||
headerSortActiveBg: '#f0f0f0',
|
headerSortActiveBg: '#f0f0f0',
|
||||||
headerSortHoverBg: '#f5f5f5',
|
headerSortHoverBg: '#f5f5f5',
|
||||||
bodySortBg: '#fafafa',
|
bodySortBg: '#fafafa',
|
||||||
rowHoverBg: 'rgba(81, 71, 255, 0.03)',
|
rowHoverBg: 'rgba(93, 135, 255, 0.04)',
|
||||||
rowSelectedBg: 'rgba(81, 71, 255, 0.05)',
|
rowSelectedBg: 'rgba(93, 135, 255, 0.08)',
|
||||||
rowSelectedHoverBg: 'rgba(81, 71, 255, 0.08)',
|
rowSelectedHoverBg: 'rgba(93, 135, 255, 0.1)',
|
||||||
rowExpandedBg: '#fbfbfb',
|
rowExpandedBg: '#fbfbfb',
|
||||||
cellPaddingBlock: 16,
|
cellPaddingBlock: 16,
|
||||||
cellPaddingInline: 16,
|
cellPaddingInline: 16,
|
||||||
@@ -505,8 +533,8 @@ export const componentThemes: ThemeConfig['components'] = {
|
|||||||
borderRadius: themeTokens.borderRadius,
|
borderRadius: themeTokens.borderRadius,
|
||||||
controlHeight: themeTokens.controlHeight,
|
controlHeight: themeTokens.controlHeight,
|
||||||
fontSize: themeTokens.fontSize,
|
fontSize: themeTokens.fontSize,
|
||||||
cellActiveWithRangeBg: 'rgba(81, 71, 255, 0.1)',
|
cellActiveWithRangeBg: 'rgba(93, 135, 255, 0.12)',
|
||||||
cellHoverWithRangeBg: 'rgba(81, 71, 255, 0.05)',
|
cellHoverWithRangeBg: 'rgba(93, 135, 255, 0.07)',
|
||||||
cellRangeBorderColor: 'transparent',
|
cellRangeBorderColor: 'transparent',
|
||||||
cellBgDisabled: '#f5f5f5',
|
cellBgDisabled: '#f5f5f5',
|
||||||
timeColumnWidth: 56,
|
timeColumnWidth: 56,
|
||||||
@@ -534,9 +562,9 @@ export const componentThemes: ThemeConfig['components'] = {
|
|||||||
itemColor: '#000000d9',
|
itemColor: '#000000d9',
|
||||||
itemHoverBg: 'rgba(0, 0, 0, 0.06)',
|
itemHoverBg: 'rgba(0, 0, 0, 0.06)',
|
||||||
itemHoverColor: '#000000d9',
|
itemHoverColor: '#000000d9',
|
||||||
itemSelectedBg: 'rgba(81, 71, 255, 0.1)',
|
itemSelectedBg: 'rgba(93, 135, 255, 0.12)',
|
||||||
itemSelectedColor: themeTokens.colorPrimary,
|
itemSelectedColor: themeTokens.colorPrimary,
|
||||||
itemActiveBg: 'rgba(81, 71, 255, 0.15)',
|
itemActiveBg: 'rgba(93, 135, 255, 0.16)',
|
||||||
subMenuItemBg: 'transparent',
|
subMenuItemBg: 'transparent',
|
||||||
itemMarginBlock: 4,
|
itemMarginBlock: 4,
|
||||||
itemMarginInline: 4,
|
itemMarginInline: 4,
|
||||||
@@ -558,6 +586,125 @@ export const componentThemes: ThemeConfig['components'] = {
|
|||||||
} as SegmentedToken,
|
} as SegmentedToken,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const getComponentThemes = ({
|
||||||
|
darkMode = false,
|
||||||
|
primaryColor = themeTokens.colorPrimary,
|
||||||
|
}: {
|
||||||
|
darkMode?: boolean;
|
||||||
|
primaryColor?: string;
|
||||||
|
} = {}): ThemeConfig['components'] => {
|
||||||
|
const primary = primaryColor || themeTokens.colorPrimary;
|
||||||
|
const primarySelectedBg = 'rgba(93, 135, 255, 0.12)';
|
||||||
|
const primaryActiveBg = 'rgba(93, 135, 255, 0.16)';
|
||||||
|
|
||||||
|
if (!darkMode) {
|
||||||
|
return {
|
||||||
|
...componentThemes,
|
||||||
|
Button: {
|
||||||
|
...(componentThemes.Button || {}),
|
||||||
|
colorPrimary: primary,
|
||||||
|
} as ButtonToken,
|
||||||
|
Input: {
|
||||||
|
...(componentThemes.Input || {}),
|
||||||
|
colorPrimary: primary,
|
||||||
|
activeBorderColor: primary,
|
||||||
|
} as InputToken,
|
||||||
|
Select: {
|
||||||
|
...(componentThemes.Select || {}),
|
||||||
|
colorPrimary: primary,
|
||||||
|
optionSelectedColor: primary,
|
||||||
|
} as SelectToken,
|
||||||
|
DatePicker: {
|
||||||
|
...(componentThemes.DatePicker || {}),
|
||||||
|
colorPrimary: primary,
|
||||||
|
} as DatePickerToken,
|
||||||
|
Menu: {
|
||||||
|
...(componentThemes.Menu || {}),
|
||||||
|
itemSelectedColor: primary,
|
||||||
|
} as MenuToken,
|
||||||
|
Segmented: {
|
||||||
|
...(componentThemes.Segmented || {}),
|
||||||
|
itemSelectedColor: primary,
|
||||||
|
} as SegmentedToken,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
...componentThemes,
|
||||||
|
Button: {
|
||||||
|
...(componentThemes.Button || {}),
|
||||||
|
colorPrimary: primary,
|
||||||
|
} as ButtonToken,
|
||||||
|
Input: {
|
||||||
|
...(componentThemes.Input || {}),
|
||||||
|
colorPrimary: primary,
|
||||||
|
activeBorderColor: primary,
|
||||||
|
hoverBorderColor: primary,
|
||||||
|
activeShadow: '0 0 0 2px rgba(93, 135, 255, 0.2)',
|
||||||
|
} as InputToken,
|
||||||
|
Select: {
|
||||||
|
...(componentThemes.Select || {}),
|
||||||
|
colorPrimary: primary,
|
||||||
|
optionSelectedBg: primarySelectedBg,
|
||||||
|
optionActiveBg: 'rgba(255, 255, 255, 0.06)',
|
||||||
|
optionSelectedColor: '#fff',
|
||||||
|
} as SelectToken,
|
||||||
|
Table: {
|
||||||
|
...(componentThemes.Table || {}),
|
||||||
|
headerBg: 'rgba(255, 255, 255, 0.04)',
|
||||||
|
headerColor: 'rgba(255, 255, 255, 0.85)',
|
||||||
|
headerSortActiveBg: 'rgba(255, 255, 255, 0.08)',
|
||||||
|
headerSortHoverBg: 'rgba(255, 255, 255, 0.06)',
|
||||||
|
bodySortBg: 'rgba(255, 255, 255, 0.03)',
|
||||||
|
rowHoverBg: 'rgba(255, 255, 255, 0.05)',
|
||||||
|
rowSelectedBg: primarySelectedBg,
|
||||||
|
rowSelectedHoverBg: primaryActiveBg,
|
||||||
|
rowExpandedBg: 'rgba(255, 255, 255, 0.03)',
|
||||||
|
} as TableToken,
|
||||||
|
Card: {
|
||||||
|
...(componentThemes.Card || {}),
|
||||||
|
actionsBg: 'rgba(255, 255, 255, 0.03)',
|
||||||
|
} as CardToken,
|
||||||
|
Modal: {
|
||||||
|
...(componentThemes.Modal || {}),
|
||||||
|
contentBg: '#171c26',
|
||||||
|
headerBg: '#171c26',
|
||||||
|
footerBg: 'transparent',
|
||||||
|
maskBg: 'rgba(0, 0, 0, 0.55)',
|
||||||
|
} as ModalToken,
|
||||||
|
Message: {
|
||||||
|
...(componentThemes.Message || {}),
|
||||||
|
contentBg: '#171c26',
|
||||||
|
} as MessageToken,
|
||||||
|
DatePicker: {
|
||||||
|
...(componentThemes.DatePicker || {}),
|
||||||
|
colorPrimary: primary,
|
||||||
|
cellActiveWithRangeBg: primarySelectedBg,
|
||||||
|
cellHoverWithRangeBg: 'rgba(255, 255, 255, 0.06)',
|
||||||
|
cellBgDisabled: 'rgba(255, 255, 255, 0.04)',
|
||||||
|
} as DatePickerToken,
|
||||||
|
Form: {
|
||||||
|
...(componentThemes.Form || {}),
|
||||||
|
labelColor: 'rgba(255, 255, 255, 0.85)',
|
||||||
|
} as FormToken,
|
||||||
|
Menu: {
|
||||||
|
...(componentThemes.Menu || {}),
|
||||||
|
itemColor: 'rgba(255, 255, 255, 0.85)',
|
||||||
|
itemHoverBg: 'rgba(255, 255, 255, 0.06)',
|
||||||
|
itemHoverColor: '#fff',
|
||||||
|
itemSelectedBg: primarySelectedBg,
|
||||||
|
itemSelectedColor: primary,
|
||||||
|
itemActiveBg: primaryActiveBg,
|
||||||
|
} as MenuToken,
|
||||||
|
Segmented: {
|
||||||
|
...(componentThemes.Segmented || {}),
|
||||||
|
itemSelectedBg: 'rgba(255, 255, 255, 0.1)',
|
||||||
|
itemSelectedColor: primary,
|
||||||
|
trackBg: 'rgba(255, 255, 255, 0.04)',
|
||||||
|
} as SegmentedToken,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
// 向后兼容的导出
|
// 向后兼容的导出
|
||||||
export const backgroundConfigs = THEME_BACKGROUND_CONFIGS;
|
export const backgroundConfigs = THEME_BACKGROUND_CONFIGS;
|
||||||
export type BackgroundConfig = ThemeBackgroundConfig;
|
export type BackgroundConfig = ThemeBackgroundConfig;
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ html {
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
min-width: 1200px; //TODO 这个要和UI 确认
|
min-width: 1200px; //TODO 这个要和UI 确认
|
||||||
background-color: #fff;
|
background-color: var(--xagi-layout-bg-secondary, #f4f7fb);
|
||||||
}
|
}
|
||||||
|
|
||||||
#root {
|
#root {
|
||||||
@@ -22,6 +22,8 @@ body {
|
|||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
transform: translate3d(0, 0, 0);
|
transform: translate3d(0, 0, 0);
|
||||||
|
background: var(--xagi-layout-bg-secondary, #f4f7fb);
|
||||||
|
color: var(--xagi-layout-text-primary, #1f2337);
|
||||||
|
|
||||||
ul,
|
ul,
|
||||||
li {
|
li {
|
||||||
@@ -48,6 +50,50 @@ body {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
body.xagi-color-weak {
|
||||||
|
filter: invert(80%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.xagi-top-progress {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
z-index: 9999;
|
||||||
|
width: 0;
|
||||||
|
height: 2px;
|
||||||
|
background: var(--xagi-color-primary, #5d87ff);
|
||||||
|
box-shadow: 0 0 10px var(--xagi-color-primary, #5d87ff);
|
||||||
|
opacity: 0;
|
||||||
|
transition: width 0.42s ease, opacity 0.18s ease;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.xagi-top-progress-active {
|
||||||
|
width: 100%;
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.xagi-global-watermark {
|
||||||
|
position: fixed;
|
||||||
|
inset: 0;
|
||||||
|
z-index: 9998;
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
|
||||||
|
gap: 72px 36px;
|
||||||
|
overflow: hidden;
|
||||||
|
padding: 72px 40px;
|
||||||
|
color: rgba(91, 105, 135, 0.14);
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: 600;
|
||||||
|
pointer-events: none;
|
||||||
|
|
||||||
|
span {
|
||||||
|
display: block;
|
||||||
|
transform: rotate(-24deg);
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 滚动条
|
// 滚动条
|
||||||
::-webkit-scrollbar {
|
::-webkit-scrollbar {
|
||||||
width: 8px;
|
width: 8px;
|
||||||
@@ -60,13 +106,13 @@ body {
|
|||||||
}
|
}
|
||||||
// 滚动条滑块
|
// 滚动条滑块
|
||||||
::-webkit-scrollbar-thumb {
|
::-webkit-scrollbar-thumb {
|
||||||
background: @GRAY19;
|
background: rgba(91, 105, 135, 0.28);
|
||||||
border-radius: 6px; /* 滚动条滑块圆角 */
|
border-radius: 6px; /* 滚动条滑块圆角 */
|
||||||
transition: background-color 0.3s ease-in-out; /* 添加过渡动画 */
|
transition: background-color 0.3s ease-in-out; /* 添加过渡动画 */
|
||||||
}
|
}
|
||||||
// 滚动条滑块悬停效果
|
// 滚动条滑块悬停效果
|
||||||
::-webkit-scrollbar-thumb:hover {
|
::-webkit-scrollbar-thumb:hover {
|
||||||
background: @GRAY; /* 鼠标悬停时颜色加深 */
|
background: rgba(91, 105, 135, 0.48); /* 鼠标悬停时颜色加深 */
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 768px) {
|
@media (max-width: 768px) {
|
||||||
@@ -93,7 +139,7 @@ body {
|
|||||||
/* 滚动条的外层滑轨背景颜色 */
|
/* 滚动条的外层滑轨背景颜色 */
|
||||||
|
|
||||||
.scrollbar::-webkit-scrollbar-thumb {
|
.scrollbar::-webkit-scrollbar-thumb {
|
||||||
background: @GRAY19;
|
background: rgba(91, 105, 135, 0.28);
|
||||||
border-radius: 6px;
|
border-radius: 6px;
|
||||||
transition: background-color 0.3s ease-in-out; /* 添加过渡动画 */
|
transition: background-color 0.3s ease-in-out; /* 添加过渡动画 */
|
||||||
|
|
||||||
@@ -102,7 +148,7 @@ body {
|
|||||||
|
|
||||||
/* 滚动条的内层滑块悬停效果 */
|
/* 滚动条的内层滑块悬停效果 */
|
||||||
.scrollbar::-webkit-scrollbar-thumb:hover {
|
.scrollbar::-webkit-scrollbar-thumb:hover {
|
||||||
background: @GRAY; /* 鼠标悬停时颜色进一步加深 */
|
background: rgba(91, 105, 135, 0.48); /* 鼠标悬停时颜色进一步加深 */
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 滚动条的内层滑块颜色 */
|
/* 滚动条的内层滑块颜色 */
|
||||||
@@ -150,12 +196,223 @@ body {
|
|||||||
.ant-table thead.ant-table-thead .ant-table-cell-fix-right,
|
.ant-table thead.ant-table-thead .ant-table-cell-fix-right,
|
||||||
.ant-table-container thead.ant-table-thead > tr > th,
|
.ant-table-container thead.ant-table-thead > tr > th,
|
||||||
.ant-table-content thead.ant-table-thead > tr > th {
|
.ant-table-content thead.ant-table-thead > tr > th {
|
||||||
background-color: #f0f2f5 !important;
|
background-color: #f4f7fb !important;
|
||||||
|
color: #5d6378 !important;
|
||||||
|
font-weight: 600;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 表格分页下拉框边框颜色
|
// 表格分页下拉框边框颜色
|
||||||
.ant-pagination-options .ant-select-selector {
|
.ant-pagination-options .ant-select-selector {
|
||||||
border-color: #f0f2f5 !important;
|
border-color: rgba(91, 105, 135, 0.14) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-modal-content,
|
||||||
|
.ant-popover-inner,
|
||||||
|
.ant-dropdown-menu,
|
||||||
|
.ant-select-dropdown,
|
||||||
|
.ant-picker-dropdown .ant-picker-panel-container {
|
||||||
|
border-radius: 8px !important;
|
||||||
|
box-shadow: 0 16px 42px rgba(32, 45, 84, 0.12) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-btn,
|
||||||
|
.ant-input,
|
||||||
|
.ant-input-affix-wrapper,
|
||||||
|
.ant-select-selector,
|
||||||
|
.ant-picker,
|
||||||
|
.ant-segmented {
|
||||||
|
border-radius: 6px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-card,
|
||||||
|
.ant-pro-card,
|
||||||
|
.ant-table-wrapper .ant-table,
|
||||||
|
.ant-pro-table .ant-pro-table-search {
|
||||||
|
border-radius: 8px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.xagi-layout-light {
|
||||||
|
.ant-input,
|
||||||
|
.ant-input-affix-wrapper,
|
||||||
|
.ant-select:not(.ant-select-customize-input) .ant-select-selector,
|
||||||
|
.ant-picker {
|
||||||
|
border-color: var(
|
||||||
|
--xagi-layout-border-primary,
|
||||||
|
rgba(91, 105, 135, 0.14)
|
||||||
|
) !important;
|
||||||
|
background: var(
|
||||||
|
--xagi-layout-bg-input,
|
||||||
|
rgba(255, 255, 255, 0.88)
|
||||||
|
) !important;
|
||||||
|
color: var(--xagi-layout-text-primary, #1f2337) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-input::placeholder,
|
||||||
|
.ant-input-affix-wrapper .ant-input::placeholder,
|
||||||
|
.ant-select-selection-placeholder {
|
||||||
|
color: var(--xagi-layout-text-tertiary, rgba(31, 35, 55, 0.52)) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-input-prefix,
|
||||||
|
.ant-input-suffix,
|
||||||
|
.ant-select-arrow,
|
||||||
|
.ant-select-clear,
|
||||||
|
.ant-picker-suffix {
|
||||||
|
color: var(--xagi-layout-text-tertiary, rgba(31, 35, 55, 0.52)) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-input-affix-wrapper:hover,
|
||||||
|
.ant-input:hover,
|
||||||
|
.ant-select:not(.ant-select-disabled):not(.ant-select-customize-input)
|
||||||
|
.ant-select-selector:hover,
|
||||||
|
.ant-picker:hover {
|
||||||
|
border-color: var(--xagi-color-primary, #5d87ff) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-input-affix-wrapper-focused,
|
||||||
|
.ant-input:focus,
|
||||||
|
.ant-select-focused:not(.ant-select-disabled):not(.ant-select-customize-input)
|
||||||
|
.ant-select-selector,
|
||||||
|
.ant-picker-focused {
|
||||||
|
border-color: var(--xagi-color-primary, #5d87ff) !important;
|
||||||
|
box-shadow: 0 0 0 2px rgba(93, 135, 255, 0.14) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-btn-default:not(.ant-btn-dangerous):not(.ant-btn-color-dangerous),
|
||||||
|
.ant-btn-color-default.ant-btn-variant-outlined {
|
||||||
|
border-color: var(
|
||||||
|
--xagi-layout-border-primary,
|
||||||
|
rgba(91, 105, 135, 0.14)
|
||||||
|
) !important;
|
||||||
|
background: var(
|
||||||
|
--xagi-layout-bg-input,
|
||||||
|
rgba(255, 255, 255, 0.88)
|
||||||
|
) !important;
|
||||||
|
color: var(--xagi-layout-text-primary, #1f2337) !important;
|
||||||
|
box-shadow: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-btn-default:not(.ant-btn-dangerous):not(.ant-btn-color-dangerous):not(
|
||||||
|
:disabled
|
||||||
|
):not(.ant-btn-disabled):hover,
|
||||||
|
.ant-btn-color-default.ant-btn-variant-outlined:not(:disabled):not(
|
||||||
|
.ant-btn-disabled
|
||||||
|
):hover {
|
||||||
|
border-color: var(--xagi-color-primary, #5d87ff) !important;
|
||||||
|
background: rgba(93, 135, 255, 0.1) !important;
|
||||||
|
background: color-mix(
|
||||||
|
in srgb,
|
||||||
|
var(--xagi-color-primary, #5d87ff) 10%,
|
||||||
|
#fff
|
||||||
|
) !important;
|
||||||
|
color: var(--xagi-color-primary, #5d87ff) !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
body.xagi-layout-light {
|
||||||
|
.ant-modal-content,
|
||||||
|
.ant-popover-inner,
|
||||||
|
.ant-dropdown-menu,
|
||||||
|
.ant-select-dropdown,
|
||||||
|
.ant-picker-dropdown .ant-picker-panel-container {
|
||||||
|
background: var(--xagi-layout-bg-container, #fff) !important;
|
||||||
|
color: var(--xagi-layout-text-primary, #1f2337) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-select-item,
|
||||||
|
.ant-dropdown-menu-item,
|
||||||
|
.ant-dropdown-menu-submenu-title {
|
||||||
|
color: var(--xagi-layout-text-primary, #1f2337) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-select-item-option-active:not(.ant-select-item-option-disabled),
|
||||||
|
.ant-dropdown-menu-item-active,
|
||||||
|
.ant-dropdown-menu-submenu-title-active {
|
||||||
|
background: rgba(93, 135, 255, 0.08) !important;
|
||||||
|
background: color-mix(
|
||||||
|
in srgb,
|
||||||
|
var(--xagi-color-primary, #5d87ff) 8%,
|
||||||
|
#fff
|
||||||
|
) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-select-item-option-selected:not(.ant-select-item-option-disabled) {
|
||||||
|
background: rgba(93, 135, 255, 0.12) !important;
|
||||||
|
background: color-mix(
|
||||||
|
in srgb,
|
||||||
|
var(--xagi-color-primary, #5d87ff) 14%,
|
||||||
|
#fff
|
||||||
|
) !important;
|
||||||
|
color: var(--xagi-color-primary, #5d87ff) !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
html[data-theme='dark'] {
|
||||||
|
background-color: var(--xagi-layout-bg-primary, #080a0f);
|
||||||
|
|
||||||
|
body {
|
||||||
|
background: var(--xagi-layout-bg-primary, #080a0f);
|
||||||
|
color: var(--xagi-layout-text-primary, #fff);
|
||||||
|
}
|
||||||
|
|
||||||
|
::-webkit-scrollbar-thumb,
|
||||||
|
.scrollbar::-webkit-scrollbar-thumb,
|
||||||
|
.x-pro-table .ant-table-body::-webkit-scrollbar-thumb,
|
||||||
|
.x-pro-table .ant-table-content::-webkit-scrollbar-thumb {
|
||||||
|
background: rgba(255, 255, 255, 0.2) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
::-webkit-scrollbar-thumb:hover,
|
||||||
|
.scrollbar::-webkit-scrollbar-thumb:hover,
|
||||||
|
.x-pro-table .ant-table-body::-webkit-scrollbar-thumb:hover,
|
||||||
|
.x-pro-table .ant-table-content::-webkit-scrollbar-thumb:hover {
|
||||||
|
background: rgba(255, 255, 255, 0.34) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.scrollbar::-webkit-scrollbar-track,
|
||||||
|
.scrollbar::-webkit-scrollbar-track-piece,
|
||||||
|
.scrollbar::-webkit-scrollbar-button {
|
||||||
|
background-color: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-pagination-options .ant-select-selector,
|
||||||
|
.ant-table thead.ant-table-thead > tr > th,
|
||||||
|
.ant-table thead.ant-table-thead .ant-table-cell-fix-left,
|
||||||
|
.ant-table thead.ant-table-thead .ant-table-cell-fix-right,
|
||||||
|
.ant-table-container thead.ant-table-thead > tr > th,
|
||||||
|
.ant-table-content thead.ant-table-thead > tr > th {
|
||||||
|
background-color: rgba(255, 255, 255, 0.04) !important;
|
||||||
|
color: var(
|
||||||
|
--xagi-layout-text-secondary,
|
||||||
|
rgba(255, 255, 255, 0.85)
|
||||||
|
) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-pagination-options .ant-select-selector {
|
||||||
|
border-color: var(
|
||||||
|
--xagi-layout-border-primary,
|
||||||
|
rgba(255, 255, 255, 0.12)
|
||||||
|
) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-modal-content,
|
||||||
|
.ant-popover-inner,
|
||||||
|
.ant-dropdown-menu,
|
||||||
|
.ant-select-dropdown,
|
||||||
|
.ant-picker-dropdown .ant-picker-panel-container {
|
||||||
|
box-shadow: 0 18px 48px rgba(0, 0, 0, 0.48) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-card,
|
||||||
|
.ant-pro-card,
|
||||||
|
.ant-table-wrapper .ant-table,
|
||||||
|
.ant-pro-table .ant-pro-table-search {
|
||||||
|
border-color: var(
|
||||||
|
--xagi-layout-border-secondary,
|
||||||
|
rgba(255, 255, 255, 0.08)
|
||||||
|
) !important;
|
||||||
|
background: var(--xagi-layout-bg-container, #111620) !important;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// XProTable 表格滚动条样式:显示滑块,隐藏轨道
|
// XProTable 表格滚动条样式:显示滑块,隐藏轨道
|
||||||
@@ -175,17 +432,17 @@ body {
|
|||||||
|
|
||||||
// 显示滚动条滑块
|
// 显示滚动条滑块
|
||||||
&::-webkit-scrollbar-thumb {
|
&::-webkit-scrollbar-thumb {
|
||||||
background: @GRAY19 !important;
|
background: rgba(91, 105, 135, 0.28) !important;
|
||||||
border-radius: 6px !important;
|
border-radius: 6px !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
&::-webkit-scrollbar-thumb:hover {
|
&::-webkit-scrollbar-thumb:hover {
|
||||||
background: @GRAY !important;
|
background: rgba(91, 105, 135, 0.48) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 针对 Firefox - 使用 thin 模式保留滑块
|
// 针对 Firefox - 使用 thin 模式保留滑块
|
||||||
scrollbar-width: thin !important;
|
scrollbar-width: thin !important;
|
||||||
scrollbar-color: @GRAY19 transparent !important;
|
scrollbar-color: rgba(91, 105, 135, 0.28) transparent !important;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -8,11 +8,14 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
UnifiedThemeBasicConfig,
|
||||||
UnifiedThemeData,
|
UnifiedThemeData,
|
||||||
unifiedThemeService,
|
unifiedThemeService,
|
||||||
} from '@/services/unifiedThemeService';
|
} from '@/services/unifiedThemeService';
|
||||||
import {
|
import {
|
||||||
ThemeLayoutColorStyle,
|
ThemeLayoutColorStyle,
|
||||||
|
ThemeMenuLayoutType,
|
||||||
|
ThemeMenuStyleType,
|
||||||
ThemeNavigationStyleType,
|
ThemeNavigationStyleType,
|
||||||
} from '@/types/enums/theme';
|
} from '@/types/enums/theme';
|
||||||
import { useCallback, useEffect, useMemo, useState } from 'react';
|
import { useCallback, useEffect, useMemo, useState } from 'react';
|
||||||
@@ -45,6 +48,14 @@ interface UseUnifiedThemeReturn {
|
|||||||
style: ThemeNavigationStyleType,
|
style: ThemeNavigationStyleType,
|
||||||
options?: UpdateOptions,
|
options?: UpdateOptions,
|
||||||
) => Promise<void>;
|
) => Promise<void>;
|
||||||
|
updateMenuLayout: (
|
||||||
|
layout: ThemeMenuLayoutType,
|
||||||
|
options?: UpdateOptions,
|
||||||
|
) => Promise<void>;
|
||||||
|
updateMenuStyle: (
|
||||||
|
style: ThemeMenuStyleType,
|
||||||
|
options?: UpdateOptions,
|
||||||
|
) => Promise<void>;
|
||||||
updateLayoutStyle: (
|
updateLayoutStyle: (
|
||||||
style: ThemeLayoutColorStyle,
|
style: ThemeLayoutColorStyle,
|
||||||
options?: UpdateOptions,
|
options?: UpdateOptions,
|
||||||
@@ -57,6 +68,10 @@ interface UseUnifiedThemeReturn {
|
|||||||
language: 'zh-CN' | 'en-US',
|
language: 'zh-CN' | 'en-US',
|
||||||
options?: UpdateOptions,
|
options?: UpdateOptions,
|
||||||
) => Promise<void>;
|
) => Promise<void>;
|
||||||
|
updateBasicConfig: (
|
||||||
|
config: Partial<UnifiedThemeBasicConfig>,
|
||||||
|
options?: UpdateOptions,
|
||||||
|
) => Promise<void>;
|
||||||
|
|
||||||
// 便捷方法
|
// 便捷方法
|
||||||
toggleAntdTheme: () => Promise<void>;
|
toggleAntdTheme: () => Promise<void>;
|
||||||
@@ -69,9 +84,12 @@ interface UseUnifiedThemeReturn {
|
|||||||
primaryColor: string;
|
primaryColor: string;
|
||||||
antdTheme: 'light' | 'dark';
|
antdTheme: 'light' | 'dark';
|
||||||
navigationStyle: ThemeNavigationStyleType;
|
navigationStyle: ThemeNavigationStyleType;
|
||||||
|
menuLayout: ThemeMenuLayoutType;
|
||||||
|
menuStyle: ThemeMenuStyleType;
|
||||||
layoutStyle: ThemeLayoutColorStyle;
|
layoutStyle: ThemeLayoutColorStyle;
|
||||||
backgroundId: string;
|
backgroundId: string;
|
||||||
language: 'zh-CN' | 'en-US';
|
language: 'zh-CN' | 'en-US';
|
||||||
|
basicConfig: UnifiedThemeBasicConfig;
|
||||||
isDarkMode: boolean;
|
isDarkMode: boolean;
|
||||||
isNavigationDark: boolean;
|
isNavigationDark: boolean;
|
||||||
isNavigationExpanded: boolean;
|
isNavigationExpanded: boolean;
|
||||||
@@ -133,6 +151,12 @@ export const useUnifiedTheme = (): UseUnifiedThemeReturn => {
|
|||||||
const updateNavigationStyle = createUpdateMethod(
|
const updateNavigationStyle = createUpdateMethod(
|
||||||
unifiedThemeService.updateNavigationStyle.bind(unifiedThemeService),
|
unifiedThemeService.updateNavigationStyle.bind(unifiedThemeService),
|
||||||
);
|
);
|
||||||
|
const updateMenuLayout = createUpdateMethod(
|
||||||
|
unifiedThemeService.updateMenuLayout.bind(unifiedThemeService),
|
||||||
|
);
|
||||||
|
const updateMenuStyle = createUpdateMethod(
|
||||||
|
unifiedThemeService.updateMenuStyle.bind(unifiedThemeService),
|
||||||
|
);
|
||||||
const updateLayoutStyle = createUpdateMethod(
|
const updateLayoutStyle = createUpdateMethod(
|
||||||
unifiedThemeService.updateLayoutStyle.bind(unifiedThemeService),
|
unifiedThemeService.updateLayoutStyle.bind(unifiedThemeService),
|
||||||
);
|
);
|
||||||
@@ -142,6 +166,18 @@ export const useUnifiedTheme = (): UseUnifiedThemeReturn => {
|
|||||||
const updateLanguage = createUpdateMethod(
|
const updateLanguage = createUpdateMethod(
|
||||||
unifiedThemeService.updateLanguage.bind(unifiedThemeService),
|
unifiedThemeService.updateLanguage.bind(unifiedThemeService),
|
||||||
);
|
);
|
||||||
|
const updateBasicConfig = createUpdateMethod(
|
||||||
|
async (
|
||||||
|
config: Partial<UnifiedThemeBasicConfig>,
|
||||||
|
options?: UpdateOptions,
|
||||||
|
) => {
|
||||||
|
const current = unifiedThemeService.getCurrentData().basicConfig;
|
||||||
|
await unifiedThemeService.updateData(
|
||||||
|
{ basicConfig: { ...current, ...config } },
|
||||||
|
options,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
// 便捷切换方法
|
// 便捷切换方法
|
||||||
const toggleAntdTheme = useCallback(async () => {
|
const toggleAntdTheme = useCallback(async () => {
|
||||||
@@ -196,9 +232,12 @@ export const useUnifiedTheme = (): UseUnifiedThemeReturn => {
|
|||||||
updatePrimaryColor,
|
updatePrimaryColor,
|
||||||
updateAntdTheme,
|
updateAntdTheme,
|
||||||
updateNavigationStyle,
|
updateNavigationStyle,
|
||||||
|
updateMenuLayout,
|
||||||
|
updateMenuStyle,
|
||||||
updateLayoutStyle,
|
updateLayoutStyle,
|
||||||
updateBackground,
|
updateBackground,
|
||||||
updateLanguage,
|
updateLanguage,
|
||||||
|
updateBasicConfig,
|
||||||
|
|
||||||
// 便捷方法
|
// 便捷方法
|
||||||
toggleAntdTheme,
|
toggleAntdTheme,
|
||||||
@@ -211,9 +250,12 @@ export const useUnifiedTheme = (): UseUnifiedThemeReturn => {
|
|||||||
primaryColor: data.primaryColor,
|
primaryColor: data.primaryColor,
|
||||||
antdTheme: data.antdTheme,
|
antdTheme: data.antdTheme,
|
||||||
navigationStyle: data.navigationStyle,
|
navigationStyle: data.navigationStyle,
|
||||||
|
menuLayout: data.menuLayout,
|
||||||
|
menuStyle: data.menuStyle,
|
||||||
layoutStyle: data.layoutStyle,
|
layoutStyle: data.layoutStyle,
|
||||||
backgroundId: data.backgroundId,
|
backgroundId: data.backgroundId,
|
||||||
language: data.language,
|
language: data.language,
|
||||||
|
basicConfig: data.basicConfig,
|
||||||
isDarkMode,
|
isDarkMode,
|
||||||
isNavigationDark,
|
isNavigationDark,
|
||||||
isNavigationExpanded,
|
isNavigationExpanded,
|
||||||
|
|||||||
@@ -3,41 +3,37 @@
|
|||||||
|
|
||||||
.collapse-button {
|
.collapse-button {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
bottom: 44px;
|
bottom: 34px;
|
||||||
width: 20px;
|
width: 28px;
|
||||||
height: 20px;
|
height: 28px;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
background-color: @colorBgElevated;
|
background-color: rgba(255, 255, 255, 0.96);
|
||||||
border: @lineWidth solid @colorSplit;
|
border: 1px solid rgba(91, 105, 135, 0.14);
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
transition: all 0.2s ease-in-out;
|
transition: transform 0.24s ease, border-color 0.24s ease,
|
||||||
|
box-shadow 0.24s ease, color 0.24s ease;
|
||||||
z-index: 999;
|
z-index: 999;
|
||||||
transform: translateX(-50%);
|
transform: translateX(-50%);
|
||||||
|
box-shadow: 0 10px 20px rgba(32, 45, 84, 0.1);
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
transform: translateX(-50%) scale(1.1);
|
border-color: @colorPrimary;
|
||||||
|
transform: translateX(-50%) scale(1.08);
|
||||||
|
box-shadow: 0 14px 24px rgba(32, 45, 84, 0.14);
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon {
|
.icon {
|
||||||
color: @colorTextSecondary;
|
color: #5d6378;
|
||||||
transition: color 0.2s ease-in-out;
|
font-size: 12px;
|
||||||
|
transition: color 0.24s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
//&:hover .icon {
|
&:hover .icon {
|
||||||
// color: @colorText;
|
color: @colorPrimary;
|
||||||
//}
|
}
|
||||||
//
|
|
||||||
//// 收起状态时的样式调整
|
|
||||||
//&.collapsed {
|
|
||||||
// &:hover {
|
|
||||||
// .icon {
|
|
||||||
// color: @colorText;
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
//}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 移动端隐藏收起按钮
|
// 移动端隐藏收起按钮
|
||||||
|
|||||||
@@ -2,7 +2,10 @@ import SvgIcon from '@/components/base/SvgIcon';
|
|||||||
import { NAVIGATION_LAYOUT_SIZES } from '@/constants/layout.constants';
|
import { NAVIGATION_LAYOUT_SIZES } from '@/constants/layout.constants';
|
||||||
import { useUnifiedTheme } from '@/hooks/useUnifiedTheme';
|
import { useUnifiedTheme } from '@/hooks/useUnifiedTheme';
|
||||||
import { dict } from '@/services/i18nRuntime';
|
import { dict } from '@/services/i18nRuntime';
|
||||||
import { ThemeNavigationStyleType } from '@/types/enums/theme';
|
import {
|
||||||
|
ThemeMenuLayoutType,
|
||||||
|
ThemeNavigationStyleType,
|
||||||
|
} from '@/types/enums/theme';
|
||||||
import { Tooltip } from 'antd';
|
import { Tooltip } from 'antd';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import React, { useEffect } from 'react';
|
import React, { useEffect } from 'react';
|
||||||
@@ -18,7 +21,7 @@ const cx = classNames.bind(styles);
|
|||||||
const CollapseButton: React.FC = () => {
|
const CollapseButton: React.FC = () => {
|
||||||
const { isSecondMenuCollapsed, setIsSecondMenuCollapsed } =
|
const { isSecondMenuCollapsed, setIsSecondMenuCollapsed } =
|
||||||
useModel('layout');
|
useModel('layout');
|
||||||
const { navigationStyle } = useUnifiedTheme();
|
const { navigationStyle, menuLayout } = useUnifiedTheme();
|
||||||
const [searchParams] = useSearchParams();
|
const [searchParams] = useSearchParams();
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
|
|
||||||
@@ -84,6 +87,19 @@ const CollapseButton: React.FC = () => {
|
|||||||
: NAVIGATION_LAYOUT_SIZES.FIRST_MENU_WIDTH.STYLE1;
|
: NAVIGATION_LAYOUT_SIZES.FIRST_MENU_WIDTH.STYLE1;
|
||||||
const menuTotalWidth =
|
const menuTotalWidth =
|
||||||
NAVIGATION_LAYOUT_SIZES.getTotalMenuWidth(navigationStyle);
|
NAVIGATION_LAYOUT_SIZES.getTotalMenuWidth(navigationStyle);
|
||||||
|
const collapsedLeft =
|
||||||
|
menuLayout === ThemeMenuLayoutType.MIXED ? 0 : firstMenuWidth;
|
||||||
|
const expandedLeft =
|
||||||
|
menuLayout === ThemeMenuLayoutType.MIXED
|
||||||
|
? NAVIGATION_LAYOUT_SIZES.SECOND_MENU_WIDTH
|
||||||
|
: menuTotalWidth;
|
||||||
|
|
||||||
|
if (
|
||||||
|
menuLayout === ThemeMenuLayoutType.HORIZONTAL ||
|
||||||
|
menuLayout === ThemeMenuLayoutType.VERTICAL
|
||||||
|
) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
// 处理点击事件(保存用户操作到localStorage)
|
// 处理点击事件(保存用户操作到localStorage)
|
||||||
const handleToggleCollapse = () => {
|
const handleToggleCollapse = () => {
|
||||||
@@ -115,7 +131,7 @@ const CollapseButton: React.FC = () => {
|
|||||||
})}
|
})}
|
||||||
onClick={handleToggleCollapse}
|
onClick={handleToggleCollapse}
|
||||||
style={{
|
style={{
|
||||||
left: isSecondMenuCollapsed ? firstMenuWidth : menuTotalWidth,
|
left: isSecondMenuCollapsed ? collapsedLeft : expandedLeft,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<SvgIcon
|
<SvgIcon
|
||||||
|
|||||||
@@ -697,8 +697,19 @@ const DynamicSecondMenu: React.FC<DynamicSecondMenuProps> = ({
|
|||||||
onToggle={() => toggleExpand(menuCode)}
|
onToggle={() => toggleExpand(menuCode)}
|
||||||
/>
|
/>
|
||||||
{/* 递归渲染子菜单 */}
|
{/* 递归渲染子菜单 */}
|
||||||
{isExpanded &&
|
<div
|
||||||
menu.children?.map((child) => renderMenuItem(child, level + 1))}
|
className="flex flex-col gap-4"
|
||||||
|
style={{
|
||||||
|
maxHeight: isExpanded ? 720 : 0,
|
||||||
|
opacity: isExpanded ? 1 : 0,
|
||||||
|
overflow: 'hidden',
|
||||||
|
transform: isExpanded ? 'translateY(0)' : 'translateY(-4px)',
|
||||||
|
transition:
|
||||||
|
'max-height 260ms cubic-bezier(0.4, 0, 0.2, 1), opacity 220ms ease, transform 220ms ease',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{menu.children?.map((child) => renderMenuItem(child, level + 1))}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -2,18 +2,18 @@
|
|||||||
@import '@/styles/token';
|
@import '@/styles/token';
|
||||||
|
|
||||||
.box {
|
.box {
|
||||||
margin-bottom: @marginXs;
|
margin-bottom: 0;
|
||||||
color: @navFirstMenuTextSecondary;
|
color: @navFirstMenuTextSecondary;
|
||||||
transition: background-color 0.3s, color 0.3s;
|
transition: transform 0.26s ease, color 0.26s ease;
|
||||||
user-select: none; /* 禁止选中 */
|
user-select: none; /* 禁止选中 */
|
||||||
|
|
||||||
.active-icon-container {
|
.active-icon-container {
|
||||||
font-size: 20px;
|
font-size: 24px;
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
border-radius: @borderRadius;
|
border-radius: 8px;
|
||||||
|
|
||||||
.icon-image {
|
.icon-image {
|
||||||
width: 20px;
|
width: 20px;
|
||||||
@@ -22,8 +22,9 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.active-box {
|
.active-box {
|
||||||
border-radius: @borderRadius;
|
border-radius: 14px;
|
||||||
transition: all 0.3s ease;
|
transition: transform 0.26s ease, background 0.26s ease,
|
||||||
|
box-shadow 0.26s ease, color 0.26s ease;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@@ -31,11 +32,12 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
color: @colorTextSecondary;
|
color: @colorPrimary;
|
||||||
|
transform: translateY(-1px);
|
||||||
|
|
||||||
.active-box {
|
.active-box {
|
||||||
background: @navItemHoverBg;
|
background: rgba(255, 255, 255, 0.7);
|
||||||
box-shadow: @navShadow;
|
box-shadow: 0 10px 18px rgba(32, 45, 84, 0.08);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -43,17 +45,18 @@
|
|||||||
color: @colorPrimary;
|
color: @colorPrimary;
|
||||||
|
|
||||||
.active-box {
|
.active-box {
|
||||||
background: @navItemActiveBg;
|
background: #fff;
|
||||||
box-shadow: @navShadow;
|
box-shadow: 0 12px 22px rgba(32, 45, 84, 0.1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 支持导航风格2的文字显示
|
// 支持导航风格2的文字显示
|
||||||
.text {
|
.text {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
margin-top: @marginXxs;
|
margin-top: 4px;
|
||||||
font-size: @navFirstMenuFontSize;
|
font-size: 12px;
|
||||||
transition: all 0.3s ease;
|
line-height: 18px;
|
||||||
|
transition: all 0.26s ease;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
|||||||
@@ -35,12 +35,12 @@ const TabItem: React.FC<TabItemProps & { isSecondMenuCollapsed?: boolean }> = ({
|
|||||||
const navStyle: React.CSSProperties = useMemo(() => {
|
const navStyle: React.CSSProperties = useMemo(() => {
|
||||||
return isStyle2
|
return isStyle2
|
||||||
? {
|
? {
|
||||||
width: '64px',
|
width: '88px',
|
||||||
height: '64px',
|
height: '64px',
|
||||||
}
|
}
|
||||||
: {
|
: {
|
||||||
width: '40px',
|
width: '58px',
|
||||||
height: '40px',
|
height: '58px',
|
||||||
padding: 0,
|
padding: 0,
|
||||||
};
|
};
|
||||||
}, [isStyle2]);
|
}, [isStyle2]);
|
||||||
|
|||||||
@@ -1,8 +1,11 @@
|
|||||||
.dynamic-tabs-container {
|
.dynamic-tabs-container {
|
||||||
align-items: flex-end;
|
align-items: center;
|
||||||
padding-right: 2px;
|
gap: 12px;
|
||||||
|
padding: 10px 0 18px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.style-two {
|
.style-two {
|
||||||
align-items: flex-end;
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
padding: 10px 8px 18px;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
@import '@/styles/color';
|
@import '@/styles/color';
|
||||||
|
|
||||||
.logo {
|
.logo {
|
||||||
width: 32px;
|
width: 48px;
|
||||||
height: 32px;
|
height: 48px;
|
||||||
border-radius: 8px;
|
border-radius: 14px;
|
||||||
|
box-shadow: 0 10px 22px rgba(93, 135, 255, 0.28);
|
||||||
}
|
}
|
||||||
|
|
||||||
.add-agent {
|
.add-agent {
|
||||||
@@ -19,9 +20,9 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.logo-container {
|
.logo-container {
|
||||||
height: 52px;
|
height: 58px;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
margin-bottom: 24px;
|
margin-bottom: 18px;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,19 @@
|
|||||||
@import '@/styles/token';
|
@import '@/styles/token';
|
||||||
|
|
||||||
.row {
|
.row {
|
||||||
height: 36px;
|
height: 42px;
|
||||||
padding: @paddingSm;
|
padding: 0 12px;
|
||||||
gap: @paddingSm;
|
gap: 10px;
|
||||||
color: @colorTextSecondary;
|
color: #5d6378;
|
||||||
|
border-radius: 6px;
|
||||||
|
transition: background 0.24s ease, color 0.24s ease, transform 0.24s ease;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
transform: translateX(2px);
|
||||||
|
}
|
||||||
|
|
||||||
span {
|
span {
|
||||||
font-size: @fontSize;
|
font-size: 14px;
|
||||||
|
line-height: 42px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,14 +2,19 @@
|
|||||||
@import '@/styles/token';
|
@import '@/styles/token';
|
||||||
|
|
||||||
.user {
|
.user {
|
||||||
width: 32px;
|
width: 42px;
|
||||||
height: 32px;
|
height: 42px;
|
||||||
margin-top: 5px;
|
margin: 0;
|
||||||
margin-bottom: 5px;
|
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
box-sizing: content-box;
|
box-sizing: content-box;
|
||||||
border: @lineWidth solid @colorBorder;
|
border: 2px solid #fff;
|
||||||
|
box-shadow: 0 8px 18px rgba(32, 45, 84, 0.12);
|
||||||
|
transition: transform 0.26s ease;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
transform: scale(1.04);
|
||||||
|
}
|
||||||
|
|
||||||
img {
|
img {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|||||||
@@ -2,53 +2,56 @@
|
|||||||
@import '@/styles/token';
|
@import '@/styles/token';
|
||||||
|
|
||||||
.container {
|
.container {
|
||||||
width: 140px;
|
width: 184px;
|
||||||
padding-top: 6px;
|
padding: 8px;
|
||||||
background: @navFirstMenuBg;
|
background: rgba(255, 255, 255, 0.96);
|
||||||
color: @navFirstMenuText;
|
color: #1f2337;
|
||||||
border: 1px solid @navBorderColor;
|
border: 1px solid rgba(91, 105, 135, 0.12);
|
||||||
border-radius: @borderRadius;
|
border-radius: 8px;
|
||||||
box-shadow: @navShadow;
|
box-shadow: 0 16px 42px rgba(32, 45, 84, 0.12);
|
||||||
|
backdrop-filter: blur(16px);
|
||||||
|
|
||||||
.cursor-default {
|
.cursor-default {
|
||||||
cursor: default;
|
cursor: default;
|
||||||
}
|
}
|
||||||
|
|
||||||
.divider-horizontal {
|
.divider-horizontal {
|
||||||
position: absolute;
|
margin: 6px 4px 0;
|
||||||
left: 0;
|
border-bottom: 1px solid rgba(91, 105, 135, 0.1);
|
||||||
right: 0;
|
|
||||||
bottom: 50px;
|
|
||||||
border-bottom: 1px solid @navDividerColor;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.log-out {
|
.log-out {
|
||||||
height: 51px;
|
height: 42px;
|
||||||
color: @colorTextSecondary;
|
color: #5d6378;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
background: @colorBgElevated;
|
background: rgba(249, 57, 32, 0.08);
|
||||||
color: @colorError;
|
color: @colorError;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 每个菜单项的默认样式
|
// 每个菜单项的默认样式
|
||||||
.item {
|
.item {
|
||||||
transition: all 0.3s ease;
|
transition: all 0.24s ease;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
background: @colorBgElevated;
|
background: rgba(93, 135, 255, 0.1);
|
||||||
color: @colorTextSecondary;
|
color: @colorPrimary;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.user-avatar-container {
|
.user-avatar-container {
|
||||||
margin-bottom: 32px;
|
margin-top: 22px;
|
||||||
margin-top: 24px;
|
margin-bottom: 24px;
|
||||||
transition: all 0.3s ease;
|
padding: 6px;
|
||||||
|
border-radius: 999px;
|
||||||
|
background: rgba(255, 255, 255, 0.58);
|
||||||
|
transition: transform 0.26s ease, background 0.26s ease, box-shadow 0.26s ease;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
transform: scale(1.05);
|
background: #fff;
|
||||||
|
box-shadow: 0 12px 22px rgba(32, 45, 84, 0.1);
|
||||||
|
transform: translateY(-1px);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,30 +2,32 @@
|
|||||||
@import '@/styles/token';
|
@import '@/styles/token';
|
||||||
|
|
||||||
.user-icon {
|
.user-icon {
|
||||||
margin-bottom: @marginXs;
|
margin-bottom: 12px;
|
||||||
color: @navFirstMenuTextSecondary;
|
color: @navFirstMenuTextSecondary;
|
||||||
transition: background-color 0.3s, color 0.3s;
|
transition: transform 0.26s ease, color 0.26s ease;
|
||||||
user-select: none;
|
user-select: none;
|
||||||
|
|
||||||
.active-icon-container {
|
.active-icon-container {
|
||||||
color: @navFirstMenuTextSecondary;
|
color: @navFirstMenuTextSecondary;
|
||||||
display: flex;
|
display: flex;
|
||||||
width: 40px;
|
width: 58px;
|
||||||
height: 40px;
|
height: 58px;
|
||||||
padding: @paddingXs;
|
padding: 0;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
border-radius: @borderRadius;
|
border-radius: 14px;
|
||||||
transition: all 0.3s ease;
|
font-size: 24px;
|
||||||
|
transition: background 0.26s ease, box-shadow 0.26s ease, color 0.26s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
color: @colorTextSecondary;
|
color: @colorPrimary;
|
||||||
|
transform: translateY(-1px);
|
||||||
|
|
||||||
.active-icon-container {
|
.active-icon-container {
|
||||||
color: @colorTextSecondary;
|
color: @colorPrimary;
|
||||||
background: @navItemHoverBg;
|
background: #fff;
|
||||||
box-shadow: @navShadow;
|
box-shadow: 0 12px 22px rgba(32, 45, 84, 0.1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -35,11 +37,12 @@
|
|||||||
|
|
||||||
:global {
|
:global {
|
||||||
.ant-badge-count {
|
.ant-badge-count {
|
||||||
top: 10px;
|
top: 8px;
|
||||||
right: 10px;
|
right: 8px;
|
||||||
background: @colorError;
|
background: @colorError;
|
||||||
color: @colorWhite;
|
color: @colorWhite;
|
||||||
border: @lineWidth solid @navBorderColor;
|
border: 2px solid #fff;
|
||||||
|
box-shadow: 0 4px 10px rgba(32, 45, 84, 0.12);
|
||||||
}
|
}
|
||||||
|
|
||||||
sup {
|
sup {
|
||||||
@@ -53,8 +56,8 @@
|
|||||||
.ant-badge-multiple-words {
|
.ant-badge-multiple-words {
|
||||||
//line-height: 12px;
|
//line-height: 12px;
|
||||||
padding: 0 2px !important;
|
padding: 0 2px !important;
|
||||||
top: 8px;
|
top: 6px;
|
||||||
right: 8px;
|
right: 6px;
|
||||||
font-size: 10px;
|
font-size: 10px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,21 +2,24 @@
|
|||||||
@import '@/styles/token';
|
@import '@/styles/token';
|
||||||
|
|
||||||
.mobile-container {
|
.mobile-container {
|
||||||
box-shadow: 12px 0 24px -4px rgba(0, 0, 0, 12%);
|
box-shadow: 12px 0 28px rgba(32, 45, 84, 12%);
|
||||||
border-radius: 0 @borderRadiusLg @borderRadiusLg 0;
|
border-radius: 0 8px 8px 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.container {
|
.container {
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
// background-color: @colorBgLayout;
|
background: var(--xagi-layout-bg-secondary, #f2f5fb);
|
||||||
|
border-right: @lineWidth solid var(--xagi-layout-border-primary);
|
||||||
|
height: 100%;
|
||||||
|
|
||||||
.first-menus {
|
.first-menus {
|
||||||
// 使用动态导航宽度token
|
// 使用动态导航宽度token
|
||||||
width: @navFirstMenuWidth;
|
width: @navFirstMenuWidth;
|
||||||
padding-top: 16px;
|
padding-top: 22px;
|
||||||
background: @navFirstMenuBg;
|
background: @navFirstMenuBg;
|
||||||
color: @navFirstMenuText;
|
color: @navFirstMenuText;
|
||||||
box-shadow: @navFirstMenuShadow;
|
box-shadow: @navFirstMenuShadow;
|
||||||
|
border-right: @lineWidth solid var(--xagi-layout-border-primary);
|
||||||
transition: width 0.3s ease, background-color 0.3s ease;
|
transition: width 0.3s ease, background-color 0.3s ease;
|
||||||
|
|
||||||
// 风格2适配
|
// 风格2适配
|
||||||
@@ -29,16 +32,16 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
padding-top: @padding;
|
padding-top: 22px;
|
||||||
// width: 200px;
|
// width: 200px;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
border-left: @lineWidth solid var(--xagi-layout-border-primary);
|
border-left: 0;
|
||||||
transition: width 0.3s ease-in-out, opacity 0.3s ease-in-out,
|
transition: width 0.3s ease-in-out, opacity 0.3s ease-in-out,
|
||||||
padding-left 0.3s ease-in-out, background-color 0.3s ease;
|
padding-left 0.3s ease-in-out, background-color 0.3s ease;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
position: relative;
|
position: relative;
|
||||||
background: @navSecondMenuBg;
|
background: @navSecondMenuBg;
|
||||||
border-radius: @borderRadiusLg 0 0 @borderRadiusLg;
|
border-radius: 0;
|
||||||
|
|
||||||
.nav-menus-scroll {
|
.nav-menus-scroll {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
@@ -46,20 +49,639 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.integral-footer {
|
.integral-footer {
|
||||||
padding: @marginSm 0 0;
|
padding: @marginSm 0 18px;
|
||||||
margin-right: @margin;
|
margin-right: 18px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.menu-title {
|
.menu-title {
|
||||||
|
font-size: 20px !important;
|
||||||
|
line-height: 28px !important;
|
||||||
|
font-weight: 600 !important;
|
||||||
color: @navSecondMenuText;
|
color: @navSecondMenuText;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 风格2下的二级导航背景适配
|
// 风格2下的二级导航背景适配
|
||||||
.xagi-nav-style2 & {
|
.xagi-nav-style2 & {
|
||||||
background: var(--xagi-color-bg-container);
|
background: var(--xagi-layout-bg-container, #ffffff);
|
||||||
|
box-shadow: 10px 0 24px rgba(32, 45, 84, 0.05);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.topMenuBar {
|
||||||
|
position: relative;
|
||||||
|
z-index: 2;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
width: 100%;
|
||||||
|
height: 64px;
|
||||||
|
min-height: 64px;
|
||||||
|
padding: 0 20px;
|
||||||
|
border-bottom: @lineWidth solid var(--xagi-layout-border-primary);
|
||||||
|
background: var(--xagi-layout-bg-container, #fff);
|
||||||
|
box-shadow: 0 1px 0 rgba(32, 45, 84, 0.03);
|
||||||
|
}
|
||||||
|
|
||||||
|
.topLogo,
|
||||||
|
.verticalLogo {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.topLogo {
|
||||||
|
min-width: 226px;
|
||||||
|
padding-right: 18px;
|
||||||
|
|
||||||
|
.brandLogo {
|
||||||
|
width: 36px;
|
||||||
|
height: 36px;
|
||||||
|
border-radius: 9px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.brandText {
|
||||||
|
margin-left: 10px;
|
||||||
|
font-size: 18px !important;
|
||||||
|
font-weight: 600;
|
||||||
|
line-height: 24px;
|
||||||
|
letter-spacing: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.brandLogo {
|
||||||
|
width: 44px;
|
||||||
|
height: 44px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
object-fit: contain;
|
||||||
|
border-radius: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.brandText {
|
||||||
|
margin-left: 12px;
|
||||||
|
color: var(--xagi-layout-text-primary, #25283f) !important;
|
||||||
|
font-size: 20px !important;
|
||||||
|
font-weight: 600;
|
||||||
|
line-height: 28px;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.topMenuScroll {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
|
height: 100%;
|
||||||
|
gap: 6px;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.topMenuItem,
|
||||||
|
.topActionButton,
|
||||||
|
.verticalMenuItem {
|
||||||
|
border: 0;
|
||||||
|
background: transparent;
|
||||||
|
color: var(--xagi-layout-text-secondary, #4d5876);
|
||||||
|
cursor: pointer;
|
||||||
|
transition: color 0.2s ease, background 0.2s ease, box-shadow 0.2s ease,
|
||||||
|
transform 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.topMenuItem {
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
height: 40px;
|
||||||
|
max-width: 168px;
|
||||||
|
padding: 0 10px;
|
||||||
|
border-radius: 8px;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 500;
|
||||||
|
line-height: 20px;
|
||||||
|
white-space: nowrap;
|
||||||
|
|
||||||
|
&:hover,
|
||||||
|
&.active {
|
||||||
|
color: @colorPrimary;
|
||||||
|
background: rgba(93, 135, 255, 0.08);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.active::after {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
left: 18px;
|
||||||
|
right: 18px;
|
||||||
|
bottom: -12px;
|
||||||
|
height: 2px;
|
||||||
|
border-radius: 2px;
|
||||||
|
background: @colorPrimary;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.topMenuIcon,
|
||||||
|
.verticalMenuIcon {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
flex-shrink: 0;
|
||||||
|
font-size: 22px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.topMenuIcon {
|
||||||
|
width: 18px;
|
||||||
|
font-size: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.verticalMenuIcon {
|
||||||
|
width: 18px;
|
||||||
|
font-size: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.topMenuText,
|
||||||
|
.verticalMenuText {
|
||||||
|
min-width: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
|
||||||
|
.topMenuText {
|
||||||
|
margin-left: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.topMenuArrow,
|
||||||
|
.verticalMenuArrow {
|
||||||
|
flex-shrink: 0;
|
||||||
|
margin-left: 7px;
|
||||||
|
font-size: 12px;
|
||||||
|
color: var(--xagi-layout-text-tertiary, #7f8aa8);
|
||||||
|
}
|
||||||
|
|
||||||
|
.topMenuArrow {
|
||||||
|
font-size: 11px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.verticalMenuArrow {
|
||||||
|
font-size: 11px;
|
||||||
|
color: var(--xagi-layout-text-tertiary, #8a92a6);
|
||||||
|
}
|
||||||
|
|
||||||
|
.topActions {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
flex-shrink: 0;
|
||||||
|
gap: 8px;
|
||||||
|
padding-left: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.topActionButton {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
width: 36px;
|
||||||
|
height: 36px;
|
||||||
|
border-radius: 8px;
|
||||||
|
font-size: 19px;
|
||||||
|
|
||||||
|
&:hover,
|
||||||
|
&.active {
|
||||||
|
color: @colorPrimary;
|
||||||
|
background: rgba(93, 135, 255, 0.1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.topDropdownMenu {
|
||||||
|
width: 236px;
|
||||||
|
max-height: 68vh;
|
||||||
|
padding: 8px;
|
||||||
|
overflow: hidden auto;
|
||||||
|
border: @lineWidth solid var(--xagi-layout-border-secondary);
|
||||||
|
border-radius: 8px;
|
||||||
|
background: var(--xagi-layout-bg-container, #fff);
|
||||||
|
box-shadow: 0 16px 42px rgba(32, 45, 84, 0.14);
|
||||||
|
}
|
||||||
|
|
||||||
|
.topDropdownGroup {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
margin-bottom: 2px;
|
||||||
|
|
||||||
|
&:last-child {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.topDropdownItem {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
width: 100%;
|
||||||
|
height: 36px;
|
||||||
|
padding: 0 10px;
|
||||||
|
border: 0;
|
||||||
|
border-radius: 6px;
|
||||||
|
background: transparent;
|
||||||
|
color: var(--xagi-layout-text-secondary, #626a80);
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 500;
|
||||||
|
line-height: 20px;
|
||||||
|
text-align: left;
|
||||||
|
transition: color 0.2s ease, background 0.2s ease;
|
||||||
|
|
||||||
|
&:hover,
|
||||||
|
&.active {
|
||||||
|
color: @colorPrimary;
|
||||||
|
background: rgba(93, 135, 255, 0.08);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.open {
|
||||||
|
color: var(--xagi-layout-text-secondary, #626a80);
|
||||||
|
|
||||||
|
.topDropdownArrow {
|
||||||
|
transform: rotate(180deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.topDropdownLevel1,
|
||||||
|
.topDropdownLevel2 {
|
||||||
|
height: 32px;
|
||||||
|
color: var(--xagi-layout-text-secondary, #677086);
|
||||||
|
font-weight: 400;
|
||||||
|
}
|
||||||
|
|
||||||
|
.topDropdownLevel1 {
|
||||||
|
padding-left: 34px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.topDropdownLevel2 {
|
||||||
|
padding-left: 46px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.topDropdownIcon {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
width: 18px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
font-size: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.topDropdownText {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
|
margin-left: 9px;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.topDropdownBullet {
|
||||||
|
width: 3px;
|
||||||
|
height: 3px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: currentColor;
|
||||||
|
opacity: 0.45;
|
||||||
|
}
|
||||||
|
|
||||||
|
.topDropdownArrow {
|
||||||
|
flex-shrink: 0;
|
||||||
|
margin-left: 8px;
|
||||||
|
color: var(--xagi-layout-text-tertiary, #8a92a6);
|
||||||
|
font-size: 11px;
|
||||||
|
transition: transform 0.2s ease, color 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.topDropdownChildren {
|
||||||
|
padding: 2px 0 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.verticalMenu {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
width: 252px;
|
||||||
|
height: 100%;
|
||||||
|
padding: 18px 12px 14px;
|
||||||
|
border-right: @lineWidth solid var(--xagi-layout-border-primary);
|
||||||
|
background: var(--xagi-layout-bg-container, #fff);
|
||||||
|
}
|
||||||
|
|
||||||
|
.verticalLogo {
|
||||||
|
height: 42px;
|
||||||
|
margin-bottom: 18px;
|
||||||
|
|
||||||
|
.brandLogo {
|
||||||
|
width: 40px;
|
||||||
|
height: 40px;
|
||||||
|
border-radius: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.brandText {
|
||||||
|
margin-left: 12px;
|
||||||
|
color: var(--xagi-layout-text-primary, #25283f) !important;
|
||||||
|
font-size: 18px !important;
|
||||||
|
font-weight: 600;
|
||||||
|
line-height: 24px;
|
||||||
|
letter-spacing: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.verticalMenuList {
|
||||||
|
flex: 1;
|
||||||
|
min-height: 0;
|
||||||
|
padding-right: 2px;
|
||||||
|
overflow: hidden auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.verticalMenuGroup {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
margin-bottom: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.verticalMenuItem {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
width: 100%;
|
||||||
|
height: 40px;
|
||||||
|
padding: 0 12px;
|
||||||
|
margin: 0;
|
||||||
|
border-radius: 8px;
|
||||||
|
text-align: left;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 500;
|
||||||
|
line-height: 20px;
|
||||||
|
color: var(--xagi-layout-text-secondary, #626a80);
|
||||||
|
|
||||||
|
&:hover,
|
||||||
|
&.active {
|
||||||
|
color: @colorPrimary;
|
||||||
|
background: var(--xagi-menu-item-active-bg, rgba(93, 135, 255, 0.09));
|
||||||
|
box-shadow: var(--xagi-menu-item-active-shadow, none);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.open {
|
||||||
|
color: @colorPrimary;
|
||||||
|
|
||||||
|
.verticalMenuArrow {
|
||||||
|
transform: rotate(180deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.verticalMenuLevel1,
|
||||||
|
.verticalMenuLevel2,
|
||||||
|
.verticalMenuLevel3 {
|
||||||
|
height: 34px;
|
||||||
|
color: var(--xagi-layout-text-secondary, #677086);
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 400;
|
||||||
|
line-height: 20px;
|
||||||
|
|
||||||
|
&:hover,
|
||||||
|
&.active {
|
||||||
|
background: var(--xagi-menu-item-hover-bg, rgba(93, 135, 255, 0.07));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.verticalMenuLevel1 {
|
||||||
|
padding-left: 42px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.verticalMenuLevel2 {
|
||||||
|
padding-left: 54px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.verticalMenuLevel3 {
|
||||||
|
padding-left: 66px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.verticalMenuText {
|
||||||
|
flex: 1;
|
||||||
|
margin-left: 10px;
|
||||||
|
min-width: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.verticalMenuLevel1,
|
||||||
|
.verticalMenuLevel2,
|
||||||
|
.verticalMenuLevel3 {
|
||||||
|
.verticalMenuText {
|
||||||
|
margin-left: 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.verticalSubMenu {
|
||||||
|
overflow: hidden;
|
||||||
|
padding: 4px 0 2px;
|
||||||
|
transition: max-height 0.24s cubic-bezier(0.4, 0, 0.2, 1), opacity 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.verticalMenuBullet {
|
||||||
|
width: 3px;
|
||||||
|
height: 3px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: currentColor;
|
||||||
|
opacity: 0.45;
|
||||||
|
}
|
||||||
|
|
||||||
|
.verticalFooter {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 10px;
|
||||||
|
padding-top: 12px;
|
||||||
|
margin-top: 12px;
|
||||||
|
border-top: @lineWidth solid var(--xagi-layout-border-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.verticalActions {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.verticalActionButton {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
width: 36px;
|
||||||
|
height: 36px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
border: 0;
|
||||||
|
border-radius: 8px;
|
||||||
|
background: transparent;
|
||||||
|
color: var(--xagi-layout-text-secondary, #5d6378);
|
||||||
|
cursor: pointer;
|
||||||
|
transition: color 0.2s ease, background 0.2s ease;
|
||||||
|
|
||||||
|
&:hover,
|
||||||
|
&.active {
|
||||||
|
color: @colorPrimary;
|
||||||
|
background: rgba(93, 135, 255, 0.11);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.verticalActionButton {
|
||||||
|
font-size: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.verticalUserAvatar {
|
||||||
|
width: 36px;
|
||||||
|
height: 36px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
|
||||||
|
> div {
|
||||||
|
width: 36px;
|
||||||
|
height: 36px;
|
||||||
|
border-width: 1px;
|
||||||
|
box-shadow: 0 8px 18px rgba(32, 45, 84, 0.1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.menuIconImg {
|
||||||
|
width: 22px;
|
||||||
|
height: 22px;
|
||||||
|
object-fit: contain;
|
||||||
|
}
|
||||||
|
|
||||||
|
.topMenuIcon .menuIconImg,
|
||||||
|
.topDropdownIcon .menuIconImg {
|
||||||
|
width: 18px;
|
||||||
|
height: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.verticalMenuIcon .menuIconImg {
|
||||||
|
width: 18px;
|
||||||
|
height: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mixedSide {
|
||||||
|
.nav-menus {
|
||||||
|
border-right: @lineWidth solid var(--xagi-layout-border-primary);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.menu-layout-horizontal {
|
||||||
|
border-right: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.menu-layout-vertical {
|
||||||
|
border-right: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.railReloadButton {
|
||||||
|
width: 36px;
|
||||||
|
height: 36px;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
color: var(--xagi-layout-text-secondary, #626a80);
|
||||||
|
border-radius: 8px !important;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
color: @colorPrimary !important;
|
||||||
|
background: var(
|
||||||
|
--xagi-menu-item-hover-bg,
|
||||||
|
rgba(93, 135, 255, 0.07)
|
||||||
|
) !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
:global(html[data-theme='light'][data-menu-style='design']) {
|
||||||
|
.menu-layout-vertical .verticalMenu,
|
||||||
|
.mixedSide .nav-menus {
|
||||||
|
--xagi-layout-text-primary: #1f2337;
|
||||||
|
--xagi-layout-text-secondary: #626a80;
|
||||||
|
--xagi-layout-text-tertiary: #8a92a6;
|
||||||
|
--xagi-layout-second-menu-text-color: #1f2337;
|
||||||
|
--xagi-layout-second-menu-text-color-secondary: #626a80;
|
||||||
|
--xagi-menu-item-active-bg: #ffffff;
|
||||||
|
--xagi-menu-item-hover-bg: rgba(255, 255, 255, 0.72);
|
||||||
|
--xagi-menu-item-active-shadow: 0 10px 20px rgba(32, 45, 84, 0.1);
|
||||||
|
--xagi-menu-item-hover-shadow: 0 8px 16px rgba(32, 45, 84, 0.06);
|
||||||
|
|
||||||
|
background: #f2f5fb;
|
||||||
|
border-color: rgba(91, 105, 135, 0.12);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
:global(html[data-theme='light'][data-menu-style='light']) {
|
||||||
|
.menu-layout-vertical .verticalMenu,
|
||||||
|
.mixedSide .nav-menus {
|
||||||
|
--xagi-layout-text-primary: #1f2337;
|
||||||
|
--xagi-layout-text-secondary: #5d6378;
|
||||||
|
--xagi-layout-text-tertiary: #8790a5;
|
||||||
|
--xagi-layout-second-menu-text-color: #1f2337;
|
||||||
|
--xagi-layout-second-menu-text-color-secondary: #5d6378;
|
||||||
|
--xagi-menu-item-active-bg: rgba(93, 135, 255, 0.1);
|
||||||
|
--xagi-menu-item-hover-bg: rgba(93, 135, 255, 0.07);
|
||||||
|
--xagi-menu-item-active-shadow: none;
|
||||||
|
--xagi-menu-item-hover-shadow: none;
|
||||||
|
|
||||||
|
background: #ffffff;
|
||||||
|
border-color: rgba(91, 105, 135, 0.12);
|
||||||
|
box-shadow: 10px 0 24px rgba(32, 45, 84, 0.04);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
:global(html[data-theme='light'][data-menu-style='dark']) {
|
||||||
|
.menu-layout-vertical .verticalMenu,
|
||||||
|
.mixedSide .nav-menus {
|
||||||
|
--xagi-layout-text-primary: rgba(255, 255, 255, 0.95);
|
||||||
|
--xagi-layout-text-secondary: rgba(255, 255, 255, 0.78);
|
||||||
|
--xagi-layout-text-tertiary: rgba(255, 255, 255, 0.56);
|
||||||
|
--xagi-layout-second-menu-text-color: rgba(255, 255, 255, 0.95);
|
||||||
|
--xagi-layout-second-menu-text-color-secondary: rgba(255, 255, 255, 0.78);
|
||||||
|
--xagi-menu-item-active-bg: rgba(93, 135, 255, 0.2);
|
||||||
|
--xagi-menu-item-hover-bg: rgba(255, 255, 255, 0.08);
|
||||||
|
--xagi-menu-item-active-shadow: none;
|
||||||
|
--xagi-menu-item-hover-shadow: none;
|
||||||
|
|
||||||
|
background: #111827;
|
||||||
|
border-color: rgba(255, 255, 255, 0.08);
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.menu-layout-vertical .verticalFooter {
|
||||||
|
border-color: rgba(255, 255, 255, 0.08);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
:global(html[data-theme='dark']) {
|
||||||
|
.topMenuBar,
|
||||||
|
.verticalMenu {
|
||||||
|
background: var(--xagi-layout-bg-container, #111620);
|
||||||
|
border-color: var(
|
||||||
|
--xagi-layout-border-secondary,
|
||||||
|
rgba(255, 255, 255, 0.08)
|
||||||
|
);
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.topMenuItem,
|
||||||
|
.topActionButton,
|
||||||
|
.topDropdownItem,
|
||||||
|
.verticalMenuItem {
|
||||||
|
color: var(--xagi-layout-text-secondary, rgba(255, 255, 255, 0.85));
|
||||||
|
|
||||||
|
&:hover,
|
||||||
|
&.active {
|
||||||
|
color: @colorPrimary;
|
||||||
|
background: rgba(93, 135, 255, 0.18);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.topDropdownMenu {
|
||||||
|
background: var(--xagi-layout-bg-container, #111620);
|
||||||
|
border-color: var(
|
||||||
|
--xagi-layout-border-secondary,
|
||||||
|
rgba(255, 255, 255, 0.08)
|
||||||
|
);
|
||||||
|
box-shadow: 0 16px 42px rgba(0, 0, 0, 0.24);
|
||||||
|
}
|
||||||
|
}
|
||||||
// 风格1下的二级导航样式适配
|
// 风格1下的二级导航样式适配
|
||||||
:global(.xagi-nav-style1) {
|
:global(.xagi-nav-style1) {
|
||||||
.container {
|
.container {
|
||||||
|
|||||||
@@ -8,12 +8,13 @@
|
|||||||
* 3. 复用现有的 Header、User、UserOperateArea、TabItem、SecondMenuItem 等组件
|
* 3. 复用现有的 Header、User、UserOperateArea、TabItem、SecondMenuItem 等组件
|
||||||
*/
|
*/
|
||||||
import HoverScrollbar from '@/components/base/HoverScrollbar';
|
import HoverScrollbar from '@/components/base/HoverScrollbar';
|
||||||
|
import SvgIcon from '@/components/base/SvgIcon';
|
||||||
import ConditionRender from '@/components/ConditionRender';
|
import ConditionRender from '@/components/ConditionRender';
|
||||||
import { NAVIGATION_LAYOUT_SIZES } from '@/constants/layout.constants';
|
import { NAVIGATION_LAYOUT_SIZES } from '@/constants/layout.constants';
|
||||||
import { useUnifiedTheme } from '@/hooks/useUnifiedTheme';
|
import { useUnifiedTheme } from '@/hooks/useUnifiedTheme';
|
||||||
import { dict } from '@/services/i18nRuntime';
|
import { dict } from '@/services/i18nRuntime';
|
||||||
import type { MenuItemDto } from '@/types/interfaces/menu';
|
import type { MenuItemDto } from '@/types/interfaces/menu';
|
||||||
import { theme, Typography } from 'antd';
|
import { Badge, Button, Popover, theme, Typography } from 'antd';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import React, {
|
import React, {
|
||||||
useCallback,
|
useCallback,
|
||||||
@@ -27,9 +28,11 @@ import DynamicSecondMenu from './DynamicSecondMenu';
|
|||||||
import DynamicTabs from './DynamicTabs';
|
import DynamicTabs from './DynamicTabs';
|
||||||
// 复用原有组件
|
// 复用原有组件
|
||||||
import CreditsBalance from '@/components/business-component/CreditsBalance';
|
import CreditsBalance from '@/components/business-component/CreditsBalance';
|
||||||
|
import { ReloadOutlined } from '@ant-design/icons';
|
||||||
import CollapseButton from './CollapseButton';
|
import CollapseButton from './CollapseButton';
|
||||||
import Header from './Header';
|
import Header from './Header';
|
||||||
import User from './User';
|
import User from './User';
|
||||||
|
import UserAvatar from './User/UserAvatar';
|
||||||
import UserOperateArea from './UserOperateArea';
|
import UserOperateArea from './UserOperateArea';
|
||||||
// 复用原有样式
|
// 复用原有样式
|
||||||
import { PATH_URL } from '@/constants/home.constants';
|
import { PATH_URL } from '@/constants/home.constants';
|
||||||
@@ -41,13 +44,24 @@ import {
|
|||||||
OTHER_MENU_CODES,
|
OTHER_MENU_CODES,
|
||||||
} from '@/constants/menus.constants';
|
} from '@/constants/menus.constants';
|
||||||
import useConversation from '@/hooks/useConversation';
|
import useConversation from '@/hooks/useConversation';
|
||||||
import { ThemeNavigationStyleType } from '@/types/enums/theme';
|
import { RoleEnum } from '@/types/enums/common';
|
||||||
|
import { AllowDevelopEnum, SpaceTypeEnum } from '@/types/enums/space';
|
||||||
|
import {
|
||||||
|
ThemeLayoutColorStyle,
|
||||||
|
ThemeMenuLayoutType,
|
||||||
|
ThemeMenuStyleType,
|
||||||
|
ThemeNavigationStyleType,
|
||||||
|
} from '@/types/enums/theme';
|
||||||
import EcosystemMarketSection from './EcosystemMarketSection';
|
import EcosystemMarketSection from './EcosystemMarketSection';
|
||||||
import HomeSection from './HomeSection';
|
import HomeSection from './HomeSection';
|
||||||
import styles from './index.less';
|
import styles from './index.less';
|
||||||
import SpaceSection from './SpaceSection';
|
import SpaceSection from './SpaceSection';
|
||||||
import SquareSection from './SquareSection';
|
import SquareSection from './SquareSection';
|
||||||
import { handleOpenUrl, normalizeMenuPathname } from './utils';
|
import {
|
||||||
|
handleOpenUrl,
|
||||||
|
normalizeMenuPathname,
|
||||||
|
updatePathUrlToLocalStorage,
|
||||||
|
} from './utils';
|
||||||
|
|
||||||
const cx = classNames.bind(styles);
|
const cx = classNames.bind(styles);
|
||||||
|
|
||||||
@@ -56,6 +70,8 @@ export interface DynamicMenusLayoutProps {
|
|||||||
overrideContainerStyle?: React.CSSProperties;
|
overrideContainerStyle?: React.CSSProperties;
|
||||||
/** 是否为移动端 */
|
/** 是否为移动端 */
|
||||||
isMobile?: boolean;
|
isMobile?: boolean;
|
||||||
|
/** 顶部/侧边渲染区域 */
|
||||||
|
region?: 'auto' | 'top' | 'side';
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -64,20 +80,25 @@ export interface DynamicMenusLayoutProps {
|
|||||||
const DynamicMenusLayout: React.FC<DynamicMenusLayoutProps> = ({
|
const DynamicMenusLayout: React.FC<DynamicMenusLayoutProps> = ({
|
||||||
overrideContainerStyle,
|
overrideContainerStyle,
|
||||||
isMobile = false,
|
isMobile = false,
|
||||||
|
region = 'auto',
|
||||||
}) => {
|
}) => {
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
const params = useParams();
|
const params = useParams();
|
||||||
const { token } = theme.useToken();
|
const { token } = theme.useToken();
|
||||||
const { navigationStyle, layoutStyle } = useUnifiedTheme();
|
const { navigationStyle, layoutStyle, menuLayout, menuStyle, basicConfig } =
|
||||||
|
useUnifiedTheme();
|
||||||
const {
|
const {
|
||||||
// showHoverMenu,
|
// showHoverMenu,
|
||||||
isSecondMenuCollapsed,
|
isSecondMenuCollapsed,
|
||||||
setOpenMessage,
|
setOpenMessage,
|
||||||
handleCloseMobileMenu,
|
handleCloseMobileMenu,
|
||||||
|
unreadCount,
|
||||||
} = useModel('layout');
|
} = useModel('layout');
|
||||||
const { firstLevelMenus, otherMenus } = useModel('menuModel');
|
const { firstLevelMenus, otherMenus, getSecondLevelMenus } =
|
||||||
|
useModel('menuModel');
|
||||||
|
|
||||||
const { refreshUserInfo } = useModel('userInfo');
|
const { refreshUserInfo, userInfo } = useModel('userInfo');
|
||||||
|
const { currentSpaceInfo, spaceList } = useModel('spaceModel');
|
||||||
|
|
||||||
// 工作空间下的最近编辑和开发收藏
|
// 工作空间下的最近编辑和开发收藏
|
||||||
const { runEdit } = useModel('devCollectAgent');
|
const { runEdit } = useModel('devCollectAgent');
|
||||||
@@ -88,10 +109,15 @@ const DynamicMenusLayout: React.FC<DynamicMenusLayoutProps> = ({
|
|||||||
// 是否点击了新对话菜单,特殊处理,用于显示title时使用
|
// 是否点击了新对话菜单,特殊处理,用于显示title时使用
|
||||||
const [isClickNewConversation, setIsClickNewConversation] =
|
const [isClickNewConversation, setIsClickNewConversation] =
|
||||||
useState<boolean>(false);
|
useState<boolean>(false);
|
||||||
|
const [verticalExpandedMenus, setVerticalExpandedMenus] = useState<string[]>(
|
||||||
|
[],
|
||||||
|
);
|
||||||
|
|
||||||
// 创建智能体会话
|
// 创建智能体会话
|
||||||
const { handleCreateConversation } = useConversation();
|
const { handleCreateConversation } = useConversation();
|
||||||
const { tenantConfigInfo } = useModel('tenantConfigInfo');
|
const { tenantConfigInfo } = useModel('tenantConfigInfo');
|
||||||
|
const effectiveMenuLayout = isMobile ? ThemeMenuLayoutType.DUAL : menuLayout;
|
||||||
|
const isEnableSubscription = tenantConfigInfo?.enableSubscription !== 0;
|
||||||
|
|
||||||
// 是否点击菜单
|
// 是否点击菜单
|
||||||
const isClickMenu = useRef<boolean>(false);
|
const isClickMenu = useRef<boolean>(false);
|
||||||
@@ -401,7 +427,7 @@ const DynamicMenusLayout: React.FC<DynamicMenusLayoutProps> = ({
|
|||||||
runEdit({
|
runEdit({
|
||||||
size: 5,
|
size: 5,
|
||||||
});
|
});
|
||||||
}, []);
|
}, [runEdit]);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 递归查找第一个有 path 的子菜单
|
* 递归查找第一个有 path 的子菜单
|
||||||
@@ -428,6 +454,246 @@ const DynamicMenusLayout: React.FC<DynamicMenusLayoutProps> = ({
|
|||||||
[],
|
[],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const getVerticalMenuChildren = useCallback(
|
||||||
|
(menu: MenuItemDto): MenuItemDto[] => {
|
||||||
|
const modelChildren = menu.code ? getSecondLevelMenus(menu.code) : [];
|
||||||
|
const children = modelChildren.length
|
||||||
|
? modelChildren
|
||||||
|
: menu.children || [];
|
||||||
|
|
||||||
|
if (menu.code === 'workspace' && !isEnableSubscription) {
|
||||||
|
return children.filter((child) => child.code !== 'resource_pricing');
|
||||||
|
}
|
||||||
|
|
||||||
|
return children;
|
||||||
|
},
|
||||||
|
[getSecondLevelMenus, isEnableSubscription],
|
||||||
|
);
|
||||||
|
|
||||||
|
const isVerticalMenuVisible = useCallback(
|
||||||
|
(menu: MenuItemDto): boolean => {
|
||||||
|
if (
|
||||||
|
(currentSpaceInfo?.type === SpaceTypeEnum.Personal ||
|
||||||
|
currentSpaceInfo?.currentUserRole === RoleEnum.User) &&
|
||||||
|
menu.code === 'member_setting'
|
||||||
|
) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
currentSpaceInfo?.currentUserRole === RoleEnum.User &&
|
||||||
|
currentSpaceInfo?.allowDevelop === AllowDevelopEnum.Not_Allow &&
|
||||||
|
(menu.code === 'agent_dev' || menu.code === 'component_lib_dev')
|
||||||
|
) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
[currentSpaceInfo],
|
||||||
|
);
|
||||||
|
|
||||||
|
const extractSpaceIdFromPath = useCallback((path: string): string | null => {
|
||||||
|
if (!path) return null;
|
||||||
|
const match = path.split('?')[0].match(/\/space\/([^/]+)/);
|
||||||
|
return match ? match[1] : null;
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const resolveVerticalMenuPath = useCallback(
|
||||||
|
(path: string, parentCode?: string): string => {
|
||||||
|
if (!path || !path.includes(':')) {
|
||||||
|
return path;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (parentCode === 'workspace') {
|
||||||
|
let spaceId: string | null = null;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const pathUrl = localStorage.getItem(PATH_URL);
|
||||||
|
if (pathUrl) {
|
||||||
|
const pathUrlObj = JSON.parse(pathUrl) as Record<string, string>;
|
||||||
|
const workspacePath = pathUrlObj.workspace;
|
||||||
|
if (workspacePath) {
|
||||||
|
spaceId = extractSpaceIdFromPath(workspacePath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch {}
|
||||||
|
|
||||||
|
if (!spaceId && params?.spaceId) {
|
||||||
|
spaceId = String(params.spaceId);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!spaceId && spaceList?.length) {
|
||||||
|
spaceId = String(spaceList[0].id);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (spaceId) {
|
||||||
|
return path.replace(/:spaceId/g, spaceId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return path.replace(/:(\w+)/g, (_match, key) => {
|
||||||
|
const value = params?.[key];
|
||||||
|
return value ? String(value) : `:${key}`;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
[extractSpaceIdFromPath, params, spaceList],
|
||||||
|
);
|
||||||
|
|
||||||
|
const toggleVerticalMenu = useCallback(
|
||||||
|
(code?: string, ancestorCodes: string[] = []) => {
|
||||||
|
if (!code) return;
|
||||||
|
setVerticalExpandedMenus((prev) => {
|
||||||
|
if (prev.includes(code)) {
|
||||||
|
return prev.filter((item) => item !== code);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (basicConfig.uniqueOpened) {
|
||||||
|
return [...ancestorCodes, code].filter(Boolean);
|
||||||
|
}
|
||||||
|
|
||||||
|
return [...prev, code];
|
||||||
|
});
|
||||||
|
},
|
||||||
|
[basicConfig.uniqueOpened],
|
||||||
|
);
|
||||||
|
|
||||||
|
const getTopMenuCodeByChildCode = useCallback(
|
||||||
|
(menuCode?: string): string | undefined => {
|
||||||
|
if (!menuCode) return undefined;
|
||||||
|
return findFirstLevelCodeByMenuCode(menuCode) || menuCode;
|
||||||
|
},
|
||||||
|
[findFirstLevelCodeByMenuCode],
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleVerticalMenuClick = useCallback(
|
||||||
|
(menu: MenuItemDto, parentCode?: string, ancestorCodes: string[] = []) => {
|
||||||
|
const children = getVerticalMenuChildren(menu).filter(
|
||||||
|
isVerticalMenuVisible,
|
||||||
|
);
|
||||||
|
const hasChildren = children.length > 0;
|
||||||
|
const topCode = parentCode || getTopMenuCodeByChildCode(menu.code);
|
||||||
|
|
||||||
|
if (topCode) {
|
||||||
|
setActiveTab(topCode);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (menu.code === 'workspace' || topCode === 'workspace') {
|
||||||
|
handleRefreshEditAndCollect();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hasChildren) {
|
||||||
|
toggleVerticalMenu(menu.code, ancestorCodes);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
handleCloseMobileMenu();
|
||||||
|
setIsClickNewConversation(false);
|
||||||
|
|
||||||
|
if (menu.path?.includes('http')) {
|
||||||
|
handleOpenUrl(menu, topCode);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!menu.path) {
|
||||||
|
if (menu.code === 'new_conversation') {
|
||||||
|
handlerClick();
|
||||||
|
setIsClickNewConversation(true);
|
||||||
|
handleNewConversation();
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const resolvedPath = resolveVerticalMenuPath(menu.path, topCode);
|
||||||
|
|
||||||
|
if (!resolvedPath || resolvedPath.includes(':')) {
|
||||||
|
if (topCode === 'workspace') {
|
||||||
|
history.push('/space', { _t: Date.now(), menuCode: topCode });
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (topCode) {
|
||||||
|
updatePathUrlToLocalStorage(topCode, resolvedPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
history.push(resolvedPath, { _t: Date.now(), menuCode: menu.code });
|
||||||
|
},
|
||||||
|
[
|
||||||
|
getVerticalMenuChildren,
|
||||||
|
isVerticalMenuVisible,
|
||||||
|
getTopMenuCodeByChildCode,
|
||||||
|
handleRefreshEditAndCollect,
|
||||||
|
toggleVerticalMenu,
|
||||||
|
handleCloseMobileMenu,
|
||||||
|
resolveVerticalMenuPath,
|
||||||
|
handlerClick,
|
||||||
|
handleNewConversation,
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
|
const findVerticalMenuCodePath = useCallback(
|
||||||
|
(menus: MenuItemDto[], targetCode?: string): string[] => {
|
||||||
|
if (!targetCode) return [];
|
||||||
|
|
||||||
|
for (const menu of menus) {
|
||||||
|
const menuCode = menu.code || '';
|
||||||
|
|
||||||
|
if (menuCode === targetCode) {
|
||||||
|
return [menuCode];
|
||||||
|
}
|
||||||
|
|
||||||
|
const children = getVerticalMenuChildren(menu).filter(
|
||||||
|
isVerticalMenuVisible,
|
||||||
|
);
|
||||||
|
if (children.length) {
|
||||||
|
const childPath = findVerticalMenuCodePath(children, targetCode);
|
||||||
|
if (childPath.length) {
|
||||||
|
return [menuCode, ...childPath].filter(Boolean);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return [];
|
||||||
|
},
|
||||||
|
[getVerticalMenuChildren, isVerticalMenuVisible],
|
||||||
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (effectiveMenuLayout !== ThemeMenuLayoutType.VERTICAL) return;
|
||||||
|
|
||||||
|
const routeMenuCode = params?.menuCode || location.state?.menuCode;
|
||||||
|
const nextExpandedCodes = new Set<string>();
|
||||||
|
|
||||||
|
if (activeTab) {
|
||||||
|
nextExpandedCodes.add(activeTab);
|
||||||
|
}
|
||||||
|
|
||||||
|
const activePath = findVerticalMenuCodePath(firstLevelMenus, routeMenuCode);
|
||||||
|
activePath.slice(0, -1).forEach((code) => nextExpandedCodes.add(code));
|
||||||
|
|
||||||
|
if (!nextExpandedCodes.size) return;
|
||||||
|
|
||||||
|
setVerticalExpandedMenus((prev) => {
|
||||||
|
const merged = [...prev];
|
||||||
|
let changed = false;
|
||||||
|
nextExpandedCodes.forEach((code) => {
|
||||||
|
if (!merged.includes(code)) {
|
||||||
|
merged.push(code);
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return changed ? merged : prev;
|
||||||
|
});
|
||||||
|
}, [
|
||||||
|
activeTab,
|
||||||
|
effectiveMenuLayout,
|
||||||
|
findVerticalMenuCodePath,
|
||||||
|
firstLevelMenus,
|
||||||
|
location.state?.menuCode,
|
||||||
|
params?.menuCode,
|
||||||
|
]);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 点击一级菜单
|
* 点击一级菜单
|
||||||
*/
|
*/
|
||||||
@@ -598,10 +864,22 @@ const DynamicMenusLayout: React.FC<DynamicMenusLayoutProps> = ({
|
|||||||
if (isMobile) {
|
if (isMobile) {
|
||||||
return NAVIGATION_LAYOUT_SIZES.FIRST_MENU_WIDTH.STYLE1;
|
return NAVIGATION_LAYOUT_SIZES.FIRST_MENU_WIDTH.STYLE1;
|
||||||
}
|
}
|
||||||
|
if (effectiveMenuLayout === ThemeMenuLayoutType.VERTICAL) {
|
||||||
|
return basicConfig.menuOpenWidth;
|
||||||
|
}
|
||||||
return navigationStyle === 'style2'
|
return navigationStyle === 'style2'
|
||||||
? NAVIGATION_LAYOUT_SIZES.FIRST_MENU_WIDTH.STYLE2
|
? NAVIGATION_LAYOUT_SIZES.FIRST_MENU_WIDTH.STYLE2
|
||||||
: NAVIGATION_LAYOUT_SIZES.FIRST_MENU_WIDTH.STYLE1;
|
: NAVIGATION_LAYOUT_SIZES.FIRST_MENU_WIDTH.STYLE1;
|
||||||
}, [navigationStyle, isMobile]);
|
}, [
|
||||||
|
basicConfig.menuOpenWidth,
|
||||||
|
effectiveMenuLayout,
|
||||||
|
navigationStyle,
|
||||||
|
isMobile,
|
||||||
|
]);
|
||||||
|
|
||||||
|
const handleReloadPage = useCallback(() => {
|
||||||
|
window.location.reload();
|
||||||
|
}, []);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 一级导航背景
|
* 一级导航背景
|
||||||
@@ -610,7 +888,7 @@ const DynamicMenusLayout: React.FC<DynamicMenusLayoutProps> = ({
|
|||||||
if (isMobile) {
|
if (isMobile) {
|
||||||
return `var(--xagi-background-image) ${token.colorBgContainer}`;
|
return `var(--xagi-background-image) ${token.colorBgContainer}`;
|
||||||
}
|
}
|
||||||
return 'transparent';
|
return 'var(--xagi-layout-bg-secondary, #f2f5fb)';
|
||||||
}, [isMobile, token.colorBgContainer]);
|
}, [isMobile, token.colorBgContainer]);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -621,10 +899,64 @@ const DynamicMenusLayout: React.FC<DynamicMenusLayoutProps> = ({
|
|||||||
return token.colorBgContainer;
|
return token.colorBgContainer;
|
||||||
}
|
}
|
||||||
return navigationStyle === 'style2'
|
return navigationStyle === 'style2'
|
||||||
? 'var(--xagi-layout-bg-container, rgba(255, 255, 255, 0.95))'
|
? 'var(--xagi-layout-bg-container, #ffffff)'
|
||||||
: 'transparent';
|
: 'var(--xagi-layout-bg-secondary, #f2f5fb)';
|
||||||
}, [isMobile, navigationStyle, token.colorBgContainer]);
|
}, [isMobile, navigationStyle, token.colorBgContainer]);
|
||||||
|
|
||||||
|
const isMixedSideNavigation =
|
||||||
|
region === 'side' && effectiveMenuLayout === ThemeMenuLayoutType.MIXED;
|
||||||
|
|
||||||
|
const secondMenuVisualStyle = useMemo(() => {
|
||||||
|
const style = {
|
||||||
|
backgroundColor: secondaryBackgroundColor,
|
||||||
|
} as React.CSSProperties & Record<string, string>;
|
||||||
|
|
||||||
|
if (!isMixedSideNavigation) {
|
||||||
|
return style;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (layoutStyle === ThemeLayoutColorStyle.DARK) {
|
||||||
|
style.backgroundColor =
|
||||||
|
layoutStyle === ThemeLayoutColorStyle.DARK
|
||||||
|
? 'var(--xagi-layout-bg-container, #111620)'
|
||||||
|
: '#111827';
|
||||||
|
style['--xagi-layout-second-menu-text-color'] =
|
||||||
|
'rgba(255, 255, 255, 0.95)';
|
||||||
|
style['--xagi-layout-second-menu-text-color-secondary'] =
|
||||||
|
'rgba(255, 255, 255, 0.78)';
|
||||||
|
style['--xagi-layout-text-tertiary'] = 'rgba(255, 255, 255, 0.56)';
|
||||||
|
style['--xagi-menu-item-active-bg'] = 'rgba(93, 135, 255, 0.2)';
|
||||||
|
style['--xagi-menu-item-hover-bg'] = 'rgba(255, 255, 255, 0.08)';
|
||||||
|
style['--xagi-menu-item-active-shadow'] = 'none';
|
||||||
|
style['--xagi-menu-item-hover-shadow'] = 'none';
|
||||||
|
return style;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (menuStyle === ThemeMenuStyleType.LIGHT) {
|
||||||
|
style.backgroundColor = '#ffffff';
|
||||||
|
style['--xagi-layout-second-menu-text-color'] = '#1f2337';
|
||||||
|
style['--xagi-layout-second-menu-text-color-secondary'] = '#5d6378';
|
||||||
|
style['--xagi-layout-text-tertiary'] = '#8790a5';
|
||||||
|
style['--xagi-menu-item-active-bg'] = 'rgba(93, 135, 255, 0.1)';
|
||||||
|
style['--xagi-menu-item-hover-bg'] = 'rgba(93, 135, 255, 0.07)';
|
||||||
|
style['--xagi-menu-item-active-shadow'] = 'none';
|
||||||
|
style['--xagi-menu-item-hover-shadow'] = 'none';
|
||||||
|
return style;
|
||||||
|
}
|
||||||
|
|
||||||
|
style.backgroundColor = '#f2f5fb';
|
||||||
|
style['--xagi-layout-second-menu-text-color'] = '#1f2337';
|
||||||
|
style['--xagi-layout-second-menu-text-color-secondary'] = '#626a80';
|
||||||
|
style['--xagi-layout-text-tertiary'] = '#8a92a6';
|
||||||
|
style['--xagi-menu-item-active-bg'] = '#ffffff';
|
||||||
|
style['--xagi-menu-item-hover-bg'] = 'rgba(255, 255, 255, 0.72)';
|
||||||
|
style['--xagi-menu-item-active-shadow'] =
|
||||||
|
'0 10px 20px rgba(32, 45, 84, 0.1)';
|
||||||
|
style['--xagi-menu-item-hover-shadow'] =
|
||||||
|
'0 8px 16px rgba(32, 45, 84, 0.06)';
|
||||||
|
return style;
|
||||||
|
}, [isMixedSideNavigation, layoutStyle, menuStyle, secondaryBackgroundColor]);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 导航容器样式类名
|
* 导航容器样式类名
|
||||||
*/
|
*/
|
||||||
@@ -634,9 +966,10 @@ const DynamicMenusLayout: React.FC<DynamicMenusLayoutProps> = ({
|
|||||||
'flex',
|
'flex',
|
||||||
`xagi-layout-${layoutStyle}`,
|
`xagi-layout-${layoutStyle}`,
|
||||||
`xagi-nav-${navigationStyle}`,
|
`xagi-nav-${navigationStyle}`,
|
||||||
|
styles[`menu-layout-${effectiveMenuLayout}`],
|
||||||
isMobile && styles['mobile-container'],
|
isMobile && styles['mobile-container'],
|
||||||
);
|
);
|
||||||
}, [layoutStyle, navigationStyle, isMobile]);
|
}, [layoutStyle, navigationStyle, effectiveMenuLayout, isMobile]);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 渲染二级菜单
|
* 渲染二级菜单
|
||||||
@@ -675,92 +1008,455 @@ const DynamicMenusLayout: React.FC<DynamicMenusLayoutProps> = ({
|
|||||||
return <DynamicSecondMenu parentCode={activeTab} />;
|
return <DynamicSecondMenu parentCode={activeTab} />;
|
||||||
}, [activeTab, overrideContainerStyle]);
|
}, [activeTab, overrideContainerStyle]);
|
||||||
|
|
||||||
return (
|
const renderMenuIcon = (menu: MenuItemDto) => {
|
||||||
<div className={navigationClassName}>
|
if (
|
||||||
{/* 一级导航菜单栏 */}
|
menu.icon &&
|
||||||
<div
|
(menu.icon.includes('.png') ||
|
||||||
className={cx(
|
menu.icon.includes('.jpg') ||
|
||||||
styles['first-menus'],
|
menu.icon.includes('.jpeg'))
|
||||||
'flex',
|
) {
|
||||||
'flex-col',
|
return <img src={menu.icon} alt="" className={cx(styles.menuIconImg)} />;
|
||||||
'items-center',
|
}
|
||||||
)}
|
|
||||||
style={{
|
return <SvgIcon name={menu.icon || 'icons-nav-task-time'} />;
|
||||||
width: firstMenuWidth,
|
};
|
||||||
background: firstMenuBackground,
|
|
||||||
}}
|
const renderBrandLogo = (compact = false) => (
|
||||||
>
|
<>
|
||||||
<Header />
|
{tenantConfigInfo?.siteLogo ? (
|
||||||
{/* 动态一级菜单 */}
|
<img
|
||||||
<DynamicTabs
|
src={tenantConfigInfo.siteLogo}
|
||||||
isStyleOne={
|
alt=""
|
||||||
navigationStyle === ThemeNavigationStyleType.STYLE1 || isMobile
|
className={cx(styles.brandLogo)}
|
||||||
}
|
|
||||||
menus={firstLevelMenus}
|
|
||||||
activeTab={activeTab}
|
|
||||||
onClick={handleTabClick}
|
|
||||||
/>
|
/>
|
||||||
{/* 用户操作区域 */}
|
) : null}
|
||||||
<UserOperateArea onClick={handleUserClick} menus={otherMenus} />
|
{!compact ? (
|
||||||
{/* 用户头像 */}
|
<Typography.Text className={cx(styles.brandText)}>
|
||||||
<User />
|
{tenantConfigInfo?.siteName || 'Art Design Pro'}
|
||||||
|
</Typography.Text>
|
||||||
|
) : null}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
|
||||||
|
const renderTopDropdownItem = (
|
||||||
|
menu: MenuItemDto,
|
||||||
|
level = 0,
|
||||||
|
parentCode?: string,
|
||||||
|
): React.ReactNode => {
|
||||||
|
if (!isVerticalMenuVisible(menu)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const children = getVerticalMenuChildren(menu).filter(
|
||||||
|
isVerticalMenuVisible,
|
||||||
|
);
|
||||||
|
const hasChildren = children.length > 0;
|
||||||
|
const isActive = isVerticalMenuActive(menu);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div key={menu.code || menu.name} className={cx(styles.topDropdownGroup)}>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className={cx(
|
||||||
|
styles.topDropdownItem,
|
||||||
|
styles[`topDropdownLevel${Math.min(level, 2)}`],
|
||||||
|
{
|
||||||
|
[styles.active]: isActive,
|
||||||
|
[styles.open]: hasChildren,
|
||||||
|
},
|
||||||
|
)}
|
||||||
|
onClick={() => {
|
||||||
|
if (!hasChildren) {
|
||||||
|
handleVerticalMenuClick(menu, parentCode);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
title={menu.name}
|
||||||
|
>
|
||||||
|
{level === 0 ? (
|
||||||
|
<span className={cx(styles.topDropdownIcon)}>
|
||||||
|
{renderMenuIcon(menu)}
|
||||||
|
</span>
|
||||||
|
) : (
|
||||||
|
<span className={cx(styles.topDropdownBullet)} />
|
||||||
|
)}
|
||||||
|
<span className={cx(styles.topDropdownText)}>{menu.name}</span>
|
||||||
|
{hasChildren ? (
|
||||||
|
<SvgIcon
|
||||||
|
name="icons-common-caret_down"
|
||||||
|
className={cx(styles.topDropdownArrow)}
|
||||||
|
/>
|
||||||
|
) : null}
|
||||||
|
</button>
|
||||||
|
{hasChildren ? (
|
||||||
|
<div className={cx(styles.topDropdownChildren)}>
|
||||||
|
{children.map((child) =>
|
||||||
|
renderTopDropdownItem(child, level + 1, parentCode),
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const renderTopMenuItem = (menu: MenuItemDto) => {
|
||||||
|
const children = getVerticalMenuChildren(menu).filter(
|
||||||
|
isVerticalMenuVisible,
|
||||||
|
);
|
||||||
|
const hasChildren = children.length > 0;
|
||||||
|
const content = (
|
||||||
|
<button
|
||||||
|
key={menu.code}
|
||||||
|
type="button"
|
||||||
|
className={cx(styles.topMenuItem, {
|
||||||
|
[styles.active]: activeTab === menu.code,
|
||||||
|
})}
|
||||||
|
onClick={() => {
|
||||||
|
if (hasChildren) {
|
||||||
|
setActiveTab(menu.code || '');
|
||||||
|
if (menu.code === 'workspace') {
|
||||||
|
handleRefreshEditAndCollect();
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
handleTabClick(menu);
|
||||||
|
}}
|
||||||
|
title={menu.name}
|
||||||
|
>
|
||||||
|
<span className={cx(styles.topMenuIcon)}>{renderMenuIcon(menu)}</span>
|
||||||
|
<span className={cx(styles.topMenuText)}>{menu.name}</span>
|
||||||
|
{hasChildren ? (
|
||||||
|
<SvgIcon
|
||||||
|
name="icons-common-caret_down"
|
||||||
|
className={cx(styles.topMenuArrow)}
|
||||||
|
/>
|
||||||
|
) : null}
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!hasChildren) {
|
||||||
|
return content;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Popover
|
||||||
|
key={menu.code}
|
||||||
|
trigger={['hover', 'click']}
|
||||||
|
placement="bottomLeft"
|
||||||
|
arrow={false}
|
||||||
|
styles={{ body: { padding: 0 } }}
|
||||||
|
content={
|
||||||
|
<div className={cx(styles.topDropdownMenu)}>
|
||||||
|
{children.map((child) =>
|
||||||
|
renderTopDropdownItem(child, 0, menu.code || ''),
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{content}
|
||||||
|
</Popover>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const renderTopNavigation = () => (
|
||||||
|
<div className={cx(styles.topMenuBar, 'noselect')}>
|
||||||
|
<div className={cx(styles.topLogo)}>{renderBrandLogo()}</div>
|
||||||
|
<div className={cx(styles.topMenuScroll)}>
|
||||||
|
{firstLevelMenus.map((menu) => renderTopMenuItem(menu))}
|
||||||
|
</div>
|
||||||
|
<div className={cx(styles.topActions)}>
|
||||||
|
{basicConfig.showRefreshButton && (
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className={cx(styles.topActionButton)}
|
||||||
|
onClick={handleReloadPage}
|
||||||
|
title="重载页面"
|
||||||
|
>
|
||||||
|
<ReloadOutlined />
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
{basicConfig.showFastEnter &&
|
||||||
|
otherMenus.map((menu) => (
|
||||||
|
<button
|
||||||
|
key={menu.code}
|
||||||
|
type="button"
|
||||||
|
className={cx(styles.topActionButton, {
|
||||||
|
[styles.active]: activeTab === menu.code,
|
||||||
|
})}
|
||||||
|
onClick={() => handleUserClick(menu)}
|
||||||
|
title={menu.name}
|
||||||
|
>
|
||||||
|
{renderMenuIcon(menu)}
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
<User placement="bottomRight" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
const renderSecondMenuPanel = () => (
|
||||||
|
<div
|
||||||
|
className={cx(styles['nav-menus'], 'noselect')}
|
||||||
|
style={{
|
||||||
|
width: isSecondMenuCollapsed
|
||||||
|
? 0
|
||||||
|
: NAVIGATION_LAYOUT_SIZES.SECOND_MENU_WIDTH,
|
||||||
|
paddingLeft: isSecondMenuCollapsed ? 0 : token.padding,
|
||||||
|
opacity: isSecondMenuCollapsed ? 0 : 1,
|
||||||
|
...secondMenuVisualStyle,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div className={cx(styles['nav-menus-scroll'])}>
|
||||||
|
<HoverScrollbar
|
||||||
|
className={cx('w-full', 'h-full')}
|
||||||
|
bodyWidth={
|
||||||
|
NAVIGATION_LAYOUT_SIZES.SECOND_MENU_WIDTH - token.padding * 2
|
||||||
|
}
|
||||||
|
style={{
|
||||||
|
padding: `${token.paddingSM}px 0`,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className={cx('flex', 'flex-col', 'h-full')}
|
||||||
|
style={{ minHeight: 0 }}
|
||||||
|
>
|
||||||
|
<ConditionRender condition={isShowTitle && currentTitle}>
|
||||||
|
<div style={{ padding: '0 12px 12px' }}>
|
||||||
|
<Typography.Title
|
||||||
|
level={5}
|
||||||
|
style={{ marginBottom: 0 }}
|
||||||
|
className={cx(styles['menu-title'])}
|
||||||
|
>
|
||||||
|
{currentTitle}
|
||||||
|
</Typography.Title>
|
||||||
|
</div>
|
||||||
|
</ConditionRender>
|
||||||
|
|
||||||
|
{renderSecondMenu}
|
||||||
|
</div>
|
||||||
|
</HoverScrollbar>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* 二级导航菜单栏 */}
|
<div className={cx(styles['integral-footer'])}>
|
||||||
<div
|
<CreditsBalance />
|
||||||
className={cx(styles['nav-menus'], 'noselect')}
|
</div>
|
||||||
style={{
|
</div>
|
||||||
width: isSecondMenuCollapsed
|
);
|
||||||
? 0
|
|
||||||
: NAVIGATION_LAYOUT_SIZES.SECOND_MENU_WIDTH,
|
const renderIconRail = () => (
|
||||||
paddingLeft: isSecondMenuCollapsed ? 0 : token.padding,
|
<div
|
||||||
opacity: isSecondMenuCollapsed ? 0 : 1,
|
className={cx(styles['first-menus'], 'flex', 'flex-col', 'items-center')}
|
||||||
backgroundColor: secondaryBackgroundColor,
|
style={{
|
||||||
}}
|
width: firstMenuWidth,
|
||||||
>
|
background: firstMenuBackground,
|
||||||
<div className={cx(styles['nav-menus-scroll'])}>
|
}}
|
||||||
<HoverScrollbar
|
>
|
||||||
className={cx('w-full', 'h-full')}
|
<Header />
|
||||||
bodyWidth={
|
<DynamicTabs
|
||||||
NAVIGATION_LAYOUT_SIZES.SECOND_MENU_WIDTH - token.padding * 2
|
isStyleOne={
|
||||||
}
|
navigationStyle === ThemeNavigationStyleType.STYLE1 || isMobile
|
||||||
|
}
|
||||||
|
menus={firstLevelMenus}
|
||||||
|
activeTab={activeTab}
|
||||||
|
onClick={handleTabClick}
|
||||||
|
/>
|
||||||
|
{basicConfig.showFastEnter && (
|
||||||
|
<UserOperateArea onClick={handleUserClick} menus={otherMenus} />
|
||||||
|
)}
|
||||||
|
{basicConfig.showRefreshButton && (
|
||||||
|
<Button
|
||||||
|
type="text"
|
||||||
|
className={cx(styles.railReloadButton)}
|
||||||
|
icon={<ReloadOutlined />}
|
||||||
|
onClick={handleReloadPage}
|
||||||
|
title="重载页面"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<User />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
const isVerticalMenuActive = (menu: MenuItemDto) => {
|
||||||
|
const routeMenuCode = params?.menuCode || location.state?.menuCode;
|
||||||
|
|
||||||
|
if (routeMenuCode && menu.code === routeMenuCode) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!menu.path) {
|
||||||
|
return menu.code === activeTab;
|
||||||
|
}
|
||||||
|
|
||||||
|
const resolvedPath = resolveVerticalMenuPath(
|
||||||
|
menu.path,
|
||||||
|
getTopMenuCodeByChildCode(menu.code),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!resolvedPath || resolvedPath.includes(':')) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return isPathMatch(resolvedPath, location.pathname);
|
||||||
|
};
|
||||||
|
|
||||||
|
const renderVerticalMenuItem = (
|
||||||
|
menu: MenuItemDto,
|
||||||
|
level = 0,
|
||||||
|
parentCode?: string,
|
||||||
|
ancestorCodes: string[] = [],
|
||||||
|
): React.ReactNode => {
|
||||||
|
if (!isVerticalMenuVisible(menu)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const menuCode = menu.code || `${menu.name}-${level}`;
|
||||||
|
const topCode = parentCode || menu.code;
|
||||||
|
const children = getVerticalMenuChildren(menu).filter(
|
||||||
|
isVerticalMenuVisible,
|
||||||
|
);
|
||||||
|
const hasChildren = children.length > 0;
|
||||||
|
const isExpanded = verticalExpandedMenus.includes(menuCode);
|
||||||
|
const nextAncestorCodes = [...ancestorCodes, menuCode].filter(Boolean);
|
||||||
|
const isTopLevel = level === 0;
|
||||||
|
const isActive = isTopLevel
|
||||||
|
? activeTab === menu.code
|
||||||
|
: isVerticalMenuActive(menu);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div key={menuCode} className={cx(styles.verticalMenuGroup)}>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className={cx(
|
||||||
|
styles.verticalMenuItem,
|
||||||
|
styles[`verticalMenuLevel${Math.min(level, 3)}`],
|
||||||
|
{
|
||||||
|
[styles.active]: isActive,
|
||||||
|
[styles.open]: isExpanded,
|
||||||
|
},
|
||||||
|
)}
|
||||||
|
onClick={() => handleVerticalMenuClick(menu, topCode, ancestorCodes)}
|
||||||
|
title={menu.name}
|
||||||
|
>
|
||||||
|
{isTopLevel ? (
|
||||||
|
<span className={cx(styles.verticalMenuIcon)}>
|
||||||
|
{renderMenuIcon(menu)}
|
||||||
|
</span>
|
||||||
|
) : (
|
||||||
|
<span className={cx(styles.verticalMenuBullet)} />
|
||||||
|
)}
|
||||||
|
<span className={cx(styles.verticalMenuText)}>{menu.name}</span>
|
||||||
|
{hasChildren ? (
|
||||||
|
<SvgIcon
|
||||||
|
name="icons-common-caret_down"
|
||||||
|
className={cx(styles.verticalMenuArrow)}
|
||||||
|
/>
|
||||||
|
) : null}
|
||||||
|
</button>
|
||||||
|
{hasChildren ? (
|
||||||
|
<div
|
||||||
|
className={cx(styles.verticalSubMenu)}
|
||||||
style={{
|
style={{
|
||||||
padding: `${token.paddingSM}px 0`,
|
maxHeight: isExpanded ? 720 : 0,
|
||||||
|
opacity: isExpanded ? 1 : 0,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div
|
{children.map((child) =>
|
||||||
className={cx('flex', 'flex-col', 'h-full')}
|
renderVerticalMenuItem(
|
||||||
style={{
|
child,
|
||||||
minHeight: 0,
|
level + 1,
|
||||||
}}
|
topCode,
|
||||||
>
|
nextAncestorCodes,
|
||||||
{/* 标题 */}
|
),
|
||||||
<ConditionRender condition={isShowTitle && currentTitle}>
|
)}
|
||||||
<div style={{ padding: '0 12px 12px' }}>
|
</div>
|
||||||
<Typography.Title
|
) : null}
|
||||||
level={5}
|
|
||||||
style={{ marginBottom: 0 }}
|
|
||||||
className={cx(styles['menu-title'])}
|
|
||||||
>
|
|
||||||
{currentTitle}
|
|
||||||
</Typography.Title>
|
|
||||||
</div>
|
|
||||||
</ConditionRender>
|
|
||||||
|
|
||||||
{/* 二级/三级菜单 */}
|
|
||||||
{renderSecondMenu}
|
|
||||||
</div>
|
|
||||||
</HoverScrollbar>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* 积分相关入口:放到二级导航栏底部固定展示 */}
|
|
||||||
<div className={cx(styles['integral-footer'])}>
|
|
||||||
<CreditsBalance />
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
{/* 收起/展开按钮 */}
|
const renderVerticalActionButton = (menu: MenuItemDto) => {
|
||||||
<CollapseButton />
|
const content = (
|
||||||
|
<button
|
||||||
|
key={menu.code}
|
||||||
|
type="button"
|
||||||
|
className={cx(styles.verticalActionButton, {
|
||||||
|
[styles.active]: activeTab === menu.code,
|
||||||
|
})}
|
||||||
|
onClick={() => handleUserClick(menu)}
|
||||||
|
title={menu.name}
|
||||||
|
>
|
||||||
|
{renderMenuIcon(menu)}
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
|
||||||
|
if (menu.code === 'notification' && unreadCount > 0) {
|
||||||
|
return (
|
||||||
|
<Badge key={menu.code} count={unreadCount} size="small">
|
||||||
|
{content}
|
||||||
|
</Badge>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return content;
|
||||||
|
};
|
||||||
|
|
||||||
|
const renderVerticalMenu = () => (
|
||||||
|
<aside
|
||||||
|
className={cx(styles.verticalMenu, 'noselect')}
|
||||||
|
style={{ width: firstMenuWidth }}
|
||||||
|
>
|
||||||
|
<div className={cx(styles.verticalLogo)}>{renderBrandLogo()}</div>
|
||||||
|
<div className={cx(styles.verticalMenuList)}>
|
||||||
|
{firstLevelMenus.map((menu) => renderVerticalMenuItem(menu))}
|
||||||
|
</div>
|
||||||
|
<div className={cx(styles.verticalFooter)}>
|
||||||
|
<div className={cx(styles.verticalActions)}>
|
||||||
|
{basicConfig.showRefreshButton && (
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className={cx(styles.verticalActionButton)}
|
||||||
|
onClick={handleReloadPage}
|
||||||
|
title="重载页面"
|
||||||
|
>
|
||||||
|
<ReloadOutlined />
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
{basicConfig.showFastEnter &&
|
||||||
|
otherMenus.map((menu) => renderVerticalActionButton(menu))}
|
||||||
|
</div>
|
||||||
|
<User placement="topRight">
|
||||||
|
<div className={cx(styles.verticalUserAvatar)}>
|
||||||
|
<UserAvatar avatar={userInfo?.avatar} onClick={() => undefined} />
|
||||||
|
</div>
|
||||||
|
</User>
|
||||||
|
</div>
|
||||||
|
</aside>
|
||||||
|
);
|
||||||
|
|
||||||
|
if (region === 'top') {
|
||||||
|
return renderTopNavigation();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (region === 'side' && effectiveMenuLayout === ThemeMenuLayoutType.MIXED) {
|
||||||
|
return (
|
||||||
|
<div className={cx(navigationClassName, styles.mixedSide)}>
|
||||||
|
{renderSecondMenuPanel()}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (effectiveMenuLayout === ThemeMenuLayoutType.HORIZONTAL) {
|
||||||
|
return renderTopNavigation();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (effectiveMenuLayout === ThemeMenuLayoutType.VERTICAL) {
|
||||||
|
return <div className={navigationClassName}>{renderVerticalMenu()}</div>;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={navigationClassName}>
|
||||||
|
{renderIconRail()}
|
||||||
|
{renderSecondMenuPanel()}
|
||||||
|
{basicConfig.showMenuButton && <CollapseButton />}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -44,7 +44,7 @@
|
|||||||
color: @WHITE;
|
color: @WHITE;
|
||||||
background: @BLANK26;
|
background: @BLANK26;
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
border-radius: 10px;
|
border-radius: 8px;
|
||||||
overflow-x: auto;
|
overflow-x: auto;
|
||||||
margin: 0 0 4px;
|
margin: 0 0 4px;
|
||||||
|
|
||||||
|
|||||||
@@ -90,7 +90,7 @@
|
|||||||
|
|
||||||
.previewRail {
|
.previewRail {
|
||||||
width: 28px;
|
width: 28px;
|
||||||
background: #006569;
|
background: #5d87ff;
|
||||||
}
|
}
|
||||||
|
|
||||||
.previewBody {
|
.previewBody {
|
||||||
@@ -141,7 +141,7 @@
|
|||||||
|
|
||||||
.portal {
|
.portal {
|
||||||
.openAppThemePreview {
|
.openAppThemePreview {
|
||||||
border-top: 4px solid #006569;
|
border-top: 4px solid #5d87ff;
|
||||||
background: #f4fbf8;
|
background: #f4fbf8;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -37,7 +37,7 @@
|
|||||||
line-height: 38px;
|
line-height: 38px;
|
||||||
padding: 0 10px;
|
padding: 0 10px;
|
||||||
margin-bottom: 10px;
|
margin-bottom: 10px;
|
||||||
border-radius: 10px;
|
border-radius: 8px;
|
||||||
transition: background-color 0.3s;
|
transition: background-color 0.3s;
|
||||||
|
|
||||||
&:hover:not(.checked) {
|
&:hover:not(.checked) {
|
||||||
@@ -67,23 +67,23 @@
|
|||||||
|
|
||||||
.left {
|
.left {
|
||||||
background: #f4fbf8;
|
background: #f4fbf8;
|
||||||
border-right: 1px solid rgba(0, 101, 105, 0.12);
|
border-right: 1px solid rgba(93, 135, 255, 0.14);
|
||||||
|
|
||||||
h3 {
|
h3 {
|
||||||
color: #006569;
|
color: #5d87ff;
|
||||||
}
|
}
|
||||||
|
|
||||||
ul .item {
|
ul .item {
|
||||||
color: #395254;
|
color: #395254;
|
||||||
|
|
||||||
&:hover:not(.checked) {
|
&:hover:not(.checked) {
|
||||||
background-color: rgba(0, 101, 105, 0.08);
|
background-color: rgba(93, 135, 255, 0.1);
|
||||||
}
|
}
|
||||||
|
|
||||||
&.checked {
|
&.checked {
|
||||||
background-color: #ffffff;
|
background-color: #ffffff;
|
||||||
color: #006569;
|
color: #5d87ff;
|
||||||
box-shadow: inset 3px 0 0 #006569;
|
box-shadow: inset 3px 0 0 #5d87ff;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -139,10 +139,10 @@
|
|||||||
|
|
||||||
.left {
|
.left {
|
||||||
background: #eef8f5;
|
background: #eef8f5;
|
||||||
border-right: 1px solid #cde5df;
|
border-right: 1px solid rgba(93, 135, 255, 0.18);
|
||||||
|
|
||||||
h3 {
|
h3 {
|
||||||
color: #00575a;
|
color: #5d87ff;
|
||||||
}
|
}
|
||||||
|
|
||||||
ul .item {
|
ul .item {
|
||||||
@@ -150,11 +150,11 @@
|
|||||||
border-radius: 6px;
|
border-radius: 6px;
|
||||||
|
|
||||||
&:hover:not(.checked) {
|
&:hover:not(.checked) {
|
||||||
background-color: rgba(0, 101, 105, 0.08);
|
background-color: rgba(93, 135, 255, 0.1);
|
||||||
}
|
}
|
||||||
|
|
||||||
&.checked {
|
&.checked {
|
||||||
background-color: #006569;
|
background-color: #5d87ff;
|
||||||
color: #ffffff;
|
color: #ffffff;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -163,7 +163,7 @@
|
|||||||
.right {
|
.right {
|
||||||
background: linear-gradient(
|
background: linear-gradient(
|
||||||
90deg,
|
90deg,
|
||||||
rgba(0, 101, 105, 0.05) 0 1px,
|
rgba(93, 135, 255, 0.05) 0 1px,
|
||||||
transparent 1px
|
transparent 1px
|
||||||
),
|
),
|
||||||
#ffffff;
|
#ffffff;
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import {
|
|||||||
OPEN_APP_THEME_CHANGED_EVENT,
|
OPEN_APP_THEME_CHANGED_EVENT,
|
||||||
OpenAppTheme,
|
OpenAppTheme,
|
||||||
} from '@/constants/openAppTheme.constants';
|
} from '@/constants/openAppTheme.constants';
|
||||||
|
import { useUnifiedTheme } from '@/hooks/useUnifiedTheme';
|
||||||
import { dict } from '@/services/i18nRuntime';
|
import { dict } from '@/services/i18nRuntime';
|
||||||
import { getTenantThemeConfig } from '@/services/tenant';
|
import { getTenantThemeConfig } from '@/services/tenant';
|
||||||
import { SettingActionEnum } from '@/types/enums/menus';
|
import { SettingActionEnum } from '@/types/enums/menus';
|
||||||
@@ -26,6 +27,7 @@ const cx = classNames.bind(styles);
|
|||||||
|
|
||||||
const Setting: React.FC = () => {
|
const Setting: React.FC = () => {
|
||||||
const { openSetting, setOpenSetting, isMobile } = useModel('layout');
|
const { openSetting, setOpenSetting, isMobile } = useModel('layout');
|
||||||
|
const { basicConfig } = useUnifiedTheme();
|
||||||
const { tenantConfigInfo } = useModel('tenantConfigInfo');
|
const { tenantConfigInfo } = useModel('tenantConfigInfo');
|
||||||
const isEnableSubscription = tenantConfigInfo?.enableSubscription !== 0;
|
const isEnableSubscription = tenantConfigInfo?.enableSubscription !== 0;
|
||||||
const [action, setAction] = useState<SettingActionEnum>(
|
const [action, setAction] = useState<SettingActionEnum>(
|
||||||
@@ -158,6 +160,9 @@ const Setting: React.FC = () => {
|
|||||||
if (item.type === SettingActionEnum.Developer_Profile) {
|
if (item.type === SettingActionEnum.Developer_Profile) {
|
||||||
return isEnableSubscription;
|
return isEnableSubscription;
|
||||||
}
|
}
|
||||||
|
if (item.type === SettingActionEnum.Language_Switch) {
|
||||||
|
return basicConfig.showLanguage;
|
||||||
|
}
|
||||||
return true;
|
return true;
|
||||||
}).map((item) => (
|
}).map((item) => (
|
||||||
<li
|
<li
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
@import '@/styles/token';
|
@import '@/styles/token';
|
||||||
|
|
||||||
.container {
|
.container {
|
||||||
// background-color: @colorBgLayout;
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
background-color: var(--xagi-layout-bg-secondary, #f2f5fb);
|
||||||
position: relative;
|
position: relative;
|
||||||
|
|
||||||
// 背景图片样式
|
// 背景图片样式
|
||||||
@@ -13,6 +15,7 @@
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
z-index: 0;
|
z-index: 0;
|
||||||
|
background-color: var(--xagi-layout-bg-secondary, #f2f5fb);
|
||||||
background-image: @backgroundImage;
|
background-image: @backgroundImage;
|
||||||
background-size: cover;
|
background-size: cover;
|
||||||
background-position: center;
|
background-position: center;
|
||||||
@@ -22,21 +25,49 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.layout-body {
|
||||||
|
position: relative;
|
||||||
|
z-index: 1;
|
||||||
|
display: flex;
|
||||||
|
flex: 1;
|
||||||
|
min-height: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.menu-layout-vertical,
|
||||||
|
.menu-layout-dual {
|
||||||
|
flex-direction: row;
|
||||||
|
|
||||||
|
.layout-body {
|
||||||
|
flex: 1;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.menu-layout-horizontal,
|
||||||
|
.menu-layout-mixed {
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
|
.layout-body {
|
||||||
|
flex: 1;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 移动端菜单容器隐藏时的样式
|
// 移动端菜单容器隐藏时的样式
|
||||||
.mobile-menu-container-hidden {
|
.mobile-menu-container-hidden {
|
||||||
width: 0 !important;
|
width: 0 !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.page-container {
|
.page-container {
|
||||||
background: @pageContainerBg;
|
background: var(--xagi-layout-bg-secondary, #f7f8fb);
|
||||||
border-left: 0 solid @pageContainerBorderColor;
|
border-left: 0 solid @pageContainerBorderColor;
|
||||||
margin: @marginXs;
|
margin: @pageContainerMargin;
|
||||||
margin-left: 0;
|
margin-left: 0;
|
||||||
border-radius: @pageContainerBorderRadius;
|
border-radius: @pageContainerBorderRadius;
|
||||||
box-shadow: 0 8px 24px -4px rgba(0, 0, 0, 8%);
|
box-shadow: none;
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
// 确保内容区域有半透明背景,提高可读性
|
// 确保内容区域有半透明背景,提高可读性
|
||||||
transition: all 0.3s ease;
|
transition: margin 0.3s ease, border-radius 0.3s ease, background 0.3s ease;
|
||||||
position: relative;
|
position: relative;
|
||||||
|
|
||||||
// 风格2的适配
|
// 风格2的适配
|
||||||
@@ -47,7 +78,7 @@
|
|||||||
// background: var(--xagi-color-bg-container);
|
// background: var(--xagi-color-bg-container);
|
||||||
&.xagi-layout-collapsed {
|
&.xagi-layout-collapsed {
|
||||||
border-left-width: 0;
|
border-left-width: 0;
|
||||||
border-radius: 12px 0 0 12px;
|
border-radius: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.xagi-layout-expanded {
|
&.xagi-layout-expanded {
|
||||||
@@ -56,3 +87,201 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.workTabsBar {
|
||||||
|
flex-shrink: 0;
|
||||||
|
padding: 10px 16px 0;
|
||||||
|
background: var(--xagi-layout-bg-secondary, #f2f5fb);
|
||||||
|
}
|
||||||
|
|
||||||
|
.workTabsScroller {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
min-height: 36px;
|
||||||
|
overflow: auto hidden;
|
||||||
|
scrollbar-width: none;
|
||||||
|
|
||||||
|
&::-webkit-scrollbar {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.workTab {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
flex-shrink: 0;
|
||||||
|
max-width: 168px;
|
||||||
|
height: 34px;
|
||||||
|
padding: 0 10px;
|
||||||
|
border: @lineWidth solid var(--xagi-layout-border-secondary);
|
||||||
|
border-radius: var(--xagi-custom-radius, 0.75rem);
|
||||||
|
background: var(--xagi-layout-bg-container, #fff);
|
||||||
|
color: var(--xagi-layout-text-secondary, #626a80);
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 14px;
|
||||||
|
line-height: 20px;
|
||||||
|
transition: color 0.2s ease, background 0.2s ease, border-color 0.2s ease,
|
||||||
|
box-shadow 0.2s ease;
|
||||||
|
|
||||||
|
&:hover,
|
||||||
|
&.active {
|
||||||
|
color: var(--xagi-color-primary, #5d87ff);
|
||||||
|
border-color: rgba(93, 135, 255, 0.28);
|
||||||
|
background: var(--xagi-layout-bg-container, #fff);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.active {
|
||||||
|
box-shadow: 0 8px 18px rgba(32, 45, 84, 0.08);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.work-tab-tab-card {
|
||||||
|
border-radius: var(--xagi-custom-radius, 0.75rem)
|
||||||
|
var(--xagi-custom-radius, 0.75rem) 4px 4px;
|
||||||
|
|
||||||
|
&.active {
|
||||||
|
border-bottom-color: var(--xagi-layout-bg-container, #fff);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.work-tab-tab-google {
|
||||||
|
height: 32px;
|
||||||
|
border-radius: 999px;
|
||||||
|
background: rgba(93, 135, 255, 0.08);
|
||||||
|
border-color: transparent;
|
||||||
|
|
||||||
|
&.active {
|
||||||
|
background: var(--xagi-color-primary, #5d87ff);
|
||||||
|
color: #fff;
|
||||||
|
box-shadow: 0 8px 18px rgba(93, 135, 255, 0.22);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.workTabTitle {
|
||||||
|
min-width: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.workTabClose {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
width: 18px;
|
||||||
|
height: 18px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
margin-left: 6px;
|
||||||
|
border-radius: 50%;
|
||||||
|
color: currentColor;
|
||||||
|
font-size: 11px;
|
||||||
|
opacity: 0.62;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: rgba(91, 105, 135, 0.12);
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.breadcrumbBar {
|
||||||
|
flex-shrink: 0;
|
||||||
|
padding: 8px 18px 0;
|
||||||
|
color: var(--xagi-layout-text-tertiary, #8790a5);
|
||||||
|
|
||||||
|
:global(.ant-breadcrumb) {
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.pageTransition {
|
||||||
|
min-height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pageTransition-none {
|
||||||
|
animation: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pageTransition-fade {
|
||||||
|
animation: xagiPageFade 0.22s ease both;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pageTransition-slide-left {
|
||||||
|
animation: xagiPageSlideLeft 0.24s ease both;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pageTransition-slide-bottom {
|
||||||
|
animation: xagiPageSlideBottom 0.24s ease both;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pageTransition-slide-top {
|
||||||
|
animation: xagiPageSlideTop 0.24s ease both;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes xagiPageFade {
|
||||||
|
from {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes xagiPageSlideLeft {
|
||||||
|
from {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateX(16px);
|
||||||
|
}
|
||||||
|
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateX(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes xagiPageSlideBottom {
|
||||||
|
from {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(-14px);
|
||||||
|
}
|
||||||
|
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes xagiPageSlideTop {
|
||||||
|
from {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(14px);
|
||||||
|
}
|
||||||
|
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
:global(html[data-theme='dark']) {
|
||||||
|
.container {
|
||||||
|
background-color: var(--xagi-layout-bg-primary, #080a0f);
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
background-color: var(--xagi-layout-bg-primary, #080a0f);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-container {
|
||||||
|
background: var(--xagi-layout-bg-primary, #080a0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
.workTabsBar {
|
||||||
|
background: var(--xagi-layout-bg-primary, #080a0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
.workTab {
|
||||||
|
background: var(--xagi-layout-bg-container, #111620);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -5,11 +5,20 @@ import {
|
|||||||
} from '@/constants/layout.constants';
|
} from '@/constants/layout.constants';
|
||||||
import useCategory from '@/hooks/useCategory';
|
import useCategory from '@/hooks/useCategory';
|
||||||
import { useUnifiedTheme } from '@/hooks/useUnifiedTheme';
|
import { useUnifiedTheme } from '@/hooks/useUnifiedTheme';
|
||||||
import { theme } from 'antd';
|
import type { MenuItemDto } from '@/types/interfaces/menu';
|
||||||
|
import { CloseOutlined } from '@ant-design/icons';
|
||||||
|
import { Breadcrumb, theme } from 'antd';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import React, { useCallback, useEffect, useMemo, useRef } from 'react';
|
import React, {
|
||||||
import { Outlet, useModel } from 'umi';
|
useCallback,
|
||||||
|
useEffect,
|
||||||
|
useMemo,
|
||||||
|
useRef,
|
||||||
|
useState,
|
||||||
|
} from 'react';
|
||||||
|
import { history, Outlet, useLocation, useModel } from 'umi';
|
||||||
import DynamicMenusLayout from './DynamicMenusLayout';
|
import DynamicMenusLayout from './DynamicMenusLayout';
|
||||||
|
import { normalizeMenuPathname } from './DynamicMenusLayout/utils';
|
||||||
import HoverMenu from './HoverMenu';
|
import HoverMenu from './HoverMenu';
|
||||||
import styles from './index.less';
|
import styles from './index.less';
|
||||||
import Message from './Message';
|
import Message from './Message';
|
||||||
@@ -19,6 +28,75 @@ import Setting from './Setting';
|
|||||||
// 绑定 classNames,便于动态样式组合
|
// 绑定 classNames,便于动态样式组合
|
||||||
const cx = classNames.bind(styles);
|
const cx = classNames.bind(styles);
|
||||||
|
|
||||||
|
interface WorkTabItem {
|
||||||
|
key: string;
|
||||||
|
path: string;
|
||||||
|
title: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface LayoutBreadcrumbItem {
|
||||||
|
key: string;
|
||||||
|
title: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const getMenuPathWithoutQuery = (path?: string) => (path || '').split('?')[0];
|
||||||
|
|
||||||
|
const isMenuPathMatch = (menuPath: string | undefined, pathname: string) => {
|
||||||
|
const normalizedPathname = normalizeMenuPathname(pathname);
|
||||||
|
const cleanMenuPath = getMenuPathWithoutQuery(menuPath);
|
||||||
|
|
||||||
|
if (!cleanMenuPath) return false;
|
||||||
|
if (cleanMenuPath === '/homepage') {
|
||||||
|
return normalizedPathname === '/home' || normalizedPathname === '/';
|
||||||
|
}
|
||||||
|
if (cleanMenuPath === '/space') {
|
||||||
|
return normalizedPathname.startsWith('/space');
|
||||||
|
}
|
||||||
|
if (cleanMenuPath.includes(':')) {
|
||||||
|
const pattern = cleanMenuPath
|
||||||
|
.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
|
||||||
|
.replace(/:(\w+)/g, '[^/]+');
|
||||||
|
return new RegExp(`^${pattern}(/.*)?$`).test(normalizedPathname);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
normalizedPathname === cleanMenuPath ||
|
||||||
|
normalizedPathname.startsWith(`${cleanMenuPath}/`)
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const findMenuTrail = (
|
||||||
|
menus: MenuItemDto[] = [],
|
||||||
|
pathname: string,
|
||||||
|
parents: LayoutBreadcrumbItem[] = [],
|
||||||
|
): LayoutBreadcrumbItem[] => {
|
||||||
|
for (const menu of menus) {
|
||||||
|
const menuKey = menu.code || menu.path || menu.name || '';
|
||||||
|
const currentTrail = menu.name
|
||||||
|
? [...parents, { key: menuKey, title: menu.name }]
|
||||||
|
: parents;
|
||||||
|
|
||||||
|
if (isMenuPathMatch(menu.path, pathname)) {
|
||||||
|
return currentTrail;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (menu.children?.length) {
|
||||||
|
const childTrail = findMenuTrail(menu.children, pathname, currentTrail);
|
||||||
|
if (childTrail.length) {
|
||||||
|
return childTrail;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return [];
|
||||||
|
};
|
||||||
|
|
||||||
|
const getFallbackRouteTitle = (pathname: string) => {
|
||||||
|
if (pathname === '/' || pathname === '/home') return '首页';
|
||||||
|
const lastSegment = pathname.split('/').filter(Boolean).pop();
|
||||||
|
return lastSegment || '当前页面';
|
||||||
|
};
|
||||||
|
|
||||||
// 开发环境判断
|
// 开发环境判断
|
||||||
// const isDev = process.env.NODE_ENV === 'development';
|
// const isDev = process.env.NODE_ENV === 'development';
|
||||||
// const isDevOrTest = isDev || process.env.CI;
|
// const isDevOrTest = isDev || process.env.CI;
|
||||||
@@ -28,6 +106,7 @@ const cx = classNames.bind(styles);
|
|||||||
* 负责响应式菜单、历史会话、消息、设置弹窗的布局与展示
|
* 负责响应式菜单、历史会话、消息、设置弹窗的布局与展示
|
||||||
*/
|
*/
|
||||||
const Layout: React.FC = () => {
|
const Layout: React.FC = () => {
|
||||||
|
const location = useLocation();
|
||||||
// 使用 useRef 避免重复获取 DOM 元素
|
// 使用 useRef 避免重复获取 DOM 元素
|
||||||
const mobileMenuContainerRef = useRef<HTMLDivElement>(null);
|
const mobileMenuContainerRef = useRef<HTMLDivElement>(null);
|
||||||
// 全局主题与语言(已在 app.tsx 统一注入,这里仅保留使用场景需要时可读取)
|
// 全局主题与语言(已在 app.tsx 统一注入,这里仅保留使用场景需要时可读取)
|
||||||
@@ -45,7 +124,8 @@ const Layout: React.FC = () => {
|
|||||||
const { runQueryCategory } = useCategory();
|
const { runQueryCategory } = useCategory();
|
||||||
|
|
||||||
// 导航风格管理(使用统一主题系统)
|
// 导航风格管理(使用统一主题系统)
|
||||||
const { navigationStyle, layoutStyle } = useUnifiedTheme();
|
const { navigationStyle, layoutStyle, menuLayout, basicConfig } =
|
||||||
|
useUnifiedTheme();
|
||||||
const { isSecondMenuCollapsed } = useModel('layout');
|
const { isSecondMenuCollapsed } = useModel('layout');
|
||||||
const { token } = theme.useToken();
|
const { token } = theme.useToken();
|
||||||
|
|
||||||
@@ -61,7 +141,8 @@ const Layout: React.FC = () => {
|
|||||||
} = useModel('layout');
|
} = useModel('layout');
|
||||||
|
|
||||||
const { asyncSpaceListFun } = useModel('spaceModel');
|
const { asyncSpaceListFun } = useModel('spaceModel');
|
||||||
const { loadMenus } = useModel('menuModel');
|
const { firstLevelMenus, loadMenus } = useModel('menuModel');
|
||||||
|
const [workTabs, setWorkTabs] = useState<WorkTabItem[]>([]);
|
||||||
|
|
||||||
// 移除对 @@initialState 的依赖,统一由 useGlobalSettings 管理全局配置
|
// 移除对 @@initialState 的依赖,统一由 useGlobalSettings 管理全局配置
|
||||||
|
|
||||||
@@ -214,6 +295,73 @@ const Layout: React.FC = () => {
|
|||||||
() => (isMobile ? { paddingTop: MOBILE_MENU_TOP_PADDING } : {}),
|
() => (isMobile ? { paddingTop: MOBILE_MENU_TOP_PADDING } : {}),
|
||||||
[isMobile],
|
[isMobile],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const currentPath = `${location.pathname}${location.search || ''}`;
|
||||||
|
const breadcrumbItems = useMemo(
|
||||||
|
() => findMenuTrail(firstLevelMenus, location.pathname),
|
||||||
|
[firstLevelMenus, location.pathname],
|
||||||
|
);
|
||||||
|
const currentPageTitle =
|
||||||
|
breadcrumbItems[breadcrumbItems.length - 1]?.title ||
|
||||||
|
getFallbackRouteTitle(location.pathname);
|
||||||
|
const transitionType = basicConfig.pageTransition || 'none';
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!basicConfig.showWorkTab || location.pathname.startsWith('/login')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setWorkTabs((prev) => {
|
||||||
|
const currentTab = {
|
||||||
|
key: currentPath,
|
||||||
|
path: currentPath,
|
||||||
|
title: currentPageTitle,
|
||||||
|
};
|
||||||
|
const exists = prev.some((item) => item.key === currentTab.key);
|
||||||
|
const nextTabs = exists
|
||||||
|
? prev.map((item) =>
|
||||||
|
item.key === currentTab.key
|
||||||
|
? { ...item, title: currentTab.title }
|
||||||
|
: item,
|
||||||
|
)
|
||||||
|
: [...prev, currentTab];
|
||||||
|
|
||||||
|
return nextTabs.slice(-12);
|
||||||
|
});
|
||||||
|
}, [
|
||||||
|
basicConfig.showWorkTab,
|
||||||
|
currentPageTitle,
|
||||||
|
currentPath,
|
||||||
|
location.pathname,
|
||||||
|
]);
|
||||||
|
|
||||||
|
const handleWorkTabClick = (tab: WorkTabItem) => {
|
||||||
|
if (tab.key !== currentPath) {
|
||||||
|
history.push(tab.path);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleWorkTabClose = (
|
||||||
|
event: React.MouseEvent<HTMLElement> | React.KeyboardEvent<HTMLElement>,
|
||||||
|
tab: WorkTabItem,
|
||||||
|
) => {
|
||||||
|
event.stopPropagation();
|
||||||
|
const nextTabs = workTabs.filter((item) => item.key !== tab.key);
|
||||||
|
setWorkTabs(nextTabs);
|
||||||
|
|
||||||
|
if (tab.key === currentPath) {
|
||||||
|
const fallbackTab = nextTabs[nextTabs.length - 1];
|
||||||
|
history.push(fallbackTab?.path || '/home');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const workTabClassName = useCallback(
|
||||||
|
(tab: WorkTabItem) =>
|
||||||
|
cx(styles.workTab, styles[`work-tab-${basicConfig.tabStyle}`], {
|
||||||
|
[styles.active]: tab.key === currentPath,
|
||||||
|
}),
|
||||||
|
[basicConfig.tabStyle, currentPath],
|
||||||
|
);
|
||||||
/**
|
/**
|
||||||
* 主容器样式类名(使用独立的布局风格类)
|
* 主容器样式类名(使用独立的布局风格类)
|
||||||
* 包含导航风格和布局风格类
|
* 包含导航风格和布局风格类
|
||||||
@@ -224,12 +372,16 @@ const Layout: React.FC = () => {
|
|||||||
'flex',
|
'flex',
|
||||||
'h-full',
|
'h-full',
|
||||||
styles.container,
|
styles.container,
|
||||||
|
styles[`menu-layout-${menuLayout}`],
|
||||||
`xagi-layout-${layoutStyle}`, // 布局风格类(独立于Ant Design)
|
`xagi-layout-${layoutStyle}`, // 布局风格类(独立于Ant Design)
|
||||||
`xagi-nav-${navigationStyle}`, // 导航风格类
|
`xagi-nav-${navigationStyle}`, // 导航风格类
|
||||||
),
|
),
|
||||||
[layoutStyle, navigationStyle],
|
[layoutStyle, navigationStyle, menuLayout],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const hasTopMenu = !isMobile && ['horizontal', 'mixed'].includes(menuLayout);
|
||||||
|
const hasSideMenu = isMobile || menuLayout !== 'horizontal';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 页面容器样式类名(使用独立的布局风格类)
|
* 页面容器样式类名(使用独立的布局风格类)
|
||||||
* 根据导航风格动态调整
|
* 根据导航风格动态调整
|
||||||
@@ -250,48 +402,118 @@ const Layout: React.FC = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={mainContainerClassName}>
|
<div className={mainContainerClassName}>
|
||||||
{/* 侧边菜单栏及弹窗区域 */}
|
{hasTopMenu && (
|
||||||
<div
|
|
||||||
ref={mobileMenuContainerRef}
|
|
||||||
className={containerClassName}
|
|
||||||
id="mobile-menu-container"
|
|
||||||
style={sidebarStyle}
|
|
||||||
>
|
|
||||||
{/* 菜单栏 */}
|
|
||||||
<DynamicMenusLayout
|
<DynamicMenusLayout
|
||||||
overrideContainerStyle={menuOverrideStyle}
|
overrideContainerStyle={menuOverrideStyle}
|
||||||
isMobile={isMobile}
|
isMobile={isMobile}
|
||||||
|
region="top"
|
||||||
/>
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
{/* 悬浮菜单 */}
|
<div className={styles['layout-body']}>
|
||||||
<HoverMenu />
|
{/* 侧边菜单栏及弹窗区域 */}
|
||||||
|
{hasSideMenu && (
|
||||||
|
<div
|
||||||
|
ref={mobileMenuContainerRef}
|
||||||
|
className={containerClassName}
|
||||||
|
id="mobile-menu-container"
|
||||||
|
style={sidebarStyle}
|
||||||
|
>
|
||||||
|
{/* 菜单栏 */}
|
||||||
|
<DynamicMenusLayout
|
||||||
|
overrideContainerStyle={menuOverrideStyle}
|
||||||
|
isMobile={isMobile}
|
||||||
|
region={hasTopMenu ? 'side' : 'auto'}
|
||||||
|
/>
|
||||||
|
|
||||||
{/* 消息弹窗 */}
|
{/* 悬浮菜单 */}
|
||||||
<Message />
|
{menuLayout !== 'vertical' && <HoverMenu />}
|
||||||
|
|
||||||
{/* 设置弹窗 */}
|
{/* 消息弹窗 */}
|
||||||
<Setting />
|
<Message />
|
||||||
|
|
||||||
{/* 移动端菜单按钮和遮罩层 */}
|
{/* 设置弹窗 */}
|
||||||
{isMobile && (
|
<Setting />
|
||||||
<MobileMenu
|
|
||||||
isOpen={fullMobileMenu}
|
{/* 移动端菜单按钮和遮罩层 */}
|
||||||
onToggle={toggleFullMobileMenu}
|
{isMobile && (
|
||||||
menuWidth={getCurrentMenuWidth()}
|
<MobileMenu
|
||||||
/>
|
isOpen={fullMobileMenu}
|
||||||
|
onToggle={toggleFullMobileMenu}
|
||||||
|
menuWidth={getCurrentMenuWidth()}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* 主内容区 */}
|
{!hasSideMenu && (
|
||||||
<div
|
<>
|
||||||
className={cx(pageContainerClassName, [
|
<Message />
|
||||||
{ 'scroll-container': !isMobile },
|
<Setting />
|
||||||
// 移动端模式下宽度100%
|
</>
|
||||||
{ 'w-full': isMobile },
|
)}
|
||||||
])}
|
|
||||||
id="page-container-selector"
|
{/* 主内容区 */}
|
||||||
>
|
<div
|
||||||
<Outlet />
|
className={cx(pageContainerClassName, [
|
||||||
|
{ 'scroll-container': !isMobile },
|
||||||
|
// 移动端模式下宽度100%
|
||||||
|
{ 'w-full': isMobile },
|
||||||
|
])}
|
||||||
|
id="page-container-selector"
|
||||||
|
>
|
||||||
|
{basicConfig.showWorkTab && workTabs.length > 0 && (
|
||||||
|
<div className={cx(styles.workTabsBar)}>
|
||||||
|
<div className={cx(styles.workTabsScroller)}>
|
||||||
|
{workTabs.map((tab) => (
|
||||||
|
<button
|
||||||
|
key={tab.key}
|
||||||
|
type="button"
|
||||||
|
className={workTabClassName(tab)}
|
||||||
|
onClick={() => handleWorkTabClick(tab)}
|
||||||
|
title={tab.title}
|
||||||
|
>
|
||||||
|
<span className={cx(styles.workTabTitle)}>{tab.title}</span>
|
||||||
|
{workTabs.length > 1 && (
|
||||||
|
<span
|
||||||
|
role="button"
|
||||||
|
tabIndex={0}
|
||||||
|
className={cx(styles.workTabClose)}
|
||||||
|
onClick={(event) => handleWorkTabClose(event, tab)}
|
||||||
|
onKeyDown={(event) => {
|
||||||
|
if (event.key === 'Enter' || event.key === ' ') {
|
||||||
|
handleWorkTabClose(event, tab);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
aria-label={`关闭${tab.title}`}
|
||||||
|
>
|
||||||
|
<CloseOutlined />
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{basicConfig.showCrumbs && breadcrumbItems.length > 0 && (
|
||||||
|
<div className={cx(styles.breadcrumbBar)}>
|
||||||
|
<Breadcrumb
|
||||||
|
items={breadcrumbItems.map((item) => ({ title: item.title }))}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<div
|
||||||
|
key={location.pathname}
|
||||||
|
className={cx(
|
||||||
|
styles.pageTransition,
|
||||||
|
styles[`pageTransition-${transitionType}`],
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<Outlet />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,62 +1,737 @@
|
|||||||
@import '@/styles/token';
|
@import '@/styles/token';
|
||||||
|
|
||||||
.container {
|
.headerActions {
|
||||||
width: 100%;
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.content {
|
.artSettingsPage {
|
||||||
padding: @paddingLg;
|
min-height: 100%;
|
||||||
|
padding: 12px 24px 24px;
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
.configContainer {
|
.settingsPanel {
|
||||||
margin-bottom: @paddingXl;
|
display: grid;
|
||||||
|
align-items: start;
|
||||||
|
width: 100%;
|
||||||
|
max-width: 1280px;
|
||||||
|
min-height: auto;
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 0;
|
||||||
|
overflow: visible;
|
||||||
|
background: transparent;
|
||||||
|
box-shadow: none;
|
||||||
|
backdrop-filter: none;
|
||||||
|
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||||
|
column-gap: 28px;
|
||||||
|
row-gap: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.settingSection {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sectionTitle {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
color: #2b3150;
|
||||||
|
font-size: 15px;
|
||||||
|
font-weight: 600;
|
||||||
|
line-height: 22px;
|
||||||
|
|
||||||
|
&::before,
|
||||||
|
&::after {
|
||||||
|
content: '';
|
||||||
|
flex: 1;
|
||||||
|
height: 1px;
|
||||||
|
background: #e5e8ef;
|
||||||
}
|
}
|
||||||
|
|
||||||
.configItem {
|
span {
|
||||||
margin-bottom: 24px;
|
padding: 0 14px;
|
||||||
padding: 0;
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
&:last-child {
|
.settingBoxWrap {
|
||||||
margin-bottom: 0;
|
display: grid;
|
||||||
|
width: 100%;
|
||||||
|
margin-bottom: 0;
|
||||||
|
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||||
|
gap: 8px 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.settingItem {
|
||||||
|
box-sizing: border-box;
|
||||||
|
width: 100%;
|
||||||
|
margin-right: 0;
|
||||||
|
padding: 0;
|
||||||
|
border: 0;
|
||||||
|
background: transparent;
|
||||||
|
text-align: center;
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
&.disabled,
|
||||||
|
&:disabled {
|
||||||
|
cursor: not-allowed;
|
||||||
|
opacity: 0.42;
|
||||||
|
|
||||||
|
.previewBox {
|
||||||
|
border-color: #edf1f7 !important;
|
||||||
|
box-shadow: none;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 响应式设计
|
.previewBox {
|
||||||
// @media (max-width: @screenLg) {
|
position: relative;
|
||||||
// .content {
|
box-sizing: border-box;
|
||||||
// .configItem {
|
display: block;
|
||||||
// margin-bottom: @paddingMd;
|
height: 42px;
|
||||||
// }
|
overflow: hidden;
|
||||||
// }
|
border: 2px solid #edf1f7;
|
||||||
// }
|
border-radius: 7px;
|
||||||
|
background: #f6f8fc;
|
||||||
|
box-shadow: 0 0 8px 0 rgb(0 0 0 / 10%);
|
||||||
|
transition: border-color 0.12s ease, box-shadow 0.12s ease;
|
||||||
|
|
||||||
// @media (max-width: @screenMd) {
|
&.active {
|
||||||
// .title {
|
border-color: @colorPrimary;
|
||||||
// padding: 16px;
|
}
|
||||||
// }
|
|
||||||
|
|
||||||
// .content {
|
&.withTopMargin {
|
||||||
// padding: 16px;
|
margin-top: 0;
|
||||||
// padding-top: 70px; // 调整移动端标题高度
|
}
|
||||||
// padding-bottom: 70px; // 调整移动端底部高度
|
|
||||||
|
|
||||||
// .configItem {
|
img {
|
||||||
// margin-bottom: @paddingSm;
|
display: block;
|
||||||
// padding: 0;
|
width: 100%;
|
||||||
// }
|
height: 100%;
|
||||||
// }
|
object-fit: cover;
|
||||||
|
}
|
||||||
|
|
||||||
// .footer {
|
i,
|
||||||
// display: flex;
|
b,
|
||||||
// justify-content: flex-start;
|
em {
|
||||||
// align-items: center;
|
position: absolute;
|
||||||
// flex-direction: column;
|
display: block;
|
||||||
// gap: @paddingMd;
|
border-radius: 4px;
|
||||||
// padding: 16px;
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// :global(.ant-btn) {
|
.settingName {
|
||||||
// width: 100%;
|
display: block;
|
||||||
// max-width: 200px;
|
margin-top: 4px;
|
||||||
// }
|
color: #111;
|
||||||
// }
|
font-size: 13px;
|
||||||
// }
|
line-height: 18px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.theme-light {
|
||||||
|
background: #f7f8fb;
|
||||||
|
|
||||||
|
i {
|
||||||
|
left: 12px;
|
||||||
|
top: 10px;
|
||||||
|
width: 18px;
|
||||||
|
height: 32px;
|
||||||
|
background: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
b,
|
||||||
|
em {
|
||||||
|
left: 42px;
|
||||||
|
height: 5px;
|
||||||
|
background: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
b {
|
||||||
|
top: 13px;
|
||||||
|
width: 72px;
|
||||||
|
}
|
||||||
|
|
||||||
|
em {
|
||||||
|
top: 25px;
|
||||||
|
width: 48px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.theme-dark {
|
||||||
|
background: #30323b;
|
||||||
|
|
||||||
|
i {
|
||||||
|
left: 10px;
|
||||||
|
top: 8px;
|
||||||
|
width: 24px;
|
||||||
|
height: 36px;
|
||||||
|
background: #40424d;
|
||||||
|
}
|
||||||
|
|
||||||
|
b,
|
||||||
|
em {
|
||||||
|
left: 48px;
|
||||||
|
height: 5px;
|
||||||
|
background: #555867;
|
||||||
|
}
|
||||||
|
|
||||||
|
b {
|
||||||
|
top: 15px;
|
||||||
|
width: 76px;
|
||||||
|
}
|
||||||
|
|
||||||
|
em {
|
||||||
|
top: 28px;
|
||||||
|
width: 48px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.theme-system {
|
||||||
|
background: linear-gradient(90deg, #f7f8fb 0 50%, #30323b 50% 100%);
|
||||||
|
|
||||||
|
i {
|
||||||
|
left: 12px;
|
||||||
|
top: 10px;
|
||||||
|
width: 24px;
|
||||||
|
height: 32px;
|
||||||
|
background: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
b,
|
||||||
|
em {
|
||||||
|
left: 48px;
|
||||||
|
height: 5px;
|
||||||
|
background: rgb(255 255 255 / 55%);
|
||||||
|
}
|
||||||
|
|
||||||
|
b {
|
||||||
|
top: 15px;
|
||||||
|
width: 72px;
|
||||||
|
}
|
||||||
|
|
||||||
|
em {
|
||||||
|
top: 28px;
|
||||||
|
width: 46px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.layout-vertical,
|
||||||
|
.layout-horizontal,
|
||||||
|
.layout-mixed,
|
||||||
|
.layout-dual,
|
||||||
|
.menu-design,
|
||||||
|
.menu-light,
|
||||||
|
.menu-dark {
|
||||||
|
background: #f7f9fd;
|
||||||
|
|
||||||
|
i,
|
||||||
|
b,
|
||||||
|
em {
|
||||||
|
background: #fff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.layout-vertical {
|
||||||
|
i {
|
||||||
|
left: 8px;
|
||||||
|
top: 7px;
|
||||||
|
width: 18px;
|
||||||
|
height: 38px;
|
||||||
|
}
|
||||||
|
|
||||||
|
b,
|
||||||
|
em {
|
||||||
|
left: 38px;
|
||||||
|
height: 7px;
|
||||||
|
}
|
||||||
|
|
||||||
|
b {
|
||||||
|
top: 13px;
|
||||||
|
width: 74px;
|
||||||
|
}
|
||||||
|
|
||||||
|
em {
|
||||||
|
top: 29px;
|
||||||
|
width: 54px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.layout-horizontal {
|
||||||
|
i {
|
||||||
|
left: 10px;
|
||||||
|
top: 8px;
|
||||||
|
width: calc(100% - 20px);
|
||||||
|
height: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
b,
|
||||||
|
em {
|
||||||
|
left: 18px;
|
||||||
|
height: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
b {
|
||||||
|
top: 28px;
|
||||||
|
width: 92px;
|
||||||
|
}
|
||||||
|
|
||||||
|
em {
|
||||||
|
top: 40px;
|
||||||
|
width: 56px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.layout-mixed {
|
||||||
|
i {
|
||||||
|
left: 8px;
|
||||||
|
top: 7px;
|
||||||
|
width: 18px;
|
||||||
|
height: 38px;
|
||||||
|
}
|
||||||
|
|
||||||
|
b {
|
||||||
|
left: 34px;
|
||||||
|
top: 8px;
|
||||||
|
width: calc(100% - 44px);
|
||||||
|
height: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
em {
|
||||||
|
left: 40px;
|
||||||
|
top: 30px;
|
||||||
|
width: 62px;
|
||||||
|
height: 7px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.layout-dual {
|
||||||
|
i {
|
||||||
|
left: 8px;
|
||||||
|
top: 7px;
|
||||||
|
width: 14px;
|
||||||
|
height: 38px;
|
||||||
|
}
|
||||||
|
|
||||||
|
b {
|
||||||
|
left: 28px;
|
||||||
|
top: 7px;
|
||||||
|
width: 18px;
|
||||||
|
height: 38px;
|
||||||
|
}
|
||||||
|
|
||||||
|
em {
|
||||||
|
left: 58px;
|
||||||
|
top: 14px;
|
||||||
|
width: 72px;
|
||||||
|
height: 8px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.menu-design {
|
||||||
|
background: #eef2fb;
|
||||||
|
|
||||||
|
i {
|
||||||
|
left: 8px;
|
||||||
|
top: 7px;
|
||||||
|
width: 18px;
|
||||||
|
height: 38px;
|
||||||
|
background: #dfe5f2;
|
||||||
|
}
|
||||||
|
|
||||||
|
b,
|
||||||
|
em {
|
||||||
|
left: 38px;
|
||||||
|
height: 7px;
|
||||||
|
background: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
b {
|
||||||
|
top: 13px;
|
||||||
|
width: 74px;
|
||||||
|
}
|
||||||
|
|
||||||
|
em {
|
||||||
|
top: 29px;
|
||||||
|
width: 54px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.menu-light {
|
||||||
|
i {
|
||||||
|
left: 8px;
|
||||||
|
top: 7px;
|
||||||
|
width: 18px;
|
||||||
|
height: 38px;
|
||||||
|
}
|
||||||
|
|
||||||
|
b,
|
||||||
|
em {
|
||||||
|
left: 38px;
|
||||||
|
height: 7px;
|
||||||
|
background: #eef2f8;
|
||||||
|
}
|
||||||
|
|
||||||
|
b {
|
||||||
|
top: 13px;
|
||||||
|
width: 74px;
|
||||||
|
}
|
||||||
|
|
||||||
|
em {
|
||||||
|
top: 29px;
|
||||||
|
width: 54px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.menu-dark {
|
||||||
|
background: #30323b;
|
||||||
|
|
||||||
|
i {
|
||||||
|
left: 8px;
|
||||||
|
top: 7px;
|
||||||
|
width: 18px;
|
||||||
|
height: 38px;
|
||||||
|
background: #20222a;
|
||||||
|
}
|
||||||
|
|
||||||
|
b,
|
||||||
|
em {
|
||||||
|
left: 38px;
|
||||||
|
height: 7px;
|
||||||
|
background: #555867;
|
||||||
|
}
|
||||||
|
|
||||||
|
b {
|
||||||
|
top: 13px;
|
||||||
|
width: 74px;
|
||||||
|
}
|
||||||
|
|
||||||
|
em {
|
||||||
|
top: 29px;
|
||||||
|
width: 54px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.colorGrid {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.colorDot {
|
||||||
|
width: 30px;
|
||||||
|
height: 30px;
|
||||||
|
padding: 0;
|
||||||
|
border: 2px solid #fff;
|
||||||
|
border-radius: 50%;
|
||||||
|
color: #fff;
|
||||||
|
cursor: pointer;
|
||||||
|
box-shadow: 0 0 0 1px #dfe5ee;
|
||||||
|
transition: transform 0.15s ease, box-shadow 0.15s ease;
|
||||||
|
|
||||||
|
&:hover,
|
||||||
|
&.active {
|
||||||
|
transform: scale(1.05);
|
||||||
|
box-shadow: 0 0 0 3px rgb(93 135 255 / 18%);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.colorPicker {
|
||||||
|
width: 30px;
|
||||||
|
height: 30px;
|
||||||
|
|
||||||
|
:global(.ant-color-picker-trigger) {
|
||||||
|
width: 30px;
|
||||||
|
height: 30px;
|
||||||
|
min-width: 30px;
|
||||||
|
padding: 2px;
|
||||||
|
border-radius: 50%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.switchRow {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
width: 100%;
|
||||||
|
min-height: 56px;
|
||||||
|
padding: 0;
|
||||||
|
border: 0;
|
||||||
|
background: transparent;
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
span {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
strong {
|
||||||
|
color: #111;
|
||||||
|
font-size: 15px;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
small {
|
||||||
|
margin-top: 4px;
|
||||||
|
color: #8b92a6;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.backgroundGrid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.backgroundItem {
|
||||||
|
min-width: 0;
|
||||||
|
padding: 6px;
|
||||||
|
border: 2px solid #edf1f7;
|
||||||
|
border-radius: 8px;
|
||||||
|
background: transparent;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: border-color 0.12s ease, box-shadow 0.12s ease;
|
||||||
|
|
||||||
|
&:hover,
|
||||||
|
&.active {
|
||||||
|
border-color: @colorPrimary;
|
||||||
|
}
|
||||||
|
|
||||||
|
img {
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
aspect-ratio: 16 / 9;
|
||||||
|
object-fit: cover;
|
||||||
|
border-radius: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
span {
|
||||||
|
display: block;
|
||||||
|
margin-top: 6px;
|
||||||
|
color: #111;
|
||||||
|
font-size: 13px;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.settingActions {
|
||||||
|
display: flex;
|
||||||
|
grid-column: 1 / -1;
|
||||||
|
gap: 8px;
|
||||||
|
justify-content: flex-end;
|
||||||
|
margin-top: 0;
|
||||||
|
padding-top: 12px;
|
||||||
|
border-top: 1px solid #e5e8ef;
|
||||||
|
background: transparent;
|
||||||
|
|
||||||
|
:global(.ant-btn) {
|
||||||
|
flex: 0 0 112px;
|
||||||
|
height: 32px;
|
||||||
|
border-radius: 6px !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.basicConfigSection {
|
||||||
|
grid-column: 1 / -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.basicConfigList {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||||
|
gap: 10px 28px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.basicConfigItem {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
min-height: 30px;
|
||||||
|
gap: 12px;
|
||||||
|
|
||||||
|
:global(.ant-switch) {
|
||||||
|
min-width: 40px;
|
||||||
|
height: 22px;
|
||||||
|
background: #d8dce6;
|
||||||
|
}
|
||||||
|
|
||||||
|
:global(.ant-switch .ant-switch-handle) {
|
||||||
|
top: 3px;
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
:global(.ant-switch.ant-switch-checked) {
|
||||||
|
background: #5d87ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
:global(.ant-switch.ant-switch-checked .ant-switch-handle) {
|
||||||
|
inset-inline-start: calc(100% - 19px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.basicConfigLabel {
|
||||||
|
min-width: 0;
|
||||||
|
color: #111;
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 400;
|
||||||
|
line-height: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.basicConfigControl {
|
||||||
|
width: 132px !important;
|
||||||
|
|
||||||
|
:global(.ant-input-number-input),
|
||||||
|
:global(.ant-select-selection-item),
|
||||||
|
:global(.ant-select-selection-placeholder) {
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
|
||||||
|
:global(.ant-input-number),
|
||||||
|
:global(.ant-select-selector) {
|
||||||
|
height: 32px !important;
|
||||||
|
border-radius: 8px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
:global(.ant-select-selector) {
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
:global(html[data-theme='dark']) {
|
||||||
|
.artSettingsPage {
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.settingsPanel {
|
||||||
|
background: transparent;
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sectionTitle {
|
||||||
|
color: var(--xagi-layout-text-primary, #fff);
|
||||||
|
|
||||||
|
&::before,
|
||||||
|
&::after {
|
||||||
|
background: var(
|
||||||
|
--xagi-layout-border-secondary,
|
||||||
|
rgba(255, 255, 255, 0.08)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.previewBox {
|
||||||
|
border-color: var(--xagi-layout-border-primary, rgba(255, 255, 255, 0.12));
|
||||||
|
background: rgba(255, 255, 255, 0.04);
|
||||||
|
box-shadow: 0 0 10px rgba(0, 0, 0, 0.32);
|
||||||
|
|
||||||
|
&.active {
|
||||||
|
border-color: @colorPrimary;
|
||||||
|
box-shadow: 0 0 0 3px rgba(93, 135, 255, 0.18);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.settingName,
|
||||||
|
.basicConfigLabel,
|
||||||
|
.switchRow strong,
|
||||||
|
.backgroundItem span {
|
||||||
|
color: var(--xagi-layout-text-primary, #fff);
|
||||||
|
}
|
||||||
|
|
||||||
|
.switchRow small {
|
||||||
|
color: var(--xagi-layout-text-tertiary, rgba(255, 255, 255, 0.65));
|
||||||
|
}
|
||||||
|
|
||||||
|
.colorDot {
|
||||||
|
border-color: rgba(255, 255, 255, 0.18);
|
||||||
|
box-shadow: 0 0 0 1px rgba(255, 255, 255, 0.12);
|
||||||
|
|
||||||
|
&:hover,
|
||||||
|
&.active {
|
||||||
|
box-shadow: 0 0 0 3px rgba(93, 135, 255, 0.24);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.colorPicker {
|
||||||
|
:global(.ant-color-picker-trigger) {
|
||||||
|
border-color: var(
|
||||||
|
--xagi-layout-border-primary,
|
||||||
|
rgba(255, 255, 255, 0.12)
|
||||||
|
);
|
||||||
|
background: rgba(255, 255, 255, 0.04);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.backgroundItem {
|
||||||
|
border-color: var(--xagi-layout-border-primary, rgba(255, 255, 255, 0.12));
|
||||||
|
background: rgba(255, 255, 255, 0.03);
|
||||||
|
|
||||||
|
&:hover,
|
||||||
|
&.active {
|
||||||
|
border-color: @colorPrimary;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.settingActions {
|
||||||
|
border-top-color: var(
|
||||||
|
--xagi-layout-border-secondary,
|
||||||
|
rgba(255, 255, 255, 0.08)
|
||||||
|
);
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 1180px) {
|
||||||
|
.settingsPanel {
|
||||||
|
max-width: 760px;
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
|
||||||
|
.basicConfigSection,
|
||||||
|
.settingActions {
|
||||||
|
grid-column: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.basicConfigList {
|
||||||
|
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.headerActions {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.artSettingsPage {
|
||||||
|
padding: 14px 16px 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.settingsPanel {
|
||||||
|
width: 100%;
|
||||||
|
min-height: auto;
|
||||||
|
margin-left: 0;
|
||||||
|
row-gap: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.settingBoxWrap {
|
||||||
|
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||||
|
}
|
||||||
|
|
||||||
|
.basicConfigList {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
|
||||||
|
.settingActions {
|
||||||
|
justify-content: stretch;
|
||||||
|
|
||||||
|
:global(.ant-btn) {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,25 +1,34 @@
|
|||||||
import WorkspaceLayout from '@/components/WorkspaceLayout';
|
import menuLayoutDual from '@/assets/images/settings/menu_layouts/dual_column.png';
|
||||||
import BackgroundImagePanel from '@/components/business-component/ThemeConfig/BackgroundImagePanel';
|
import menuLayoutHorizontal from '@/assets/images/settings/menu_layouts/horizontal.png';
|
||||||
import NavigationStylePanel from '@/components/business-component/ThemeConfig/NavigationStylePanel';
|
import menuLayoutMixed from '@/assets/images/settings/menu_layouts/mixed.png';
|
||||||
import ThemeColorPanel from '@/components/business-component/ThemeConfig/ThemeColorPanel';
|
import menuLayoutVertical from '@/assets/images/settings/menu_layouts/vertical.png';
|
||||||
|
import menuStyleDesign from '@/assets/images/settings/menu_styles/design.png';
|
||||||
|
import menuStyleLight from '@/assets/images/settings/menu_styles/light.png';
|
||||||
|
import themeStyleDark from '@/assets/images/settings/theme_styles/dark.png';
|
||||||
|
import themeStyleLight from '@/assets/images/settings/theme_styles/light.png';
|
||||||
|
import themeStyleSystem from '@/assets/images/settings/theme_styles/system.png';
|
||||||
import {
|
import {
|
||||||
backgroundConfigs,
|
|
||||||
DEFAULT_THEME_CONFIG,
|
DEFAULT_THEME_CONFIG,
|
||||||
|
THEME_COLOR_CONFIGS,
|
||||||
} from '@/constants/theme.constants';
|
} from '@/constants/theme.constants';
|
||||||
import { useUnifiedTheme } from '@/hooks/useUnifiedTheme';
|
import { useUnifiedTheme } from '@/hooks/useUnifiedTheme';
|
||||||
import { t } from '@/services/i18nRuntime';
|
import { t } from '@/services/i18nRuntime';
|
||||||
import { apiSystemConfigUpdate } from '@/services/systemManage';
|
import { apiSystemConfigUpdate } from '@/services/systemManage';
|
||||||
|
import { unifiedThemeService } from '@/services/unifiedThemeService';
|
||||||
import {
|
import {
|
||||||
reloadConfiguration,
|
ThemeLayoutColorStyle,
|
||||||
unifiedThemeService,
|
ThemeMenuLayoutType,
|
||||||
} from '@/services/unifiedThemeService';
|
ThemeMenuStyleType,
|
||||||
import { BackgroundImage } from '@/types/background';
|
ThemePageTransitionType,
|
||||||
import { ThemeLayoutColorStyle } from '@/types/enums/theme';
|
ThemeTabStyleType,
|
||||||
|
} from '@/types/enums/theme';
|
||||||
import { ThemeConfigData } from '@/types/interfaces/systemManage';
|
import { ThemeConfigData } from '@/types/interfaces/systemManage';
|
||||||
import { App, Button } from 'antd';
|
import { CheckOutlined } from '@ant-design/icons';
|
||||||
|
import { App, Button, ColorPicker, InputNumber, Select, Switch } from 'antd';
|
||||||
|
import type { Color } from 'antd/es/color-picker';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import React, { useEffect, useMemo, useState } from 'react';
|
import React, { useMemo, useState } from 'react';
|
||||||
import { useLocation, useModel } from 'umi';
|
import { useModel } from 'umi';
|
||||||
import styles from './index.less';
|
import styles from './index.less';
|
||||||
|
|
||||||
// 使用统一的存储键名
|
// 使用统一的存储键名
|
||||||
@@ -28,13 +37,13 @@ const cx = classNames.bind(styles);
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 主题配置页面(重构版本)
|
* 主题配置页面(重构版本)
|
||||||
* 提供主题色、导航栏风格和背景图片的配置功能
|
* 提供主题色、导航栏风格和菜单布局的配置功能
|
||||||
*
|
*
|
||||||
* 重构特点:
|
* 重构特点:
|
||||||
* - 使用统一的主题数据管理服务
|
* - 使用统一的主题数据管理服务
|
||||||
* - 所有切换都是临时预览效果,不会立即保存到本地缓存
|
* - 所有切换都是临时预览效果,不会立即保存到本地缓存
|
||||||
* - Users must click the "Save Config" button to persist changes to backend and local cache
|
* - Users must click the "Save Config" button to persist changes to backend and local cache
|
||||||
* - 支持完整的自定义功能(自定义颜色、背景图片上传等)
|
* - 支持完整的自定义功能(自定义颜色等)
|
||||||
* - 配置优先级:用户设置 > 租户信息设置 > 默认配置
|
* - 配置优先级:用户设置 > 租户信息设置 > 默认配置
|
||||||
*/
|
*/
|
||||||
const ThemeConfig: React.FC = () => {
|
const ThemeConfig: React.FC = () => {
|
||||||
@@ -43,45 +52,176 @@ const ThemeConfig: React.FC = () => {
|
|||||||
// 使用统一主题管理
|
// 使用统一主题管理
|
||||||
const {
|
const {
|
||||||
primaryColor,
|
primaryColor,
|
||||||
backgroundId,
|
|
||||||
navigationStyle,
|
navigationStyle,
|
||||||
|
menuLayout,
|
||||||
|
menuStyle,
|
||||||
layoutStyle,
|
layoutStyle,
|
||||||
isNavigationDark,
|
antdTheme,
|
||||||
|
basicConfig,
|
||||||
extraColors,
|
extraColors,
|
||||||
updatePrimaryColor,
|
updatePrimaryColor,
|
||||||
updateBackground,
|
|
||||||
updateNavigationStyle,
|
|
||||||
updateLayoutStyle,
|
|
||||||
} = useUnifiedTheme();
|
} = useUnifiedTheme();
|
||||||
|
|
||||||
// 预览状态管理(临时预览,不立即保存)
|
// 预览状态管理(临时预览,不立即保存)
|
||||||
const [previewPrimaryColor, setPreviewPrimaryColor] = useState(primaryColor);
|
const [previewPrimaryColor, setPreviewPrimaryColor] = useState(primaryColor);
|
||||||
const [previewBackgroundId, setPreviewBackgroundId] = useState(backgroundId);
|
|
||||||
const [previewNavigationStyle, setPreviewNavigationStyle] =
|
const [previewNavigationStyle, setPreviewNavigationStyle] =
|
||||||
useState(navigationStyle);
|
useState(navigationStyle);
|
||||||
|
const [previewMenuLayout, setPreviewMenuLayout] =
|
||||||
|
useState<ThemeMenuLayoutType>(menuLayout);
|
||||||
|
const [previewMenuStyle, setPreviewMenuStyle] =
|
||||||
|
useState<ThemeMenuStyleType>(menuStyle);
|
||||||
const [previewLayoutStyle, setPreviewLayoutStyle] = useState(layoutStyle);
|
const [previewLayoutStyle, setPreviewLayoutStyle] = useState(layoutStyle);
|
||||||
const [previewIsNavigationDark, setPreviewIsNavigationDark] =
|
const [previewAntdTheme, setPreviewAntdTheme] = useState<'light' | 'dark'>(
|
||||||
useState(isNavigationDark);
|
antdTheme,
|
||||||
const location = useLocation();
|
);
|
||||||
|
const [previewBasicConfig, setPreviewBasicConfig] = useState(basicConfig);
|
||||||
|
|
||||||
// 转换背景配置为组件需要的格式
|
const themeStyleOptions = useMemo(
|
||||||
const backgroundImages: BackgroundImage[] = useMemo(() => {
|
() => [
|
||||||
return backgroundConfigs.map((config) => ({
|
{ id: 'light', name: '浅色', img: themeStyleLight },
|
||||||
id: config.id,
|
{ id: 'dark', name: '深色', img: themeStyleDark },
|
||||||
name: config.name,
|
{ id: 'system', name: '系统', img: themeStyleSystem },
|
||||||
path: config.url,
|
],
|
||||||
preview: config.url,
|
[],
|
||||||
}));
|
);
|
||||||
}, []);
|
|
||||||
|
|
||||||
// 根据背景图片ID获取对应的布局风格
|
const menuLayoutOptions = useMemo(
|
||||||
const getLayoutStyleByBackgroundId = (
|
() => [
|
||||||
backgroundId: string,
|
{
|
||||||
): ThemeLayoutColorStyle => {
|
id: ThemeMenuLayoutType.VERTICAL,
|
||||||
const backgroundConfig = backgroundConfigs.find(
|
name: '垂直',
|
||||||
(config) => config.id === backgroundId,
|
navigationStyle: 'style1',
|
||||||
|
img: menuLayoutVertical,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: ThemeMenuLayoutType.HORIZONTAL,
|
||||||
|
name: '水平',
|
||||||
|
navigationStyle: 'style2',
|
||||||
|
img: menuLayoutHorizontal,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: ThemeMenuLayoutType.MIXED,
|
||||||
|
name: '混合',
|
||||||
|
navigationStyle: 'style2',
|
||||||
|
img: menuLayoutMixed,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: ThemeMenuLayoutType.DUAL,
|
||||||
|
name: '双列',
|
||||||
|
navigationStyle: 'style1',
|
||||||
|
img: menuLayoutDual,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
[],
|
||||||
|
);
|
||||||
|
|
||||||
|
const menuStyleOptions = useMemo(
|
||||||
|
() => [
|
||||||
|
{ id: ThemeMenuStyleType.DESIGN, name: '设计风格', img: menuStyleDesign },
|
||||||
|
{ id: ThemeMenuStyleType.LIGHT, name: '浅色菜单', img: menuStyleLight },
|
||||||
|
],
|
||||||
|
[],
|
||||||
|
);
|
||||||
|
|
||||||
|
const basicSwitchOptions = useMemo(
|
||||||
|
() => [
|
||||||
|
{ key: 'showWorkTab', label: '开启多标签栏' },
|
||||||
|
{ key: 'uniqueOpened', label: '侧边栏开启手风琴模式' },
|
||||||
|
{ key: 'showMenuButton', label: '显示折叠侧边栏按钮' },
|
||||||
|
{ key: 'showFastEnter', label: '显示快速入口' },
|
||||||
|
{ key: 'showRefreshButton', label: '显示重载页面按钮' },
|
||||||
|
{ key: 'showCrumbs', label: '显示全局面包屑导航' },
|
||||||
|
{ key: 'showLanguage', label: '显示多语言选择' },
|
||||||
|
{ key: 'showNprogress', label: '显示顶部进度条' },
|
||||||
|
{ key: 'colorWeak', label: '色弱模式' },
|
||||||
|
{ key: 'watermarkVisible', label: '全局水印' },
|
||||||
|
],
|
||||||
|
[],
|
||||||
|
);
|
||||||
|
|
||||||
|
const tabStyleOptions = useMemo(
|
||||||
|
() => [
|
||||||
|
{ value: ThemeTabStyleType.DEFAULT, label: '默认' },
|
||||||
|
{ value: ThemeTabStyleType.CARD, label: '卡片' },
|
||||||
|
{ value: ThemeTabStyleType.GOOGLE, label: '谷歌' },
|
||||||
|
],
|
||||||
|
[],
|
||||||
|
);
|
||||||
|
|
||||||
|
const pageTransitionOptions = useMemo(
|
||||||
|
() => [
|
||||||
|
{ value: ThemePageTransitionType.NONE, label: '请选择' },
|
||||||
|
{ value: ThemePageTransitionType.FADE, label: '淡入淡出' },
|
||||||
|
{ value: ThemePageTransitionType.SLIDE_LEFT, label: '向左滑动' },
|
||||||
|
{ value: ThemePageTransitionType.SLIDE_BOTTOM, label: '向下滑动' },
|
||||||
|
{ value: ThemePageTransitionType.SLIDE_TOP, label: '向上滑动' },
|
||||||
|
],
|
||||||
|
[],
|
||||||
|
);
|
||||||
|
|
||||||
|
const customRadiusOptions = useMemo(
|
||||||
|
() => [
|
||||||
|
{ value: '0', label: '0' },
|
||||||
|
{ value: '0.25', label: '0.25' },
|
||||||
|
{ value: '0.5', label: '0.5' },
|
||||||
|
{ value: '0.75', label: '0.75' },
|
||||||
|
{ value: '1', label: '1' },
|
||||||
|
],
|
||||||
|
[],
|
||||||
|
);
|
||||||
|
|
||||||
|
const [previewThemeStyle, setPreviewThemeStyle] = useState<string>(
|
||||||
|
layoutStyle === ThemeLayoutColorStyle.DARK ? 'dark' : 'light',
|
||||||
|
);
|
||||||
|
const isDarkThemePreview = previewAntdTheme === 'dark';
|
||||||
|
const isMenuStyleChangeDisabled =
|
||||||
|
isDarkThemePreview ||
|
||||||
|
![ThemeMenuLayoutType.VERTICAL, ThemeMenuLayoutType.MIXED].includes(
|
||||||
|
previewMenuLayout,
|
||||||
);
|
);
|
||||||
return backgroundConfig?.layoutStyle || ThemeLayoutColorStyle.LIGHT;
|
|
||||||
|
const getAntdThemeByLayoutStyle = (
|
||||||
|
nextLayoutStyle: ThemeLayoutColorStyle,
|
||||||
|
): 'light' | 'dark' => {
|
||||||
|
return nextLayoutStyle === ThemeLayoutColorStyle.DARK ? 'dark' : 'light';
|
||||||
|
};
|
||||||
|
|
||||||
|
const getSystemLayoutStyle = (): ThemeLayoutColorStyle => {
|
||||||
|
if (window.matchMedia?.('(prefers-color-scheme: dark)').matches) {
|
||||||
|
return ThemeLayoutColorStyle.DARK;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ThemeLayoutColorStyle.LIGHT;
|
||||||
|
};
|
||||||
|
|
||||||
|
const isMenuStyleSupported = (
|
||||||
|
nextAntdTheme: 'light' | 'dark',
|
||||||
|
nextMenuLayout: ThemeMenuLayoutType,
|
||||||
|
) => {
|
||||||
|
return (
|
||||||
|
nextAntdTheme !== 'dark' &&
|
||||||
|
[ThemeMenuLayoutType.VERTICAL, ThemeMenuLayoutType.MIXED].includes(
|
||||||
|
nextMenuLayout,
|
||||||
|
)
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const previewThemeData = async (
|
||||||
|
updates: Parameters<typeof unifiedThemeService.updateData>[0],
|
||||||
|
) => {
|
||||||
|
await unifiedThemeService.updateData(updates, { saveToStorage: false });
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleBasicConfigChange = async (
|
||||||
|
updates: Partial<typeof previewBasicConfig>,
|
||||||
|
) => {
|
||||||
|
const nextBasicConfig = { ...previewBasicConfig, ...updates };
|
||||||
|
setPreviewBasicConfig(nextBasicConfig);
|
||||||
|
try {
|
||||||
|
await previewThemeData({ basicConfig: nextBasicConfig });
|
||||||
|
} catch (error) {
|
||||||
|
console.warn('Failed to preview basic config:', error);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// 处理主题色变更(临时预览,但实时显示效果)
|
// 处理主题色变更(临时预览,但实时显示效果)
|
||||||
@@ -96,6 +236,87 @@ const ThemeConfig: React.FC = () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleCustomColorChange = (_color: Color, hex: string) => {
|
||||||
|
handleColorChange(hex);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleThemeStyleChange = async (mode: string) => {
|
||||||
|
setPreviewThemeStyle(mode);
|
||||||
|
const nextLayoutStyle =
|
||||||
|
mode === 'dark'
|
||||||
|
? ThemeLayoutColorStyle.DARK
|
||||||
|
: mode === 'system'
|
||||||
|
? getSystemLayoutStyle()
|
||||||
|
: ThemeLayoutColorStyle.LIGHT;
|
||||||
|
const nextAntdTheme = getAntdThemeByLayoutStyle(nextLayoutStyle);
|
||||||
|
const nextMenuStyle = isMenuStyleSupported(nextAntdTheme, previewMenuLayout)
|
||||||
|
? previewMenuStyle
|
||||||
|
: ThemeMenuStyleType.DESIGN;
|
||||||
|
setPreviewLayoutStyle(nextLayoutStyle);
|
||||||
|
setPreviewAntdTheme(nextAntdTheme);
|
||||||
|
setPreviewMenuStyle(nextMenuStyle);
|
||||||
|
|
||||||
|
try {
|
||||||
|
await previewThemeData({
|
||||||
|
layoutStyle: nextLayoutStyle,
|
||||||
|
antdTheme: nextAntdTheme,
|
||||||
|
menuStyle: nextMenuStyle,
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.warn('Failed to preview theme style:', error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleMenuLayoutChange = async (
|
||||||
|
layoutId: ThemeMenuLayoutType,
|
||||||
|
styleId: string,
|
||||||
|
) => {
|
||||||
|
setPreviewMenuLayout(layoutId);
|
||||||
|
setPreviewNavigationStyle(styleId as any);
|
||||||
|
const nextMenuStyle = isMenuStyleSupported(previewAntdTheme, layoutId)
|
||||||
|
? previewMenuStyle
|
||||||
|
: ThemeMenuStyleType.DESIGN;
|
||||||
|
setPreviewMenuStyle(nextMenuStyle);
|
||||||
|
|
||||||
|
try {
|
||||||
|
await previewThemeData({
|
||||||
|
menuLayout: layoutId,
|
||||||
|
menuStyle: nextMenuStyle,
|
||||||
|
navigationStyle: styleId as any,
|
||||||
|
layoutStyle: previewLayoutStyle,
|
||||||
|
antdTheme: previewAntdTheme,
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.warn('Failed to preview menu layout:', error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleMenuStyleChange = async (styleId: ThemeMenuStyleType) => {
|
||||||
|
if (isDarkThemePreview) {
|
||||||
|
message.warning('深色主题下不可修改菜单风格');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
![ThemeMenuLayoutType.VERTICAL, ThemeMenuLayoutType.MIXED].includes(
|
||||||
|
previewMenuLayout,
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
message.warning('当前菜单布局不支持修改菜单风格,仅垂直和混合布局可用');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setPreviewMenuStyle(styleId);
|
||||||
|
|
||||||
|
try {
|
||||||
|
await previewThemeData({
|
||||||
|
menuStyle: styleId,
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.warn('Failed to preview menu style:', error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// 处理导航风格变更(临时预览,但实时显示效果)
|
// 处理导航风格变更(临时预览,但实时显示效果)
|
||||||
const handleNavigationStyleChange = async (styleId: string) => {
|
const handleNavigationStyleChange = async (styleId: string) => {
|
||||||
setPreviewNavigationStyle(styleId as any);
|
setPreviewNavigationStyle(styleId as any);
|
||||||
@@ -103,126 +324,42 @@ const ThemeConfig: React.FC = () => {
|
|||||||
// 立即应用导航风格预览效果(不保存到localStorage)
|
// 立即应用导航风格预览效果(不保存到localStorage)
|
||||||
// 使用批量更新确保导航风格和布局风格同步应用
|
// 使用批量更新确保导航风格和布局风格同步应用
|
||||||
try {
|
try {
|
||||||
// 导入 unifiedThemeService 进行批量更新
|
|
||||||
const { unifiedThemeService } = await import(
|
|
||||||
'@/services/unifiedThemeService'
|
|
||||||
);
|
|
||||||
|
|
||||||
// 批量更新导航风格和布局风格,确保CSS变量同步
|
// 批量更新导航风格和布局风格,确保CSS变量同步
|
||||||
await unifiedThemeService.updateData(
|
await previewThemeData({
|
||||||
{
|
navigationStyle: styleId as any,
|
||||||
navigationStyle: styleId as any,
|
layoutStyle: previewLayoutStyle,
|
||||||
layoutStyle: previewLayoutStyle,
|
antdTheme: previewAntdTheme,
|
||||||
},
|
});
|
||||||
{ saveToStorage: false },
|
|
||||||
);
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.warn('Failed to preview navigation style:', error);
|
console.warn('Failed to preview navigation style:', error);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// 背景图片切换处理(临时预览,但实时显示效果)
|
|
||||||
const handleBackgroundChange = async (backgroundId: string) => {
|
|
||||||
// 设置背景图片(临时预览)
|
|
||||||
setPreviewBackgroundId(backgroundId);
|
|
||||||
|
|
||||||
// 根据背景图片自动切换导航栏深浅色(临时预览)
|
|
||||||
const newLayoutStyle = getLayoutStyleByBackgroundId(backgroundId);
|
|
||||||
setPreviewLayoutStyle(newLayoutStyle);
|
|
||||||
setPreviewIsNavigationDark(newLayoutStyle === ThemeLayoutColorStyle.DARK);
|
|
||||||
|
|
||||||
// 立即应用背景图预览效果(不保存到localStorage)
|
|
||||||
try {
|
|
||||||
await updateBackground(backgroundId, { saveToStorage: false });
|
|
||||||
} catch (error) {
|
|
||||||
console.warn('Failed to preview background image:', error);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 显示联动提示
|
|
||||||
const backgroundConfig = backgroundConfigs.find(
|
|
||||||
(config) => config.id === backgroundId,
|
|
||||||
);
|
|
||||||
if (backgroundConfig) {
|
|
||||||
message.info(
|
|
||||||
t(
|
|
||||||
'PC.Pages.SystemThemeConfig.autoSwitchedNavModePreview',
|
|
||||||
newLayoutStyle === ThemeLayoutColorStyle.DARK
|
|
||||||
? t('PC.Pages.SystemThemeConfig.darkMode')
|
|
||||||
: t('PC.Pages.SystemThemeConfig.lightMode'),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// 切换导航栏深浅色(临时预览,但实时显示效果)
|
|
||||||
const handleNavigationThemeToggle = async () => {
|
|
||||||
const newLayoutStyle =
|
|
||||||
previewLayoutStyle === ThemeLayoutColorStyle.DARK
|
|
||||||
? ThemeLayoutColorStyle.LIGHT
|
|
||||||
: ThemeLayoutColorStyle.DARK;
|
|
||||||
setPreviewLayoutStyle(newLayoutStyle);
|
|
||||||
setPreviewIsNavigationDark(newLayoutStyle === ThemeLayoutColorStyle.DARK);
|
|
||||||
|
|
||||||
// 立即应用布局风格预览效果(不保存到localStorage)
|
|
||||||
try {
|
|
||||||
await updateLayoutStyle(newLayoutStyle, { saveToStorage: false });
|
|
||||||
} catch (error) {
|
|
||||||
console.warn('Failed to preview layout style:', error);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 检查当前背景是否与新的导航栏深浅色匹配
|
|
||||||
const currentBackgroundLayoutStyle =
|
|
||||||
getLayoutStyleByBackgroundId(previewBackgroundId);
|
|
||||||
|
|
||||||
if (currentBackgroundLayoutStyle !== newLayoutStyle) {
|
|
||||||
// 当前背景不匹配,自动切换到匹配的背景(临时预览)
|
|
||||||
const matchingBackground = backgroundConfigs.find(
|
|
||||||
(config) => config.layoutStyle === newLayoutStyle,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (matchingBackground) {
|
|
||||||
// 切换背景但不触发布局风格联动(避免循环)
|
|
||||||
setPreviewBackgroundId(matchingBackground.id);
|
|
||||||
|
|
||||||
// 立即应用背景图预览效果(不保存到localStorage)
|
|
||||||
try {
|
|
||||||
await updateBackground(matchingBackground.id, {
|
|
||||||
saveToStorage: false,
|
|
||||||
});
|
|
||||||
} catch (error) {
|
|
||||||
console.warn('Failed to preview background image:', error);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 显示背景自动匹配提示
|
|
||||||
message.info(
|
|
||||||
t(
|
|
||||||
'PC.Pages.SystemThemeConfig.autoSwitchedBackgroundPreview',
|
|
||||||
matchingBackground.name,
|
|
||||||
newLayoutStyle === ThemeLayoutColorStyle.DARK
|
|
||||||
? t('PC.Pages.SystemThemeConfig.darkMode')
|
|
||||||
: t('PC.Pages.SystemThemeConfig.lightMode'),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// 保存配置到后端
|
// 保存配置到后端
|
||||||
const handleSave = async () => {
|
const handleSave = async () => {
|
||||||
try {
|
try {
|
||||||
// 首先使用统一主题服务保存预览的配置
|
// 首先使用统一主题服务保存预览的配置
|
||||||
await updatePrimaryColor(previewPrimaryColor);
|
await unifiedThemeService.updateData({
|
||||||
await updateBackground(previewBackgroundId);
|
primaryColor: previewPrimaryColor,
|
||||||
await updateNavigationStyle(previewNavigationStyle);
|
backgroundId: '',
|
||||||
await updateLayoutStyle(previewLayoutStyle);
|
navigationStyle: previewNavigationStyle,
|
||||||
|
menuLayout: previewMenuLayout,
|
||||||
|
menuStyle: previewMenuStyle,
|
||||||
|
basicConfig: previewBasicConfig,
|
||||||
|
layoutStyle: previewLayoutStyle,
|
||||||
|
antdTheme: previewAntdTheme,
|
||||||
|
});
|
||||||
|
|
||||||
// 构建后端所需的数据格式
|
// 构建后端所需的数据格式
|
||||||
const themeConfig: ThemeConfigData = {
|
const themeConfig: ThemeConfigData = {
|
||||||
primaryColor: previewPrimaryColor,
|
primaryColor: previewPrimaryColor,
|
||||||
backgroundId: previewBackgroundId,
|
backgroundId: '',
|
||||||
antdTheme: DEFAULT_THEME_CONFIG.THEME, // 目前没有支持切换Ant Design主题
|
antdTheme: previewAntdTheme,
|
||||||
layoutStyle: previewLayoutStyle, // 导航栏深浅色
|
layoutStyle: previewLayoutStyle, // 导航栏深浅色
|
||||||
navigationStyle: previewNavigationStyle, // 导航风格 ID
|
navigationStyle: previewNavigationStyle, // 导航风格 ID
|
||||||
|
menuLayout: previewMenuLayout,
|
||||||
|
menuStyle: previewMenuStyle,
|
||||||
|
basicConfig: previewBasicConfig,
|
||||||
timestamp: Date.now(),
|
timestamp: Date.now(),
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -244,86 +381,251 @@ const ThemeConfig: React.FC = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// 重置为默认配置
|
// 重置为默认配置
|
||||||
const handleReset = () => {
|
const handleReset = async () => {
|
||||||
setPreviewPrimaryColor('#5147ff'); // 默认蓝色
|
setPreviewPrimaryColor(DEFAULT_THEME_CONFIG.PRIMARY_COLOR); // 默认蓝色
|
||||||
setPreviewBackgroundId('bg-variant-1'); // 默认背景
|
|
||||||
setPreviewNavigationStyle('style1' as any);
|
setPreviewNavigationStyle('style1' as any);
|
||||||
|
setPreviewThemeStyle('light');
|
||||||
|
setPreviewMenuLayout(ThemeMenuLayoutType.DUAL);
|
||||||
|
setPreviewMenuStyle(DEFAULT_THEME_CONFIG.MENU_STYLE);
|
||||||
|
setPreviewBasicConfig({ ...DEFAULT_THEME_CONFIG.BASIC_CONFIG });
|
||||||
setPreviewLayoutStyle(ThemeLayoutColorStyle.LIGHT);
|
setPreviewLayoutStyle(ThemeLayoutColorStyle.LIGHT);
|
||||||
setPreviewIsNavigationDark(false);
|
setPreviewAntdTheme('light');
|
||||||
|
|
||||||
|
try {
|
||||||
|
await previewThemeData({
|
||||||
|
primaryColor: DEFAULT_THEME_CONFIG.PRIMARY_COLOR,
|
||||||
|
backgroundId: '',
|
||||||
|
navigationStyle: 'style1' as any,
|
||||||
|
menuLayout: ThemeMenuLayoutType.DUAL,
|
||||||
|
menuStyle: DEFAULT_THEME_CONFIG.MENU_STYLE,
|
||||||
|
basicConfig: { ...DEFAULT_THEME_CONFIG.BASIC_CONFIG },
|
||||||
|
layoutStyle: ThemeLayoutColorStyle.LIGHT,
|
||||||
|
antdTheme: 'light',
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.warn('Failed to preview reset theme:', error);
|
||||||
|
}
|
||||||
|
|
||||||
message.info(t('PC.Pages.SystemThemeConfig.resetPreview'));
|
message.info(t('PC.Pages.SystemThemeConfig.resetPreview'));
|
||||||
};
|
};
|
||||||
|
|
||||||
// 恢复到已保存状态(页面加载状态)
|
|
||||||
const handleRestore = React.useCallback(() => {
|
|
||||||
reloadConfiguration(); // 重新加载存储中的配置
|
|
||||||
const savedData = unifiedThemeService.getCurrentData(); // 获取重新加载后的数据
|
|
||||||
|
|
||||||
setPreviewPrimaryColor(savedData.primaryColor);
|
|
||||||
setPreviewBackgroundId(savedData.backgroundId);
|
|
||||||
setPreviewNavigationStyle(savedData.navigationStyle);
|
|
||||||
setPreviewLayoutStyle(savedData.layoutStyle);
|
|
||||||
setPreviewIsNavigationDark(
|
|
||||||
savedData.layoutStyle === ThemeLayoutColorStyle.DARK,
|
|
||||||
);
|
|
||||||
|
|
||||||
// 使用 setTimeout 确保在 remount 后消息能正常显示
|
|
||||||
// setTimeout(() => {
|
|
||||||
// message.info('Reset to default config (preview mode)');
|
|
||||||
// }, 200);
|
|
||||||
}, [message]);
|
|
||||||
|
|
||||||
// 监听 location.state 变化
|
|
||||||
useEffect(() => {
|
|
||||||
const state = location.state as any;
|
|
||||||
if (state?._t) {
|
|
||||||
handleRestore();
|
|
||||||
}
|
|
||||||
}, [location.state, handleRestore]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<WorkspaceLayout
|
<>
|
||||||
title={t('PC.Pages.SystemThemeConfig.pageTitle')}
|
<div className={cx(styles.artSettingsPage)}>
|
||||||
extraContent={
|
<aside className={cx(styles.settingsPanel)}>
|
||||||
<div style={{ padding: '12px 6px' }}>
|
<div className={cx(styles.settingSection)}>
|
||||||
|
<div className={cx(styles.sectionTitle)}>
|
||||||
|
<span>主题风格</span>
|
||||||
|
</div>
|
||||||
|
<div className={cx(styles.settingBoxWrap)}>
|
||||||
|
{themeStyleOptions.map((item) => (
|
||||||
|
<button
|
||||||
|
key={item.id}
|
||||||
|
type="button"
|
||||||
|
className={cx(styles.settingItem)}
|
||||||
|
onClick={() => handleThemeStyleChange(item.id)}
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
className={cx(styles.previewBox, {
|
||||||
|
[styles.active]: previewThemeStyle === item.id,
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
<img src={item.img} alt={item.name} />
|
||||||
|
</span>
|
||||||
|
<span className={cx(styles.settingName)}>{item.name}</span>
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className={cx(styles.settingSection)}>
|
||||||
|
<div className={cx(styles.sectionTitle)}>
|
||||||
|
<span>菜单布局</span>
|
||||||
|
</div>
|
||||||
|
<div className={cx(styles.settingBoxWrap)}>
|
||||||
|
{menuLayoutOptions.map((item, index) => (
|
||||||
|
<button
|
||||||
|
key={item.id}
|
||||||
|
type="button"
|
||||||
|
className={cx(styles.settingItem)}
|
||||||
|
onClick={() =>
|
||||||
|
handleMenuLayoutChange(item.id, item.navigationStyle)
|
||||||
|
}
|
||||||
|
title={item.name}
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
className={cx(
|
||||||
|
styles.previewBox,
|
||||||
|
index > 2 && styles.withTopMargin,
|
||||||
|
{ [styles.active]: previewMenuLayout === item.id },
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<img src={item.img} alt={item.name} />
|
||||||
|
</span>
|
||||||
|
<span className={cx(styles.settingName)}>{item.name}</span>
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className={cx(styles.settingSection)}>
|
||||||
|
<div className={cx(styles.sectionTitle)}>
|
||||||
|
<span>菜单风格</span>
|
||||||
|
</div>
|
||||||
|
<div className={cx(styles.settingBoxWrap)}>
|
||||||
|
{menuStyleOptions.map((item) => (
|
||||||
|
<button
|
||||||
|
key={item.id}
|
||||||
|
type="button"
|
||||||
|
className={cx(styles.settingItem, {
|
||||||
|
[styles.disabled]: isMenuStyleChangeDisabled,
|
||||||
|
})}
|
||||||
|
onClick={() => handleMenuStyleChange(item.id)}
|
||||||
|
title={
|
||||||
|
isMenuStyleChangeDisabled
|
||||||
|
? isDarkThemePreview
|
||||||
|
? '深色主题下不可修改菜单风格'
|
||||||
|
: '当前菜单布局不支持修改菜单风格,仅垂直和混合布局可用'
|
||||||
|
: item.name
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
className={cx(styles.previewBox, {
|
||||||
|
[styles.active]: previewMenuStyle === item.id,
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
<img src={item.img} alt={item.name} />
|
||||||
|
</span>
|
||||||
|
<span className={cx(styles.settingName)}>{item.name}</span>
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className={cx(styles.settingSection)}>
|
||||||
|
<div className={cx(styles.sectionTitle)}>
|
||||||
|
<span>系统主题色</span>
|
||||||
|
</div>
|
||||||
|
<div className={cx(styles.colorGrid)}>
|
||||||
|
{[
|
||||||
|
...THEME_COLOR_CONFIGS,
|
||||||
|
...extraColors.map((color) => ({ color, name: '自定义' })),
|
||||||
|
].map((item) => (
|
||||||
|
<button
|
||||||
|
key={item.color}
|
||||||
|
type="button"
|
||||||
|
className={cx(styles.colorDot, {
|
||||||
|
[styles.active]: previewPrimaryColor === item.color,
|
||||||
|
})}
|
||||||
|
style={{ backgroundColor: item.color }}
|
||||||
|
title={item.name}
|
||||||
|
onClick={() => handleColorChange(item.color)}
|
||||||
|
>
|
||||||
|
{previewPrimaryColor === item.color && <CheckOutlined />}
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
<ColorPicker
|
||||||
|
value={previewPrimaryColor}
|
||||||
|
format="hex"
|
||||||
|
onChange={handleCustomColorChange}
|
||||||
|
className={cx(styles.colorPicker)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className={cx(styles.settingSection, styles.basicConfigSection)}>
|
||||||
|
<div className={cx(styles.sectionTitle)}>
|
||||||
|
<span>基础配置</span>
|
||||||
|
</div>
|
||||||
|
<div className={cx(styles.basicConfigList)}>
|
||||||
|
{basicSwitchOptions.map((item) => (
|
||||||
|
<div key={item.key} className={cx(styles.basicConfigItem)}>
|
||||||
|
<span className={cx(styles.basicConfigLabel)}>
|
||||||
|
{item.label}
|
||||||
|
</span>
|
||||||
|
<Switch
|
||||||
|
checked={
|
||||||
|
previewBasicConfig[
|
||||||
|
item.key as keyof typeof previewBasicConfig
|
||||||
|
] as boolean
|
||||||
|
}
|
||||||
|
onChange={(checked) =>
|
||||||
|
handleBasicConfigChange({ [item.key]: checked })
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
|
||||||
|
<div className={cx(styles.basicConfigItem)}>
|
||||||
|
<span className={cx(styles.basicConfigLabel)}>菜单宽度</span>
|
||||||
|
<InputNumber
|
||||||
|
min={180}
|
||||||
|
max={320}
|
||||||
|
step={10}
|
||||||
|
value={previewBasicConfig.menuOpenWidth}
|
||||||
|
onChange={(value) =>
|
||||||
|
handleBasicConfigChange({
|
||||||
|
menuOpenWidth: Number(value || 180),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
className={cx(styles.basicConfigControl)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className={cx(styles.basicConfigItem)}>
|
||||||
|
<span className={cx(styles.basicConfigLabel)}>标签页风格</span>
|
||||||
|
<Select
|
||||||
|
value={previewBasicConfig.tabStyle}
|
||||||
|
options={tabStyleOptions}
|
||||||
|
onChange={(value) =>
|
||||||
|
handleBasicConfigChange({ tabStyle: value })
|
||||||
|
}
|
||||||
|
className={cx(styles.basicConfigControl)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className={cx(styles.basicConfigItem)}>
|
||||||
|
<span className={cx(styles.basicConfigLabel)}>
|
||||||
|
页面切换动画
|
||||||
|
</span>
|
||||||
|
<Select
|
||||||
|
value={previewBasicConfig.pageTransition}
|
||||||
|
options={pageTransitionOptions}
|
||||||
|
onChange={(value) =>
|
||||||
|
handleBasicConfigChange({ pageTransition: value })
|
||||||
|
}
|
||||||
|
className={cx(styles.basicConfigControl)}
|
||||||
|
placeholder="请选择"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className={cx(styles.basicConfigItem)}>
|
||||||
|
<span className={cx(styles.basicConfigLabel)}>自定义圆角</span>
|
||||||
|
<Select
|
||||||
|
value={previewBasicConfig.customRadius}
|
||||||
|
options={customRadiusOptions}
|
||||||
|
onChange={(value) =>
|
||||||
|
handleBasicConfigChange({ customRadius: value })
|
||||||
|
}
|
||||||
|
className={cx(styles.basicConfigControl)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
{hasPermission('system_theme_config_save') && (
|
{hasPermission('system_theme_config_save') && (
|
||||||
<>
|
<div className={cx(styles.settingActions)}>
|
||||||
<Button type="primary" onClick={handleSave}>
|
<Button type="primary" onClick={handleSave}>
|
||||||
{t('PC.Pages.SystemThemeConfig.saveConfig')}
|
{t('PC.Pages.SystemThemeConfig.saveConfig')}
|
||||||
</Button>
|
</Button>
|
||||||
<Button style={{ marginLeft: 12 }} onClick={handleReset}>
|
<Button danger ghost onClick={handleReset}>
|
||||||
{t('PC.Pages.SystemThemeConfig.resetDefault')}
|
{t('PC.Pages.SystemThemeConfig.resetDefault')}
|
||||||
</Button>
|
</Button>
|
||||||
</>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</aside>
|
||||||
}
|
|
||||||
>
|
|
||||||
<div className={cx(styles.configContainer)}>
|
|
||||||
<div className={cx(styles.configItem)}>
|
|
||||||
<ThemeColorPanel
|
|
||||||
currentColor={previewPrimaryColor}
|
|
||||||
onColorChange={handleColorChange}
|
|
||||||
extraColors={extraColors}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className={cx(styles.configItem)}>
|
|
||||||
<NavigationStylePanel
|
|
||||||
isNavigationDarkMode={previewIsNavigationDark}
|
|
||||||
onNavigationThemeToggle={handleNavigationThemeToggle}
|
|
||||||
onNavigationStyleChange={handleNavigationStyleChange}
|
|
||||||
currentNavigationStyle={previewNavigationStyle}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className={cx(styles.configItem)}>
|
|
||||||
<BackgroundImagePanel
|
|
||||||
backgroundImages={backgroundImages}
|
|
||||||
currentBackground={previewBackgroundId}
|
|
||||||
onBackgroundChange={handleBackgroundChange}
|
|
||||||
enableCustomUpload={false} //先关闭后期考虑开启
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</WorkspaceLayout>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ export const getTenantInfo = async (): Promise<TenantInfo> => {
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
// 使用统一主题数据,如果没有则使用默认值
|
// 使用统一主题数据,如果没有则使用默认值
|
||||||
defaultThemeColor: currentData.primaryColor || '#5147ff',
|
defaultThemeColor: currentData.primaryColor || '#5D87FF',
|
||||||
defaultBackgroundId: currentBackground || 'variant-1',
|
defaultBackgroundId: currentBackground || 'variant-1',
|
||||||
defaultNavigationStyleId:
|
defaultNavigationStyleId:
|
||||||
currentNavStyle === 'style2' ? 'style2' : 'style1',
|
currentNavStyle === 'style2' ? 'style2' : 'style1',
|
||||||
|
|||||||
@@ -17,13 +17,33 @@ import {
|
|||||||
STORAGE_KEYS,
|
STORAGE_KEYS,
|
||||||
STYLE_CONFIGS,
|
STYLE_CONFIGS,
|
||||||
THEME_COLOR_CONFIGS,
|
THEME_COLOR_CONFIGS,
|
||||||
backgroundConfigs,
|
|
||||||
} from '@/constants/theme.constants';
|
} from '@/constants/theme.constants';
|
||||||
import {
|
import {
|
||||||
ThemeLayoutColorStyle,
|
ThemeLayoutColorStyle,
|
||||||
|
ThemeMenuLayoutType,
|
||||||
|
ThemeMenuStyleType,
|
||||||
ThemeNavigationStyleType,
|
ThemeNavigationStyleType,
|
||||||
|
ThemePageTransitionType,
|
||||||
|
ThemeTabStyleType,
|
||||||
} from '@/types/enums/theme';
|
} from '@/types/enums/theme';
|
||||||
|
|
||||||
|
export interface UnifiedThemeBasicConfig {
|
||||||
|
showWorkTab: boolean;
|
||||||
|
uniqueOpened: boolean;
|
||||||
|
showMenuButton: boolean;
|
||||||
|
showFastEnter: boolean;
|
||||||
|
showRefreshButton: boolean;
|
||||||
|
showCrumbs: boolean;
|
||||||
|
showLanguage: boolean;
|
||||||
|
showNprogress: boolean;
|
||||||
|
colorWeak: boolean;
|
||||||
|
watermarkVisible: boolean;
|
||||||
|
menuOpenWidth: number;
|
||||||
|
tabStyle: ThemeTabStyleType;
|
||||||
|
pageTransition: ThemePageTransitionType;
|
||||||
|
customRadius: string;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 统一主题配置接口
|
* 统一主题配置接口
|
||||||
*/
|
*/
|
||||||
@@ -34,6 +54,8 @@ export interface UnifiedThemeData {
|
|||||||
|
|
||||||
// 导航配置
|
// 导航配置
|
||||||
navigationStyle: ThemeNavigationStyleType;
|
navigationStyle: ThemeNavigationStyleType;
|
||||||
|
menuLayout: ThemeMenuLayoutType;
|
||||||
|
menuStyle: ThemeMenuStyleType;
|
||||||
layoutStyle: ThemeLayoutColorStyle;
|
layoutStyle: ThemeLayoutColorStyle;
|
||||||
|
|
||||||
// 背景配置
|
// 背景配置
|
||||||
@@ -42,6 +64,9 @@ export interface UnifiedThemeData {
|
|||||||
// 语言配置
|
// 语言配置
|
||||||
language: 'zh-CN' | 'en-US';
|
language: 'zh-CN' | 'en-US';
|
||||||
|
|
||||||
|
// 基础配置
|
||||||
|
basicConfig: UnifiedThemeBasicConfig;
|
||||||
|
|
||||||
// 元数据
|
// 元数据
|
||||||
timestamp: number;
|
timestamp: number;
|
||||||
source: 'user' | 'tenant' | 'default';
|
source: 'user' | 'tenant' | 'default';
|
||||||
@@ -148,14 +173,64 @@ class UnifiedThemeService {
|
|||||||
antdTheme: DEFAULT_THEME_CONFIG.THEME as 'light' | 'dark',
|
antdTheme: DEFAULT_THEME_CONFIG.THEME as 'light' | 'dark',
|
||||||
navigationStyle:
|
navigationStyle:
|
||||||
DEFAULT_THEME_CONFIG.NAVIGATION_STYLE as ThemeNavigationStyleType,
|
DEFAULT_THEME_CONFIG.NAVIGATION_STYLE as ThemeNavigationStyleType,
|
||||||
|
menuLayout: DEFAULT_THEME_CONFIG.MENU_LAYOUT as ThemeMenuLayoutType,
|
||||||
|
menuStyle: DEFAULT_THEME_CONFIG.MENU_STYLE as ThemeMenuStyleType,
|
||||||
layoutStyle: DEFAULT_THEME_CONFIG.LAYOUT_STYLE as ThemeLayoutColorStyle,
|
layoutStyle: DEFAULT_THEME_CONFIG.LAYOUT_STYLE as ThemeLayoutColorStyle,
|
||||||
backgroundId: DEFAULT_THEME_CONFIG.BACKGROUND_ID,
|
backgroundId: DEFAULT_THEME_CONFIG.BACKGROUND_ID,
|
||||||
language: DEFAULT_THEME_CONFIG.LANGUAGE as 'zh-CN' | 'en-US',
|
language: DEFAULT_THEME_CONFIG.LANGUAGE as 'zh-CN' | 'en-US',
|
||||||
|
basicConfig: { ...DEFAULT_THEME_CONFIG.BASIC_CONFIG },
|
||||||
timestamp: Date.now(),
|
timestamp: Date.now(),
|
||||||
source: 'default',
|
source: 'default',
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private normalizeMenuStyle(
|
||||||
|
menuStyle?: ThemeMenuStyleType,
|
||||||
|
): ThemeMenuStyleType {
|
||||||
|
if (
|
||||||
|
menuStyle === ThemeMenuStyleType.DESIGN ||
|
||||||
|
menuStyle === ThemeMenuStyleType.LIGHT
|
||||||
|
) {
|
||||||
|
return menuStyle;
|
||||||
|
}
|
||||||
|
|
||||||
|
return DEFAULT_THEME_CONFIG.MENU_STYLE as ThemeMenuStyleType;
|
||||||
|
}
|
||||||
|
|
||||||
|
private normalizeBasicConfig(config?: any): UnifiedThemeBasicConfig {
|
||||||
|
const defaults = DEFAULT_THEME_CONFIG.BASIC_CONFIG;
|
||||||
|
const menuOpenWidth = Number(config?.menuOpenWidth);
|
||||||
|
const customRadius = String(config?.customRadius ?? defaults.customRadius);
|
||||||
|
const tabStyleValues = Object.values(ThemeTabStyleType);
|
||||||
|
const pageTransitionValues = Object.values(ThemePageTransitionType);
|
||||||
|
|
||||||
|
return {
|
||||||
|
showWorkTab: config?.showWorkTab ?? defaults.showWorkTab,
|
||||||
|
uniqueOpened: config?.uniqueOpened ?? defaults.uniqueOpened,
|
||||||
|
showMenuButton: config?.showMenuButton ?? defaults.showMenuButton,
|
||||||
|
showFastEnter: config?.showFastEnter ?? defaults.showFastEnter,
|
||||||
|
showRefreshButton:
|
||||||
|
config?.showRefreshButton ?? defaults.showRefreshButton,
|
||||||
|
showCrumbs: config?.showCrumbs ?? defaults.showCrumbs,
|
||||||
|
showLanguage: config?.showLanguage ?? defaults.showLanguage,
|
||||||
|
showNprogress: config?.showNprogress ?? defaults.showNprogress,
|
||||||
|
colorWeak: config?.colorWeak ?? defaults.colorWeak,
|
||||||
|
watermarkVisible: config?.watermarkVisible ?? defaults.watermarkVisible,
|
||||||
|
menuOpenWidth: Number.isFinite(menuOpenWidth)
|
||||||
|
? Math.min(320, Math.max(180, menuOpenWidth))
|
||||||
|
: defaults.menuOpenWidth,
|
||||||
|
tabStyle: tabStyleValues.includes(config?.tabStyle)
|
||||||
|
? config.tabStyle
|
||||||
|
: defaults.tabStyle,
|
||||||
|
pageTransition: pageTransitionValues.includes(config?.pageTransition)
|
||||||
|
? config.pageTransition
|
||||||
|
: defaults.pageTransition,
|
||||||
|
customRadius: ['0', '0.25', '0.5', '0.75', '1'].includes(customRadius)
|
||||||
|
? customRadius
|
||||||
|
: defaults.customRadius,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 标准化用户配置格式
|
* 标准化用户配置格式
|
||||||
*/
|
*/
|
||||||
@@ -165,9 +240,12 @@ class UnifiedThemeService {
|
|||||||
primaryColor: config.selectedThemeColor || defaults.primaryColor,
|
primaryColor: config.selectedThemeColor || defaults.primaryColor,
|
||||||
antdTheme: config.antdTheme || defaults.antdTheme,
|
antdTheme: config.antdTheme || defaults.antdTheme,
|
||||||
navigationStyle: config.navigationStyleId || defaults.navigationStyle,
|
navigationStyle: config.navigationStyleId || defaults.navigationStyle,
|
||||||
|
menuLayout: config.menuLayout || defaults.menuLayout,
|
||||||
|
menuStyle: this.normalizeMenuStyle(config.menuStyle),
|
||||||
layoutStyle: config.navigationStyle || defaults.layoutStyle,
|
layoutStyle: config.navigationStyle || defaults.layoutStyle,
|
||||||
backgroundId: config.selectedBackgroundId || defaults.backgroundId,
|
backgroundId: '',
|
||||||
language: config.language || defaults.language,
|
language: config.language || defaults.language,
|
||||||
|
basicConfig: this.normalizeBasicConfig(config.basicConfig),
|
||||||
timestamp: config.timestamp || Date.now(),
|
timestamp: config.timestamp || Date.now(),
|
||||||
source: 'user',
|
source: 'user',
|
||||||
};
|
};
|
||||||
@@ -182,9 +260,12 @@ class UnifiedThemeService {
|
|||||||
primaryColor: settings.primaryColor || defaults.primaryColor,
|
primaryColor: settings.primaryColor || defaults.primaryColor,
|
||||||
antdTheme: settings.theme || defaults.antdTheme,
|
antdTheme: settings.theme || defaults.antdTheme,
|
||||||
navigationStyle: defaults.navigationStyle,
|
navigationStyle: defaults.navigationStyle,
|
||||||
|
menuLayout: settings.menuLayout || defaults.menuLayout,
|
||||||
|
menuStyle: this.normalizeMenuStyle(settings.menuStyle),
|
||||||
layoutStyle: defaults.layoutStyle,
|
layoutStyle: defaults.layoutStyle,
|
||||||
backgroundId: settings.backgroundImageId || defaults.backgroundId,
|
backgroundId: '',
|
||||||
language: settings.language || defaults.language,
|
language: settings.language || defaults.language,
|
||||||
|
basicConfig: this.normalizeBasicConfig(settings.basicConfig),
|
||||||
timestamp: Date.now(),
|
timestamp: Date.now(),
|
||||||
source: 'user',
|
source: 'user',
|
||||||
};
|
};
|
||||||
@@ -199,9 +280,12 @@ class UnifiedThemeService {
|
|||||||
primaryColor: config.selectedThemeColor || defaults.primaryColor,
|
primaryColor: config.selectedThemeColor || defaults.primaryColor,
|
||||||
antdTheme: config.antdTheme || defaults.antdTheme,
|
antdTheme: config.antdTheme || defaults.antdTheme,
|
||||||
navigationStyle: config.navigationStyleId || defaults.navigationStyle,
|
navigationStyle: config.navigationStyleId || defaults.navigationStyle,
|
||||||
|
menuLayout: config.menuLayout || defaults.menuLayout,
|
||||||
|
menuStyle: this.normalizeMenuStyle(config.menuStyle),
|
||||||
layoutStyle: config.navigationStyle || defaults.layoutStyle,
|
layoutStyle: config.navigationStyle || defaults.layoutStyle,
|
||||||
backgroundId: config.selectedBackgroundId || defaults.backgroundId,
|
backgroundId: '',
|
||||||
language: config.language || defaults.language,
|
language: config.language || defaults.language,
|
||||||
|
basicConfig: this.normalizeBasicConfig(config.basicConfig),
|
||||||
timestamp: config.timestamp || Date.now(),
|
timestamp: config.timestamp || Date.now(),
|
||||||
source: 'tenant',
|
source: 'tenant',
|
||||||
};
|
};
|
||||||
@@ -277,6 +361,26 @@ class UnifiedThemeService {
|
|||||||
await this.updateData({ navigationStyle: style }, options);
|
await this.updateData({ navigationStyle: style }, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新菜单布局
|
||||||
|
*/
|
||||||
|
async updateMenuLayout(
|
||||||
|
menuLayout: ThemeMenuLayoutType,
|
||||||
|
options: UpdateOptions = {},
|
||||||
|
): Promise<void> {
|
||||||
|
await this.updateData({ menuLayout }, options);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新菜单视觉风格
|
||||||
|
*/
|
||||||
|
async updateMenuStyle(
|
||||||
|
menuStyle: ThemeMenuStyleType,
|
||||||
|
options: UpdateOptions = {},
|
||||||
|
): Promise<void> {
|
||||||
|
await this.updateData({ menuStyle }, options);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 更新布局风格(导航栏深浅色)
|
* 更新布局风格(导航栏深浅色)
|
||||||
*/
|
*/
|
||||||
@@ -294,19 +398,7 @@ class UnifiedThemeService {
|
|||||||
backgroundId: string,
|
backgroundId: string,
|
||||||
options: UpdateOptions = {},
|
options: UpdateOptions = {},
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
// 根据背景图自动设置布局风格
|
await this.updateData({ backgroundId: '' }, options);
|
||||||
const backgroundConfig = backgroundConfigs.find(
|
|
||||||
(bg) => bg.id === backgroundId,
|
|
||||||
);
|
|
||||||
|
|
||||||
const layoutStyle = backgroundConfig?.layoutStyle;
|
|
||||||
|
|
||||||
const updates: Partial<UnifiedThemeData> = { backgroundId };
|
|
||||||
if (layoutStyle) {
|
|
||||||
updates.layoutStyle = layoutStyle;
|
|
||||||
}
|
|
||||||
|
|
||||||
await this.updateData(updates, options);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -336,6 +428,13 @@ class UnifiedThemeService {
|
|||||||
this.currentData = {
|
this.currentData = {
|
||||||
...this.currentData,
|
...this.currentData,
|
||||||
...(updates || {}),
|
...(updates || {}),
|
||||||
|
backgroundId: '',
|
||||||
|
basicConfig: this.normalizeBasicConfig(
|
||||||
|
updates?.basicConfig || this.currentData.basicConfig,
|
||||||
|
),
|
||||||
|
menuStyle: this.normalizeMenuStyle(
|
||||||
|
updates?.menuStyle || this.currentData.menuStyle,
|
||||||
|
),
|
||||||
timestamp: Date.now(),
|
timestamp: Date.now(),
|
||||||
source: 'user', // 用户操作都标记为用户来源
|
source: 'user', // 用户操作都标记为用户来源
|
||||||
};
|
};
|
||||||
@@ -365,10 +464,13 @@ class UnifiedThemeService {
|
|||||||
// 保存到用户主题配置
|
// 保存到用户主题配置
|
||||||
const userThemeConfig = {
|
const userThemeConfig = {
|
||||||
selectedThemeColor: this.currentData.primaryColor,
|
selectedThemeColor: this.currentData.primaryColor,
|
||||||
selectedBackgroundId: this.currentData.backgroundId,
|
selectedBackgroundId: '',
|
||||||
antdTheme: this.currentData.antdTheme,
|
antdTheme: this.currentData.antdTheme,
|
||||||
navigationStyle: this.currentData.layoutStyle,
|
navigationStyle: this.currentData.layoutStyle,
|
||||||
navigationStyleId: this.currentData.navigationStyle,
|
navigationStyleId: this.currentData.navigationStyle,
|
||||||
|
menuLayout: this.currentData.menuLayout,
|
||||||
|
menuStyle: this.currentData.menuStyle,
|
||||||
|
basicConfig: this.currentData.basicConfig,
|
||||||
language: this.currentData.language,
|
language: this.currentData.language,
|
||||||
timestamp: this.currentData.timestamp,
|
timestamp: this.currentData.timestamp,
|
||||||
};
|
};
|
||||||
@@ -383,7 +485,10 @@ class UnifiedThemeService {
|
|||||||
theme: this.currentData.antdTheme,
|
theme: this.currentData.antdTheme,
|
||||||
language: this.currentData.language,
|
language: this.currentData.language,
|
||||||
primaryColor: this.currentData.primaryColor,
|
primaryColor: this.currentData.primaryColor,
|
||||||
backgroundImageId: this.currentData.backgroundId,
|
backgroundImageId: '',
|
||||||
|
menuLayout: this.currentData.menuLayout,
|
||||||
|
menuStyle: this.currentData.menuStyle,
|
||||||
|
basicConfig: this.currentData.basicConfig,
|
||||||
};
|
};
|
||||||
|
|
||||||
localStorage.setItem(
|
localStorage.setItem(
|
||||||
@@ -408,22 +513,7 @@ class UnifiedThemeService {
|
|||||||
this.currentData.primaryColor,
|
this.currentData.primaryColor,
|
||||||
);
|
);
|
||||||
|
|
||||||
// 设置背景图
|
root.style.removeProperty('--xagi-background-image');
|
||||||
if (this.currentData.backgroundId) {
|
|
||||||
// 查找背景图配置获取正确的URL
|
|
||||||
const backgroundConfig = backgroundConfigs.find(
|
|
||||||
(bg) => bg.id === this.currentData.backgroundId,
|
|
||||||
);
|
|
||||||
|
|
||||||
const backgroundUrl =
|
|
||||||
backgroundConfig?.url || `/bg/${this.currentData.backgroundId}.png`;
|
|
||||||
|
|
||||||
// 设置CSS变量
|
|
||||||
root.style.setProperty(
|
|
||||||
'--xagi-background-image',
|
|
||||||
`url(${backgroundUrl})`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 根据布局风格和导航风格应用完整的CSS变量
|
// 根据布局风格和导航风格应用完整的CSS变量
|
||||||
const layoutStyleKey =
|
const layoutStyleKey =
|
||||||
@@ -465,6 +555,32 @@ class UnifiedThemeService {
|
|||||||
? 'compact'
|
? 'compact'
|
||||||
: 'expanded',
|
: 'expanded',
|
||||||
);
|
);
|
||||||
|
root.setAttribute('data-menu-layout', this.currentData.menuLayout);
|
||||||
|
root.setAttribute('data-menu-style', this.currentData.menuStyle);
|
||||||
|
root.setAttribute(
|
||||||
|
'data-tab-style',
|
||||||
|
this.currentData.basicConfig.tabStyle,
|
||||||
|
);
|
||||||
|
root.setAttribute(
|
||||||
|
'data-page-transition',
|
||||||
|
this.currentData.basicConfig.pageTransition || 'none',
|
||||||
|
);
|
||||||
|
root.setAttribute(
|
||||||
|
'data-color-weak',
|
||||||
|
String(this.currentData.basicConfig.colorWeak),
|
||||||
|
);
|
||||||
|
root.setAttribute(
|
||||||
|
'data-watermark-visible',
|
||||||
|
String(this.currentData.basicConfig.watermarkVisible),
|
||||||
|
);
|
||||||
|
root.style.setProperty(
|
||||||
|
'--xagi-basic-menu-open-width',
|
||||||
|
`${this.currentData.basicConfig.menuOpenWidth}px`,
|
||||||
|
);
|
||||||
|
root.style.setProperty(
|
||||||
|
'--xagi-custom-radius',
|
||||||
|
`${this.currentData.basicConfig.customRadius}rem`,
|
||||||
|
);
|
||||||
this.updateBodyClasses();
|
this.updateBodyClasses();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Failed to apply theme data to DOM:', error);
|
console.error('Failed to apply theme data to DOM:', error);
|
||||||
@@ -481,11 +597,28 @@ class UnifiedThemeService {
|
|||||||
'xagi-layout-dark',
|
'xagi-layout-dark',
|
||||||
'xagi-nav-style1',
|
'xagi-nav-style1',
|
||||||
'xagi-nav-style2',
|
'xagi-nav-style2',
|
||||||
|
'xagi-menu-layout-vertical',
|
||||||
|
'xagi-menu-layout-horizontal',
|
||||||
|
'xagi-menu-layout-mixed',
|
||||||
|
'xagi-menu-layout-dual',
|
||||||
|
'xagi-menu-style-design',
|
||||||
|
'xagi-menu-style-light',
|
||||||
|
'xagi-menu-style-dark',
|
||||||
|
'xagi-color-weak',
|
||||||
);
|
);
|
||||||
|
|
||||||
// 添加当前样式对应的类名
|
// 添加当前样式对应的类名
|
||||||
document.body.classList.add(`xagi-layout-${this.currentData.layoutStyle}`);
|
document.body.classList.add(`xagi-layout-${this.currentData.layoutStyle}`);
|
||||||
document.body.classList.add(`xagi-nav-${this.currentData.navigationStyle}`);
|
document.body.classList.add(`xagi-nav-${this.currentData.navigationStyle}`);
|
||||||
|
document.body.classList.add(
|
||||||
|
`xagi-menu-layout-${this.currentData.menuLayout}`,
|
||||||
|
);
|
||||||
|
document.body.classList.add(
|
||||||
|
`xagi-menu-style-${this.currentData.menuStyle}`,
|
||||||
|
);
|
||||||
|
if (this.currentData.basicConfig.colorWeak) {
|
||||||
|
document.body.classList.add('xagi-color-weak');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -108,7 +108,7 @@
|
|||||||
@BLUE10: #0256ff;
|
@BLUE10: #0256ff;
|
||||||
@BLUE11: #edf3ff;
|
@BLUE11: #edf3ff;
|
||||||
@BLUE12: rgba(150, 159, 255, 0.45);
|
@BLUE12: rgba(150, 159, 255, 0.45);
|
||||||
@BLUE13: #5147ff;
|
@BLUE13: #5d87ff;
|
||||||
|
|
||||||
@GREEN: #008000;
|
@GREEN: #008000;
|
||||||
@GREEN01: rgba(103, 229, 146, 0.2);
|
@GREEN01: rgba(103, 229, 146, 0.2);
|
||||||
|
|||||||
@@ -210,7 +210,7 @@ each(range(2, 3, 1), {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.radius-10 {
|
.radius-10 {
|
||||||
border-radius: 10px;
|
border-radius: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.font-12 {
|
.font-12 {
|
||||||
|
|||||||
@@ -17,7 +17,7 @@
|
|||||||
|
|
||||||
// ---------- 表格滚动条:只保留滑块 ----------
|
// ---------- 表格滚动条:只保留滑块 ----------
|
||||||
// Firefox
|
// Firefox
|
||||||
scrollbar-color: @GRAY19 transparent;
|
scrollbar-color: rgba(91, 105, 135, 0.28) transparent;
|
||||||
scrollbar-width: thin;
|
scrollbar-width: thin;
|
||||||
|
|
||||||
// Chrome / Safari / Edge
|
// Chrome / Safari / Edge
|
||||||
@@ -36,12 +36,12 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
&::-webkit-scrollbar-thumb {
|
&::-webkit-scrollbar-thumb {
|
||||||
background: @GRAY19;
|
background: rgba(91, 105, 135, 0.28);
|
||||||
border-radius: 6px;
|
border-radius: 6px;
|
||||||
}
|
}
|
||||||
|
|
||||||
&::-webkit-scrollbar-thumb:hover {
|
&::-webkit-scrollbar-thumb:hover {
|
||||||
background: @GRAY;
|
background: rgba(91, 105, 135, 0.48);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------- 固定列背景(右侧“操作”列) ----------
|
// ---------- 固定列背景(右侧“操作”列) ----------
|
||||||
|
|||||||
@@ -11,7 +11,7 @@
|
|||||||
img {
|
img {
|
||||||
width: 50px;
|
width: 50px;
|
||||||
height: 50px;
|
height: 50px;
|
||||||
border-radius: 10px;
|
border-radius: 8px;
|
||||||
margin-right: 15px;
|
margin-right: 15px;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -29,7 +29,7 @@
|
|||||||
background-color: #f6f4dd;
|
background-color: #f6f4dd;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
padding: 10px 0;
|
padding: 10px 0;
|
||||||
border-radius: 10px;
|
border-radius: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.add-member-modal-content {
|
.add-member-modal-content {
|
||||||
|
|||||||
@@ -488,12 +488,12 @@
|
|||||||
|
|
||||||
// 导航尺寸配置(直接使用动态CSS变量)
|
// 导航尺寸配置(直接使用动态CSS变量)
|
||||||
@navFirstMenuWidth: var(--xagi-nav-first-menu-width, 60px);
|
@navFirstMenuWidth: var(--xagi-nav-first-menu-width, 60px);
|
||||||
@navFirstMenuWidthStyle2: 88px; // 展开模式宽度
|
@navFirstMenuWidthStyle2: 112px; // 展开模式宽度
|
||||||
@navFirstMenuFontSize: @fontSizeSm;
|
@navFirstMenuFontSize: @fontSizeSm;
|
||||||
|
|
||||||
// 导航背景色(使用Ant Design的动态颜色)
|
// 导航背景色(使用Ant Design的动态颜色)
|
||||||
@navFirstMenuBg: var(--xagi-color-bg-container, rgba(255, 255, 255, 0.95));
|
@navFirstMenuBg: var(--xagi-layout-bg-secondary, #f2f5fb);
|
||||||
@navSecondMenuBg: var(--xagi-color-bg-layout, rgba(255, 255, 255, 0.85));
|
@navSecondMenuBg: var(--xagi-layout-bg-secondary, #f2f5fb);
|
||||||
|
|
||||||
// 页面容器相关Token
|
// 页面容器相关Token
|
||||||
@pageContainerMargin: var(--xagi-page-container-margin, 12px);
|
@pageContainerMargin: var(--xagi-page-container-margin, 12px);
|
||||||
@@ -524,9 +524,9 @@
|
|||||||
);
|
);
|
||||||
|
|
||||||
// 导航交互状态颜色(使用主题色)
|
// 导航交互状态颜色(使用主题色)
|
||||||
@navItemHoverBg: #ffffff;
|
@navItemHoverBg: rgba(255, 255, 255, 0.66);
|
||||||
@navItemActiveBg: #ffffff;
|
@navItemActiveBg: #ffffff;
|
||||||
@navItemSelectedBg: #ffffff;
|
@navItemSelectedBg: var(--xagi-color-primary-bg, rgba(93, 135, 255, 0.1));
|
||||||
|
|
||||||
// 导航边框和分割线(使用独立的布局风格变量)
|
// 导航边框和分割线(使用独立的布局风格变量)
|
||||||
@navBorderColor: var(--xagi-layout-border-primary, rgba(0, 0, 0, 0.15));
|
@navBorderColor: var(--xagi-layout-border-primary, rgba(0, 0, 0, 0.15));
|
||||||
@@ -538,7 +538,7 @@
|
|||||||
);
|
);
|
||||||
|
|
||||||
// 导航阴影(使用独立的布局风格变量)
|
// 导航阴影(使用独立的布局风格变量)
|
||||||
@navShadow: var(--xagi-layout-shadow, 0 2px 8px rgba(0, 0, 0, 0.15));
|
@navShadow: var(--xagi-layout-shadow, 0 8px 20px rgba(32, 45, 84, 0.08));
|
||||||
@navFirstMenuShadow: none;
|
@navFirstMenuShadow: none;
|
||||||
@navSecondMenuShadow: none;
|
@navSecondMenuShadow: none;
|
||||||
|
|
||||||
|
|||||||
@@ -24,3 +24,53 @@ export enum ThemeNavigationStyleType {
|
|||||||
/** 展开模式 */
|
/** 展开模式 */
|
||||||
STYLE2 = 'style2',
|
STYLE2 = 'style2',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 菜单布局类型
|
||||||
|
* 对应 Art Design Pro 的 Left / Top / TopLeft / DualMenu 四类结构
|
||||||
|
*/
|
||||||
|
export enum ThemeMenuLayoutType {
|
||||||
|
/** 左侧垂直菜单 */
|
||||||
|
VERTICAL = 'vertical',
|
||||||
|
/** 顶部水平菜单 */
|
||||||
|
HORIZONTAL = 'horizontal',
|
||||||
|
/** 顶部一级菜单 + 左侧二级菜单 */
|
||||||
|
MIXED = 'mixed',
|
||||||
|
/** 左侧图标栏 + 右侧二级菜单 */
|
||||||
|
DUAL = 'dual',
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 菜单视觉风格类型
|
||||||
|
* 只用于侧边菜单外观,不改变主题模式和菜单布局
|
||||||
|
*/
|
||||||
|
export enum ThemeMenuStyleType {
|
||||||
|
/** Art Design Pro 风格 */
|
||||||
|
DESIGN = 'design',
|
||||||
|
/** 浅色菜单 */
|
||||||
|
LIGHT = 'light',
|
||||||
|
/** 深色菜单 */
|
||||||
|
DARK = 'dark',
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 标签页风格类型
|
||||||
|
* 对齐 Art Design Pro 的 tab-default / tab-card / tab-google
|
||||||
|
*/
|
||||||
|
export enum ThemeTabStyleType {
|
||||||
|
DEFAULT = 'tab-default',
|
||||||
|
CARD = 'tab-card',
|
||||||
|
GOOGLE = 'tab-google',
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 页面切换动画类型
|
||||||
|
* 对齐 Art Design Pro 的 transition 配置
|
||||||
|
*/
|
||||||
|
export enum ThemePageTransitionType {
|
||||||
|
NONE = '',
|
||||||
|
FADE = 'fade',
|
||||||
|
SLIDE_LEFT = 'slide-left',
|
||||||
|
SLIDE_BOTTOM = 'slide-bottom',
|
||||||
|
SLIDE_TOP = 'slide-top',
|
||||||
|
}
|
||||||
|
|||||||
@@ -7,6 +7,12 @@ import {
|
|||||||
UserRoleEnum,
|
UserRoleEnum,
|
||||||
UserStatusEnum,
|
UserStatusEnum,
|
||||||
} from '@/types/enums/systemManage';
|
} from '@/types/enums/systemManage';
|
||||||
|
import {
|
||||||
|
ThemeMenuLayoutType,
|
||||||
|
ThemeMenuStyleType,
|
||||||
|
ThemePageTransitionType,
|
||||||
|
ThemeTabStyleType,
|
||||||
|
} from '@/types/enums/theme';
|
||||||
import { PublishStatusEnum } from '../enums/common';
|
import { PublishStatusEnum } from '../enums/common';
|
||||||
import {
|
import {
|
||||||
KnowledgeDataTypeEnum,
|
KnowledgeDataTypeEnum,
|
||||||
@@ -437,6 +443,27 @@ export interface ThemeConfigData {
|
|||||||
layoutStyle: 'light' | 'dark';
|
layoutStyle: 'light' | 'dark';
|
||||||
/** 导航风格ID(style1/style2) */
|
/** 导航风格ID(style1/style2) */
|
||||||
navigationStyle: 'style1' | 'style2';
|
navigationStyle: 'style1' | 'style2';
|
||||||
|
/** 菜单布局(垂直/水平/混合/双列) */
|
||||||
|
menuLayout?: ThemeMenuLayoutType;
|
||||||
|
/** 菜单视觉风格 */
|
||||||
|
menuStyle?: ThemeMenuStyleType;
|
||||||
|
/** 基础配置 */
|
||||||
|
basicConfig?: {
|
||||||
|
showWorkTab: boolean;
|
||||||
|
uniqueOpened: boolean;
|
||||||
|
showMenuButton: boolean;
|
||||||
|
showFastEnter: boolean;
|
||||||
|
showRefreshButton: boolean;
|
||||||
|
showCrumbs: boolean;
|
||||||
|
showLanguage: boolean;
|
||||||
|
showNprogress: boolean;
|
||||||
|
colorWeak: boolean;
|
||||||
|
watermarkVisible: boolean;
|
||||||
|
menuOpenWidth: number;
|
||||||
|
tabStyle: ThemeTabStyleType;
|
||||||
|
pageTransition: ThemePageTransitionType;
|
||||||
|
customRadius: string;
|
||||||
|
};
|
||||||
/** 时间戳 */
|
/** 时间戳 */
|
||||||
timestamp: number;
|
timestamp: number;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -101,29 +101,29 @@ export class StyleInitializer {
|
|||||||
|
|
||||||
// 设置默认的导航相关 CSS 变量
|
// 设置默认的导航相关 CSS 变量
|
||||||
const defaultNavVars = {
|
const defaultNavVars = {
|
||||||
'--xagi-nav-first-menu-width': '60px',
|
'--xagi-nav-first-menu-width': '88px',
|
||||||
'--xagi-page-container-margin': '16px',
|
'--xagi-page-container-margin': '0',
|
||||||
'--xagi-page-container-border-radius': '12px',
|
'--xagi-page-container-border-radius': '0',
|
||||||
'--xagi-page-container-border-color': 'transparent',
|
'--xagi-page-container-border-color': 'transparent',
|
||||||
};
|
};
|
||||||
|
|
||||||
// 设置默认的布局相关 CSS 变量
|
// 设置默认的布局相关 CSS 变量
|
||||||
const defaultLayoutVars = {
|
const defaultLayoutVars = {
|
||||||
'--xagi-layout-text-primary': '#000000',
|
'--xagi-layout-text-primary': '#000000',
|
||||||
'--xagi-layout-text-secondary': 'rgba(0, 0, 0, 0.65)',
|
'--xagi-layout-text-secondary': 'rgba(31, 35, 55, 0.72)',
|
||||||
'--xagi-layout-text-tertiary': 'rgba(0, 0, 0, 0.65)',
|
'--xagi-layout-text-tertiary': 'rgba(31, 35, 55, 0.52)',
|
||||||
'--xagi-layout-text-disabled': 'rgba(0, 0, 0, 0.25)',
|
'--xagi-layout-text-disabled': 'rgba(0, 0, 0, 0.25)',
|
||||||
'--xagi-layout-second-menu-text-color': 'rgba(0, 0, 0, 0.88)',
|
'--xagi-layout-second-menu-text-color': '#1f2337',
|
||||||
'--xagi-layout-second-menu-text-color-secondary': 'rgba(0, 0, 0, 0.65)',
|
'--xagi-layout-second-menu-text-color-secondary': '#5d6378',
|
||||||
'--xagi-layout-bg-primary': 'rgba(255, 255, 255, 0.95)',
|
'--xagi-layout-bg-primary': '#f4f7fb',
|
||||||
'--xagi-layout-bg-secondary': 'rgba(255, 255, 255, 0.85)',
|
'--xagi-layout-bg-secondary': '#f2f5fb',
|
||||||
'--xagi-layout-bg-card': 'rgba(255, 255, 255, 0.65)',
|
'--xagi-layout-bg-card': 'rgba(255, 255, 255, 0.92)',
|
||||||
'--xagi-layout-bg-input': 'rgba(255, 255, 255, 0.45)',
|
'--xagi-layout-bg-input': 'rgba(255, 255, 255, 0.88)',
|
||||||
'--xagi-layout-border-primary': 'rgba(0, 0, 0, 0.15)',
|
'--xagi-layout-border-primary': 'rgba(91, 105, 135, 0.14)',
|
||||||
'--xagi-layout-border-secondary': 'rgba(0, 0, 0, 0.1)',
|
'--xagi-layout-border-secondary': 'rgba(91, 105, 135, 0.1)',
|
||||||
'--xagi-layout-shadow': 'rgba(0, 0, 0, 0.1)',
|
'--xagi-layout-shadow': '0 8px 20px rgba(32, 45, 84, 0.08)',
|
||||||
'--xagi-layout-overlay': 'rgba(255, 255, 255, 0.7)',
|
'--xagi-layout-overlay': 'rgba(255, 255, 255, 0.78)',
|
||||||
'--xagi-layout-bg-container': 'rgba(255, 255, 255, 0.95)',
|
'--xagi-layout-bg-container': '#ffffff',
|
||||||
};
|
};
|
||||||
|
|
||||||
// 设置默认主题色相关 CSS 变量
|
// 设置默认主题色相关 CSS 变量
|
||||||
|
|||||||