@nocobase/client-v2 2.3.0-alpha.1 → 3.0.0-alpha.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/es/acl/ACLProvider.d.ts +10 -0
- package/es/authRedirect.d.ts +5 -0
- package/es/components/AttachmentUpload.d.ts +51 -0
- package/es/components/form/ScanInput/useCodeScanner.d.ts +2 -2
- package/es/components/index.d.ts +1 -0
- package/es/flow/actions/linkageRules.d.ts +1 -0
- package/es/flow/admin-shell/admin-layout/NocoBaseLogo.d.ts +10 -0
- package/es/flow/components/FlowRoute.d.ts +2 -2
- package/es/flow/internal/registerDeviceTypeContext.d.ts +10 -0
- package/es/flow/models/base/ActionModelCore.d.ts +4 -2
- package/es/flow/models/base/BlockGridModel.d.ts +3 -0
- package/es/flow/models/blocks/table/TableColumnModel.d.ts +1 -0
- package/es/flow/models/fields/DateTimeFieldModel/dateLimit.d.ts +15 -7
- package/es/index.d.ts +4 -1
- package/es/index.mjs +139 -125
- package/es/nocobase-buildin-plugin/currentUserAuthStatus.d.ts +11 -0
- package/es/nocobase-buildin-plugin/index.d.ts +9 -0
- package/es/settings-app/SettingsApplication.d.ts +18 -0
- package/es/settings-app/SettingsBrand.d.ts +16 -0
- package/es/settings-app/SettingsBuildInPlugin.d.ts +9 -0
- package/es/settings-app/SettingsDocumentRedirect.d.ts +10 -0
- package/es/settings-app/SettingsGroupNav.d.ts +17 -0
- package/es/settings-app/SettingsPluginSettingsManager.d.ts +14 -0
- package/es/settings-app/SettingsRouterManager.d.ts +15 -0
- package/es/settings-app/SettingsSearch.d.ts +17 -0
- package/es/settings-app/SettingsShell.d.ts +10 -0
- package/es/settings-app/isSettingsApp.d.ts +18 -0
- package/es/settings-app/runtimePaths.d.ts +18 -0
- package/es/settings-app/settingsDocumentPath.d.ts +14 -0
- package/es/settings-app/settingsTheme.d.ts +87 -0
- package/es/settings-app/useSettingsThemeConfig.d.ts +22 -0
- package/es/settings-center/AdminSettingsLayout.d.ts +2 -2
- package/es/settings-center/groups.d.ts +66 -0
- package/es/settings-center/useSettingsGroups.d.ts +36 -0
- package/es/settings-center/useSettingsSearch.d.ts +35 -0
- package/es/settings-center/utils.d.ts +24 -0
- package/es/theme/index.d.ts +1 -0
- package/es/theme/itemActive.d.ts +24 -0
- package/es/theme/type.d.ts +9 -0
- package/lib/index.js +141 -127
- package/lib/locale/languageCodes.js +1 -0
- package/package.json +8 -7
- package/src/__tests__/authRedirect.test.ts +137 -0
- package/src/__tests__/browserChecker.test.ts +124 -6
- package/src/__tests__/mockSettingsApplication.ts +29 -0
- package/src/__tests__/nocobase-buildin-plugin-auth.test.tsx +51 -10
- package/src/__tests__/plugin-manager.test.tsx +7 -6
- package/src/__tests__/settings-application.test.ts +164 -0
- package/src/__tests__/settings-auth-routes.test.ts +71 -0
- package/src/__tests__/settings-center.test.tsx +38 -37
- package/src/__tests__/settings-layout-root.test.tsx +260 -0
- package/src/__tests__/settings-runtime-paths.test.ts +99 -0
- package/src/__tests__/settings-shell.test.tsx +200 -0
- package/src/acl/ACLProvider.tsx +21 -0
- package/src/authRedirect.ts +72 -3
- package/src/components/AttachmentUpload.tsx +275 -0
- package/src/components/form/ScanInput/CodeScanner.tsx +61 -34
- package/src/components/form/ScanInput/__tests__/CodeScanner.test.tsx +101 -0
- package/src/components/form/ScanInput/__tests__/useCodeScanner.test.tsx +114 -18
- package/src/components/form/ScanInput/useCodeScanner.ts +166 -23
- package/src/components/index.ts +1 -0
- package/src/flow/__tests__/FlowRoute.test.tsx +126 -8
- package/src/flow/__tests__/PluginFlowEngine.test.ts +58 -0
- package/src/flow/actions/__tests__/actionLinkageRules.forkProps.test.ts +314 -0
- package/src/flow/actions/__tests__/dataScopeFormValueClear.test.ts +102 -0
- package/src/flow/actions/linkageRules.tsx +26 -6
- package/src/flow/admin-shell/admin-layout/AdminLayoutComponent.tsx +1 -56
- package/src/flow/admin-shell/admin-layout/NocoBaseLogo.tsx +77 -0
- package/src/flow/admin-shell/admin-layout/TopbarActionsBar.tsx +37 -14
- package/src/flow/admin-shell/admin-layout/__tests__/TopbarActionsBar.test.tsx +77 -13
- package/src/flow/common/Markdown/style.ts +4 -0
- package/src/flow/components/BlockItemCard.tsx +2 -2
- package/src/flow/components/FlowRoute.tsx +71 -29
- package/src/flow/index.ts +2 -0
- package/src/flow/internal/registerDeviceTypeContext.ts +39 -0
- package/src/flow/models/base/ActionModelCore.tsx +11 -2
- package/src/flow/models/base/BlockGridModel.tsx +26 -0
- package/src/flow/models/base/CollectionBlockModel.tsx +6 -2
- package/src/flow/models/base/PageModel/PageTabModel.tsx +67 -17
- package/src/flow/models/base/PageModel/__tests__/PageTabModel.test.ts +509 -12
- package/src/flow/models/base/__tests__/ActionModelCore.render.test.tsx +49 -0
- package/src/flow/models/base/__tests__/BlockGridModel.selectSceneActivation.test.ts +124 -0
- package/src/flow/models/base/__tests__/CollectionBlockModel.addAppends.test.ts +41 -0
- package/src/flow/models/blocks/filter-form/FilterFormBlockModel.tsx +27 -3
- package/src/flow/models/blocks/filter-form/__tests__/defaultValues.wiring.test.ts +32 -4
- package/src/flow/models/blocks/js-block/JSBlock.tsx +1 -1
- package/src/flow/models/blocks/table/TableBlockModel.tsx +3 -1
- package/src/flow/models/blocks/table/TableColumnModel.tsx +30 -1
- package/src/flow/models/blocks/table/__tests__/TableBlockModel.quickEditRefresh.test.ts +12 -0
- package/src/flow/models/blocks/table/__tests__/TableColumnModel.test.tsx +33 -0
- package/src/flow/models/fields/DateTimeFieldModel/__tests__/DateTimeNoTzFieldModel.dateLimit.test.tsx +149 -0
- package/src/flow/models/fields/DateTimeFieldModel/dateLimit.ts +149 -113
- package/src/flow/models/fields/JsonFieldModel.tsx +31 -8
- package/src/flow/models/fields/__tests__/JsonFieldModel.test.ts +82 -0
- package/src/flow/models/topbar/TopbarActionModel.tsx +52 -180
- package/src/flow/utils/dataScopeFormValueClear.ts +4 -3
- package/src/index.ts +10 -1
- package/src/locale/languageCodes.ts +1 -0
- package/src/nocobase-buildin-plugin/currentUserAuthStatus.ts +58 -0
- package/src/nocobase-buildin-plugin/index.tsx +99 -71
- package/src/settings-app/SettingsApplication.ts +31 -0
- package/src/settings-app/SettingsBrand.tsx +108 -0
- package/src/settings-app/SettingsBuildInPlugin.ts +10 -0
- package/src/settings-app/SettingsDocumentRedirect.tsx +26 -0
- package/src/settings-app/SettingsGroupNav.tsx +124 -0
- package/src/settings-app/SettingsPluginSettingsManager.ts +38 -0
- package/src/settings-app/SettingsRouterManager.ts +92 -0
- package/src/settings-app/SettingsSearch.tsx +238 -0
- package/src/settings-app/SettingsShell.tsx +167 -0
- package/src/settings-app/isSettingsApp.ts +21 -0
- package/src/settings-app/runtimePaths.ts +104 -0
- package/src/settings-app/settingsDocumentPath.ts +66 -0
- package/src/settings-app/settingsTheme.ts +301 -0
- package/src/settings-app/useSettingsThemeConfig.ts +100 -0
- package/src/settings-center/AdminSettingsLayout.tsx +122 -141
- package/src/settings-center/SystemSettingsPage.tsx +4 -170
- package/src/settings-center/groups.ts +108 -0
- package/src/settings-center/useSettingsGroups.ts +105 -0
- package/src/settings-center/useSettingsSearch.ts +173 -0
- package/src/settings-center/utils.tsx +50 -1
- package/src/theme/index.tsx +1 -0
- package/src/theme/itemActive.ts +31 -0
- package/src/theme/type.ts +10 -0
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This file is part of the NocoBase (R) project.
|
|
3
|
+
* Copyright (c) 2020-2024 NocoBase Co., Ltd.
|
|
4
|
+
* Authors: NocoBase Team.
|
|
5
|
+
*
|
|
6
|
+
* This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
|
|
7
|
+
* For more information, please refer to: https://www.nocobase.com/agreement.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { FlowModelRenderer } from '@nocobase/flow-engine';
|
|
11
|
+
import { ConfigProvider, Layout, theme as antdTheme, type ThemeConfig } from 'antd';
|
|
12
|
+
import React, { useMemo, type FC } from 'react';
|
|
13
|
+
import { useLocation } from 'react-router-dom';
|
|
14
|
+
import { HelpLite } from '../flow/admin-shell/admin-layout/HelpLite';
|
|
15
|
+
import {
|
|
16
|
+
USER_CENTER_ACTION_ID,
|
|
17
|
+
type UserCenterTopbarActionModel,
|
|
18
|
+
} from '../flow/models/topbar/UserCenterTopbarActionModel';
|
|
19
|
+
import { useApp } from '../hooks/useApp';
|
|
20
|
+
import { useCurrentUserAuthStatus } from '../nocobase-buildin-plugin/currentUserAuthStatus';
|
|
21
|
+
import { SettingsBrand } from './SettingsBrand';
|
|
22
|
+
import { SettingsGroupNav } from './SettingsGroupNav';
|
|
23
|
+
import { SettingsSearch } from './SettingsSearch';
|
|
24
|
+
import {
|
|
25
|
+
buildSettingsGlobalCss,
|
|
26
|
+
buildSettingsNeutralTheme,
|
|
27
|
+
getSettingsHeaderColors,
|
|
28
|
+
withSettingsHeaderTheme,
|
|
29
|
+
} from './settingsTheme';
|
|
30
|
+
import { useSettingsThemeConfig } from './useSettingsThemeConfig';
|
|
31
|
+
|
|
32
|
+
const rootStyle: React.CSSProperties = {
|
|
33
|
+
height: '100vh',
|
|
34
|
+
minWidth: 0,
|
|
35
|
+
overflow: 'hidden',
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
const headerContentStyle: React.CSSProperties = {
|
|
39
|
+
alignItems: 'center',
|
|
40
|
+
display: 'flex',
|
|
41
|
+
gap: 24,
|
|
42
|
+
height: '100%',
|
|
43
|
+
justifyContent: 'space-between',
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
const actionsStyle: React.CSSProperties = {
|
|
47
|
+
alignItems: 'center',
|
|
48
|
+
display: 'flex',
|
|
49
|
+
flexShrink: 0,
|
|
50
|
+
gap: 8,
|
|
51
|
+
height: '100%',
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
const workspaceStyle: React.CSSProperties = {
|
|
55
|
+
display: 'flex',
|
|
56
|
+
flex: 1,
|
|
57
|
+
minWidth: 0,
|
|
58
|
+
minHeight: 0,
|
|
59
|
+
overflow: 'hidden',
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
const contentStyle: React.CSSProperties = {
|
|
63
|
+
flex: 1,
|
|
64
|
+
minWidth: 0,
|
|
65
|
+
minHeight: 0,
|
|
66
|
+
overflow: 'hidden',
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
const embedContainerStyle: React.CSSProperties = {
|
|
70
|
+
flexShrink: 0,
|
|
71
|
+
height: '100%',
|
|
72
|
+
position: 'relative',
|
|
73
|
+
width: 'fit-content',
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
export const SettingsShell: FC = ({ children }) => {
|
|
77
|
+
const app = useApp();
|
|
78
|
+
const location = useLocation();
|
|
79
|
+
const { token } = antdTheme.useToken();
|
|
80
|
+
// 设置中心的外观由主题编辑器里那条「简约」主题记录约束;读不到才用代码里的中性配色兜底。
|
|
81
|
+
const storedThemeConfig = useSettingsThemeConfig();
|
|
82
|
+
// 顶栏和补丁样式要按**设置中心自己这套主题**算色,不能直接用上面那个 `token`——
|
|
83
|
+
// 这个组件在自己的 ConfigProvider 外面,`token` 是业务端主题的。用它的话,业务端一切暗黑,
|
|
84
|
+
// 顶栏就跟着变黑,而内容区还是简约的白,一半黑一半白。
|
|
85
|
+
const settingsToken = useMemo(
|
|
86
|
+
() => (storedThemeConfig ? antdTheme.getDesignToken(storedThemeConfig) : token),
|
|
87
|
+
[storedThemeConfig, token],
|
|
88
|
+
);
|
|
89
|
+
const settingsShellTheme = useMemo<ThemeConfig>(
|
|
90
|
+
() =>
|
|
91
|
+
storedThemeConfig ? withSettingsHeaderTheme(storedThemeConfig, settingsToken) : buildSettingsNeutralTheme(token),
|
|
92
|
+
[settingsToken, storedThemeConfig, token],
|
|
93
|
+
);
|
|
94
|
+
const headerColors = useMemo(() => getSettingsHeaderColors(settingsToken), [settingsToken]);
|
|
95
|
+
const settingsGlobalCss = useMemo(() => buildSettingsGlobalCss(settingsToken), [settingsToken]);
|
|
96
|
+
const authStatus = useCurrentUserAuthStatus(app);
|
|
97
|
+
const isAuthenticationRoute = (app.router.matchRoutes(location.pathname) || []).some((match) => {
|
|
98
|
+
const routeId = match.route.id;
|
|
99
|
+
return routeId === 'auth' || routeId?.startsWith('auth.') || routeId === '2fa' || routeId?.startsWith('2fa.');
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
if (isAuthenticationRoute) {
|
|
103
|
+
// 登录 / 二次验证页也要套设置中心自己的主题,否则它跟的是业务端主题:
|
|
104
|
+
// 业务端切暗黑之后登录页是暗的,登进去又变成浅色,中间闪一下。
|
|
105
|
+
// 外面这层用 `display: contents`,只为了让作用在 `.nb-settings-shell` 上的补丁样式
|
|
106
|
+
// 也能命中登录页,不改变任何布局。
|
|
107
|
+
return (
|
|
108
|
+
<ConfigProvider theme={settingsShellTheme}>
|
|
109
|
+
<style>{settingsGlobalCss}</style>
|
|
110
|
+
<div className="nb-settings-shell" style={{ display: 'contents' }}>
|
|
111
|
+
{children}
|
|
112
|
+
</div>
|
|
113
|
+
</ConfigProvider>
|
|
114
|
+
);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const shouldShowHeader = authStatus === 'authenticated';
|
|
118
|
+
const hasUserCenterModel = Boolean(app.flowEngine.getModelClass('UserCenterTopbarActionModel'));
|
|
119
|
+
const userCenter = hasUserCenterModel
|
|
120
|
+
? app.flowEngine.getModel<UserCenterTopbarActionModel>(`topbar-action-${USER_CENTER_ACTION_ID}`) ||
|
|
121
|
+
app.flowEngine.createModel<UserCenterTopbarActionModel>({
|
|
122
|
+
use: 'UserCenterTopbarActionModel',
|
|
123
|
+
uid: `topbar-action-${USER_CENTER_ACTION_ID}`,
|
|
124
|
+
})
|
|
125
|
+
: null;
|
|
126
|
+
|
|
127
|
+
return (
|
|
128
|
+
<ConfigProvider theme={settingsShellTheme}>
|
|
129
|
+
<style>{settingsGlobalCss}</style>
|
|
130
|
+
<Layout className="nb-settings-shell" style={rootStyle}>
|
|
131
|
+
<Layout.Header
|
|
132
|
+
style={
|
|
133
|
+
{
|
|
134
|
+
// 深色顶栏自带层次,只有浅色顶栏才需要一条分割线把它和内容区分开。
|
|
135
|
+
borderBottom: headerColors.dark
|
|
136
|
+
? 'none'
|
|
137
|
+
: `${settingsToken.lineWidth}px solid ${settingsToken.colorBorderSecondary}`,
|
|
138
|
+
display: shouldShowHeader ? undefined : 'none',
|
|
139
|
+
height: 46,
|
|
140
|
+
lineHeight: '46px',
|
|
141
|
+
paddingInline: settingsToken.paddingLG,
|
|
142
|
+
// 顶栏子组件(动作条、pinned 插件列表)有一部分直接读 CSS 变量而不是 antd token,
|
|
143
|
+
// 而且兜底值是按深色顶栏写死的浅色,这里一并覆盖,免得白底上出现白图标。
|
|
144
|
+
'--nb-topbar-action-hover-bg': headerColors.bgHover,
|
|
145
|
+
'--colorTextHeaderMenu': headerColors.text,
|
|
146
|
+
'--nb-topbar-action-color': headerColors.text,
|
|
147
|
+
} as React.CSSProperties
|
|
148
|
+
}
|
|
149
|
+
>
|
|
150
|
+
<div style={headerContentStyle}>
|
|
151
|
+
<SettingsBrand />
|
|
152
|
+
<SettingsGroupNav />
|
|
153
|
+
<div style={actionsStyle}>
|
|
154
|
+
<SettingsSearch />
|
|
155
|
+
<HelpLite />
|
|
156
|
+
{userCenter ? <FlowModelRenderer model={userCenter} /> : null}
|
|
157
|
+
</div>
|
|
158
|
+
</div>
|
|
159
|
+
</Layout.Header>
|
|
160
|
+
<div style={workspaceStyle}>
|
|
161
|
+
<Layout.Content style={contentStyle}>{children}</Layout.Content>
|
|
162
|
+
<div id="nocobase-embed-container" style={embedContainerStyle} />
|
|
163
|
+
</div>
|
|
164
|
+
</Layout>
|
|
165
|
+
</ConfigProvider>
|
|
166
|
+
);
|
|
167
|
+
};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This file is part of the NocoBase (R) project.
|
|
3
|
+
* Copyright (c) 2020-2024 NocoBase Co., Ltd.
|
|
4
|
+
* Authors: NocoBase Team.
|
|
5
|
+
*
|
|
6
|
+
* This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
|
|
7
|
+
* For more information, please refer to: https://www.nocobase.com/agreement.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* 判断当前跑的是不是设置中心那个壳。
|
|
12
|
+
*
|
|
13
|
+
* 插件用它来决定某个入口要不要出现在设置中心里,不用 import `SettingsApplication`
|
|
14
|
+
* 本身——那会让插件反向依赖壳的实现。
|
|
15
|
+
*
|
|
16
|
+
* @param {any} app 应用实例
|
|
17
|
+
* @returns {boolean} 是设置中心返回 true
|
|
18
|
+
*/
|
|
19
|
+
export function isSettingsApp(app?: any): boolean {
|
|
20
|
+
return Boolean(app?.isSettingsApp);
|
|
21
|
+
}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This file is part of the NocoBase (R) project.
|
|
3
|
+
* Copyright (c) 2020-2024 NocoBase Co., Ltd.
|
|
4
|
+
* Authors: NocoBase Team.
|
|
5
|
+
*
|
|
6
|
+
* This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
|
|
7
|
+
* For more information, please refer to: https://www.nocobase.com/agreement.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { stripModernClientPrefix } from '../authRedirect';
|
|
11
|
+
import type { BaseApplication } from '../BaseApplication';
|
|
12
|
+
import {
|
|
13
|
+
resolveSettingsAppScope,
|
|
14
|
+
resolveSettingsAppScopeWithinPublicPath,
|
|
15
|
+
resolveSettingsDocumentPath,
|
|
16
|
+
} from './settingsDocumentPath';
|
|
17
|
+
|
|
18
|
+
type SettingsRuntimeApp = Pick<BaseApplication<any>, 'name'> & {
|
|
19
|
+
getPublicPath?: () => string;
|
|
20
|
+
router?: {
|
|
21
|
+
basename?: string;
|
|
22
|
+
getBasename?: () => string | undefined;
|
|
23
|
+
};
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
const LEGACY_EMAIL_OAUTH_PATH = '/admin/settings/mail/oauth2';
|
|
27
|
+
|
|
28
|
+
const ROUTE_MAPPINGS = [
|
|
29
|
+
{ from: '/admin/ai/knowledge-base/detail', to: '/settings/ai/knowledge-base/detail' },
|
|
30
|
+
{ from: '/admin/workflow/executions', to: '/settings/workflow/executions' },
|
|
31
|
+
{ from: '/admin/workflow/workflows', to: '/settings/workflow/workflows' },
|
|
32
|
+
{ from: '/admin/settings', to: '/settings' },
|
|
33
|
+
{ from: '/settings', to: '/settings' },
|
|
34
|
+
] as const;
|
|
35
|
+
|
|
36
|
+
function normalizePathname(pathname?: string) {
|
|
37
|
+
const normalized = `/${String(pathname || '/').trim()}`.replace(/\/{2,}/g, '/');
|
|
38
|
+
return normalized === '/' ? normalized : normalized.replace(/\/+$/, '');
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function splitPathSuffix(pathLike: string) {
|
|
42
|
+
const match = String(pathLike || '').match(/^([^?#]*)(.*)$/);
|
|
43
|
+
return {
|
|
44
|
+
pathname: normalizePathname(match?.[1]),
|
|
45
|
+
suffix: match?.[2] || '',
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function getRuntimeAppScope(app: SettingsRuntimeApp, pathname: string) {
|
|
50
|
+
const basename = app.router?.getBasename?.() || app.router?.basename;
|
|
51
|
+
const publicPath = app.getPublicPath?.() || '/';
|
|
52
|
+
return (
|
|
53
|
+
resolveSettingsAppScopeWithinPublicPath(publicPath, pathname) ||
|
|
54
|
+
resolveSettingsAppScopeWithinPublicPath(publicPath, basename) ||
|
|
55
|
+
(app.name && app.name !== 'main' ? resolveSettingsAppScope(`/apps/${app.name}`) : '')
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function getRootPublicPath(app: SettingsRuntimeApp, appScope: string) {
|
|
60
|
+
const basename = app.router?.getBasename?.() || app.router?.basename || '';
|
|
61
|
+
const publicPath = app.getPublicPath?.();
|
|
62
|
+
if (publicPath) {
|
|
63
|
+
return normalizePathname(stripModernClientPrefix(publicPath));
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const normalizedBasename = normalizePathname(basename);
|
|
67
|
+
const basenameWithoutAppScope =
|
|
68
|
+
appScope && normalizedBasename.endsWith(appScope)
|
|
69
|
+
? normalizedBasename.slice(0, -appScope.length)
|
|
70
|
+
: normalizedBasename;
|
|
71
|
+
return normalizePathname(stripModernClientPrefix(basenameWithoutAppScope));
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function findMappedRoute(pathname: string) {
|
|
75
|
+
for (const mapping of ROUTE_MAPPINGS) {
|
|
76
|
+
const index = pathname.indexOf(mapping.from);
|
|
77
|
+
if (index < 0) {
|
|
78
|
+
continue;
|
|
79
|
+
}
|
|
80
|
+
const tail = pathname.slice(index + mapping.from.length);
|
|
81
|
+
if (tail && !tail.startsWith('/')) {
|
|
82
|
+
continue;
|
|
83
|
+
}
|
|
84
|
+
return `${mapping.to}${tail}`;
|
|
85
|
+
}
|
|
86
|
+
return '/settings';
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export function resolveStandaloneSettingsPath(app: SettingsRuntimeApp, pathLike: string, contextPathname?: string) {
|
|
90
|
+
const { pathname, suffix } = splitPathSuffix(pathLike);
|
|
91
|
+
const appScope = getRuntimeAppScope(app, contextPathname || pathname);
|
|
92
|
+
const rootPublicPath = getRootPublicPath(app, appScope).replace(/\/+$/, '');
|
|
93
|
+
const documentBasePath = `${rootPublicPath}${appScope}`;
|
|
94
|
+
const oauthIndex = pathname.indexOf(LEGACY_EMAIL_OAUTH_PATH);
|
|
95
|
+
|
|
96
|
+
if (oauthIndex >= 0) {
|
|
97
|
+
const oauthTail = pathname.slice(oauthIndex + LEGACY_EMAIL_OAUTH_PATH.length);
|
|
98
|
+
if (!oauthTail || oauthTail.startsWith('/')) {
|
|
99
|
+
return `${documentBasePath}${LEGACY_EMAIL_OAUTH_PATH}${oauthTail}${suffix}`;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
return `${resolveSettingsDocumentPath(rootPublicPath, appScope, findMappedRoute(pathname))}${suffix}`;
|
|
104
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This file is part of the NocoBase (R) project.
|
|
3
|
+
* Copyright (c) 2020-2024 NocoBase Co., Ltd.
|
|
4
|
+
* Authors: NocoBase Team.
|
|
5
|
+
*
|
|
6
|
+
* This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
|
|
7
|
+
* For more information, please refer to: https://www.nocobase.com/agreement.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
export type SettingsAppScope = '' | `/${'apps' | '_app'}/${string}`;
|
|
11
|
+
|
|
12
|
+
function ensureLeadingSlash(value: string) {
|
|
13
|
+
return value.startsWith('/') ? value : `/${value}`;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function normalizeSettingsRootPublicPath(value?: string) {
|
|
17
|
+
const normalized = ensureLeadingSlash(String(value || '/').trim() || '/').replace(/\/{2,}/g, '/');
|
|
18
|
+
return normalized.endsWith('/') ? normalized : `${normalized}/`;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function resolveSettingsAppScope(pathname?: string): SettingsAppScope {
|
|
22
|
+
const match = /\/(apps|_app)\/([^/?#]+)(?=\/|[?#]|$)/.exec(ensureLeadingSlash(String(pathname || '/')));
|
|
23
|
+
return match ? (`/${match[1]}/${match[2]}` as SettingsAppScope) : '';
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function resolveSettingsAppScopeWithinPublicPath(publicPath: string, pathname?: string): SettingsAppScope {
|
|
27
|
+
const root = normalizeSettingsRootPublicPath(publicPath).replace(/\/+$/, '');
|
|
28
|
+
const path = ensureLeadingSlash(String(pathname || '/').split(/[?#]/)[0]).replace(/\/{2,}/g, '/');
|
|
29
|
+
const relativePath = root && (path === root || path.startsWith(`${root}/`)) ? path.slice(root.length) || '/' : path;
|
|
30
|
+
const match = /^\/(?:settings\/)?(apps|_app)\/([^/]+)(?=\/|$)/.exec(relativePath);
|
|
31
|
+
return match ? (`/${match[1]}/${match[2]}` as SettingsAppScope) : '';
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function normalizeSettingsRoutePath(value: string) {
|
|
35
|
+
const normalized = ensureLeadingSlash(String(value || '/settings')).replace(/\/{2,}/g, '/');
|
|
36
|
+
return normalized === '/' ? '/settings' : normalized.replace(/\/+$/, '');
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function removeSettingsRouteRoot(settingsRoute: string) {
|
|
40
|
+
if (settingsRoute === '/settings') {
|
|
41
|
+
return '';
|
|
42
|
+
}
|
|
43
|
+
return settingsRoute.replace(/^\/settings(?=\/|$)/, '');
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function resolveSettingsDocumentPath(
|
|
47
|
+
rootPublicPath: string,
|
|
48
|
+
appScope: SettingsAppScope,
|
|
49
|
+
settingsRoutePath: string,
|
|
50
|
+
) {
|
|
51
|
+
const root = normalizeSettingsRootPublicPath(rootPublicPath);
|
|
52
|
+
const rootPrefix = root === '/' ? '' : root.replace(/\/+$/, '');
|
|
53
|
+
const settingsRoute = normalizeSettingsRoutePath(settingsRoutePath);
|
|
54
|
+
if (!appScope) {
|
|
55
|
+
return `${rootPrefix}${settingsRoute}`;
|
|
56
|
+
}
|
|
57
|
+
return `${rootPrefix}/settings${appScope}${removeSettingsRouteRoot(settingsRoute)}`;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export function resolveSettingsDocumentBasename(rootPublicPath: string, appScope: SettingsAppScope) {
|
|
61
|
+
const root = normalizeSettingsRootPublicPath(rootPublicPath);
|
|
62
|
+
if (!appScope) {
|
|
63
|
+
return root;
|
|
64
|
+
}
|
|
65
|
+
return normalizeSettingsRootPublicPath(`${root}settings${appScope}`);
|
|
66
|
+
}
|
|
@@ -0,0 +1,301 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This file is part of the NocoBase (R) project.
|
|
3
|
+
* Copyright (c) 2020-2024 NocoBase Co., Ltd.
|
|
4
|
+
* Authors: NocoBase Team.
|
|
5
|
+
*
|
|
6
|
+
* This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
|
|
7
|
+
* For more information, please refer to: https://www.nocobase.com/agreement.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import type { GlobalToken, ThemeConfig } from 'antd';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* 粗略判断一个颜色是不是深色背景。
|
|
14
|
+
*
|
|
15
|
+
* 只支持 `#rgb` / `#rrggbb`,解析不出来时按浅色处理。
|
|
16
|
+
*
|
|
17
|
+
* @param {string | undefined} color 背景色
|
|
18
|
+
* @returns {boolean} 是否为深色
|
|
19
|
+
*/
|
|
20
|
+
export function isDarkColor(color?: string) {
|
|
21
|
+
if (typeof color !== 'string') {
|
|
22
|
+
return false;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const hex = color.trim().replace('#', '');
|
|
26
|
+
const normalized =
|
|
27
|
+
hex.length === 3
|
|
28
|
+
? hex
|
|
29
|
+
.split('')
|
|
30
|
+
.map((char) => char + char)
|
|
31
|
+
.join('')
|
|
32
|
+
: hex;
|
|
33
|
+
|
|
34
|
+
if (normalized.length !== 6 || /[^0-9a-f]/i.test(normalized)) {
|
|
35
|
+
return false;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const r = parseInt(normalized.slice(0, 2), 16);
|
|
39
|
+
const g = parseInt(normalized.slice(2, 4), 16);
|
|
40
|
+
const b = parseInt(normalized.slice(4, 6), 16);
|
|
41
|
+
|
|
42
|
+
// Rec. 601 亮度
|
|
43
|
+
return (r * 299 + g * 587 + b * 114) / 1000 < 128;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* 取顶栏配色。
|
|
48
|
+
*
|
|
49
|
+
* 设置中心的顶栏刻意**不跟随**主题编辑器里的 `colorBgHeader`:那套深色顶栏是给业务端的,
|
|
50
|
+
* 设置中心用容器底色(浅色主题下就是白色)+ 一条细分割线,和业务端在视觉上分开。
|
|
51
|
+
* 文字和图标一律走正文色,深色主题下由 `colorBgContainer` 自动翻成浅色。
|
|
52
|
+
*
|
|
53
|
+
* @param {GlobalToken} token 外层主题 token
|
|
54
|
+
* @returns 顶栏底色、文字色、hover / active 色,以及顶栏是否为深色
|
|
55
|
+
*/
|
|
56
|
+
export function getSettingsHeaderColors(token: GlobalToken) {
|
|
57
|
+
const bg = token.colorBgContainer;
|
|
58
|
+
|
|
59
|
+
return {
|
|
60
|
+
bg,
|
|
61
|
+
dark: isDarkColor(bg),
|
|
62
|
+
text: token.colorText,
|
|
63
|
+
textHover: token.colorText,
|
|
64
|
+
textActive: token.colorText,
|
|
65
|
+
bgHover: token.colorFillQuaternary,
|
|
66
|
+
bgActive: token.colorFillQuaternary,
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* 顶栏一级导航专用主题。
|
|
72
|
+
*
|
|
73
|
+
* 顶栏自己是透明底(底色由 Layout.Header 给),下拉浮层跟着顶栏用容器色;
|
|
74
|
+
* 选中 / hover 的颜色一律交给外层主题,也就是默认蓝。
|
|
75
|
+
*
|
|
76
|
+
* @param {GlobalToken} token 外层主题 token
|
|
77
|
+
* @returns {ThemeConfig} 顶栏菜单主题
|
|
78
|
+
*/
|
|
79
|
+
export function buildSettingsHeaderMenuTheme(token: GlobalToken): ThemeConfig {
|
|
80
|
+
const colors = getSettingsHeaderColors(token);
|
|
81
|
+
|
|
82
|
+
return {
|
|
83
|
+
components: {
|
|
84
|
+
Menu: {
|
|
85
|
+
itemBg: 'transparent',
|
|
86
|
+
popupBg: colors.bg,
|
|
87
|
+
subMenuItemBg: 'transparent',
|
|
88
|
+
// 顶栏表达的是「在哪个模块」,属于背景信息:选中态用一条下划线 + 加粗就够,
|
|
89
|
+
// 实心色块太重,会跟左栏那个「当前页面」的强指示打架。
|
|
90
|
+
// 未选中项也用正文色——次要灰在白底顶栏上偏淡,看不清;选中态靠下划线 + 加粗区分。
|
|
91
|
+
itemColor: token.colorText,
|
|
92
|
+
itemHoverColor: token.colorText,
|
|
93
|
+
horizontalItemSelectedColor: token.colorText,
|
|
94
|
+
horizontalItemSelectedBg: 'transparent',
|
|
95
|
+
horizontalItemHoverColor: token.colorText,
|
|
96
|
+
horizontalItemBorderRadius: 0,
|
|
97
|
+
},
|
|
98
|
+
},
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* 构造设置中心的外壳主题。
|
|
104
|
+
*
|
|
105
|
+
* 只做**顶栏**这一层:白(或深色主题下的容器色)底 + 细分割线,文字与图标走正文色,
|
|
106
|
+
* 和业务端那条深色顶栏区分开。
|
|
107
|
+
*
|
|
108
|
+
* 选中态一律用浅灰底 + 正文色,只有开关这类「开/关」控件才用纯黑填充。
|
|
109
|
+
* 各插件页面里 `controlItemBgActive` 常被当成纯背景用、文字色不跟着走,所以那个 token
|
|
110
|
+
* 也保持浅色,不然列表行会变成深底深字。
|
|
111
|
+
*
|
|
112
|
+
* @param {GlobalToken} token 外层主题 token
|
|
113
|
+
* @returns {ThemeConfig} 设置中心外壳主题
|
|
114
|
+
*/
|
|
115
|
+
export function buildSettingsNeutralTheme(token: GlobalToken): ThemeConfig {
|
|
116
|
+
const headerColors = getSettingsHeaderColors(token);
|
|
117
|
+
const dark = headerColors.dark;
|
|
118
|
+
/** 选中态底色:浅色主题下是黑,深色主题下翻成白 */
|
|
119
|
+
const activeBg = dark ? '#ffffff' : '#000000';
|
|
120
|
+
const activeText = dark ? '#000000' : '#ffffff';
|
|
121
|
+
|
|
122
|
+
return {
|
|
123
|
+
token: {
|
|
124
|
+
// 顶栏图标(帮助、用户中心、pinned 插件)默认按深色顶栏取浅色;
|
|
125
|
+
// 这里的顶栏是灰白的,必须翻成正文色,否则整排图标白底白字看不见。
|
|
126
|
+
colorTextHeaderMenu: headerColors.text,
|
|
127
|
+
colorTextHeaderMenuHover: headerColors.textHover,
|
|
128
|
+
colorTextHeaderMenuActive: headerColors.textActive,
|
|
129
|
+
colorBgHeaderMenuHover: headerColors.bgHover,
|
|
130
|
+
colorBgHeaderMenuActive: headerColors.bgActive,
|
|
131
|
+
// 主色收成黑白。antd 会拿它派生一整套色板,而近黑色派生出来的「浅色背景」是 #5e5e5e
|
|
132
|
+
// 这种中灰,列表行、Select 选项选中后就成了深底深字,所以派生的那几个必须显式钉住。
|
|
133
|
+
colorPrimary: activeBg,
|
|
134
|
+
colorInfo: activeBg,
|
|
135
|
+
colorPrimaryHover: dark ? '#ffffff' : '#333333',
|
|
136
|
+
colorPrimaryActive: dark ? '#d4d4d4' : '#000000',
|
|
137
|
+
colorPrimaryBg: token.colorFillQuaternary,
|
|
138
|
+
colorPrimaryBgHover: token.colorFillTertiary,
|
|
139
|
+
colorPrimaryBorder: token.colorBorder,
|
|
140
|
+
colorPrimaryBorderHover: token.colorTextTertiary,
|
|
141
|
+
// 提示类(Alert / Tag 的 info 态)同理:主色一黑,派生的底色就成了脏灰配黑字。
|
|
142
|
+
colorInfoBg: token.colorFillQuaternary,
|
|
143
|
+
colorInfoBgHover: token.colorFillTertiary,
|
|
144
|
+
colorInfoBorder: token.colorBorderSecondary,
|
|
145
|
+
colorInfoBorderHover: token.colorBorder,
|
|
146
|
+
colorInfoText: token.colorText,
|
|
147
|
+
controlItemBgActive: token.colorFillQuaternary,
|
|
148
|
+
controlItemBgActiveHover: token.colorFillTertiary,
|
|
149
|
+
// 压在主色上的文字:浅色主题下主色是黑,文字白;深色主题反过来。
|
|
150
|
+
colorTextLightSolid: activeText,
|
|
151
|
+
// 链接:灰色 + 下划线(下划线在 settingsGlobalCss 里补,token 层没有这一项)
|
|
152
|
+
colorLink: token.colorTextSecondary,
|
|
153
|
+
colorLinkHover: token.colorText,
|
|
154
|
+
colorLinkActive: token.colorText,
|
|
155
|
+
// 自绘选中态(列表行、树节点)读这一对,见 CustomToken 里的说明。
|
|
156
|
+
// 这里用浅底:整行纯黑太重,行内的图标、标签都得跟着反色,得不偿失。
|
|
157
|
+
// 文字色留空表示「不覆盖」,各页面保持正文色即可。
|
|
158
|
+
colorBgItemActive: token.colorFillSecondary,
|
|
159
|
+
colorTextItemActive: '',
|
|
160
|
+
} as ThemeConfig['token'],
|
|
161
|
+
components: {
|
|
162
|
+
Layout: {
|
|
163
|
+
headerBg: headerColors.bg,
|
|
164
|
+
headerColor: headerColors.text,
|
|
165
|
+
bodyBg: token.colorBgLayout,
|
|
166
|
+
},
|
|
167
|
+
Menu: {
|
|
168
|
+
// 左栏选中态:浅底 + 加粗,不再画左侧竖线。
|
|
169
|
+
itemSelectedBg: token.colorFillSecondary,
|
|
170
|
+
itemSelectedColor: token.colorText,
|
|
171
|
+
// 子菜单容器保留一层很淡的底色标记「这是一组」,但要比选中项浅一档,
|
|
172
|
+
// 否则两层灰叠在一起就分不出哪个是选中的。
|
|
173
|
+
subMenuItemBg: token.colorFillQuaternary,
|
|
174
|
+
subMenuItemSelectedColor: token.colorText,
|
|
175
|
+
horizontalItemSelectedColor: token.colorText,
|
|
176
|
+
horizontalItemSelectedBg: 'transparent',
|
|
177
|
+
activeBarBorderWidth: 0,
|
|
178
|
+
},
|
|
179
|
+
Spin: {
|
|
180
|
+
// 加载态跟着中性配色走,别在一片黑白灰里闪一个蓝点。
|
|
181
|
+
colorPrimary: token.colorTextTertiary,
|
|
182
|
+
},
|
|
183
|
+
Select: {
|
|
184
|
+
// 下拉选中项用浅灰底 + 正文色,别在下拉里压一条纯黑。
|
|
185
|
+
optionSelectedBg: token.colorFillSecondary,
|
|
186
|
+
optionSelectedColor: token.colorText,
|
|
187
|
+
},
|
|
188
|
+
Switch: {
|
|
189
|
+
colorPrimary: activeBg,
|
|
190
|
+
colorPrimaryHover: activeBg,
|
|
191
|
+
},
|
|
192
|
+
Tabs: {
|
|
193
|
+
// 页签选中态跟着黑白走,不然一页里既有黑色选中项又有蓝色页签。
|
|
194
|
+
itemSelectedColor: token.colorText,
|
|
195
|
+
itemHoverColor: token.colorText,
|
|
196
|
+
itemActiveColor: token.colorText,
|
|
197
|
+
inkBarColor: token.colorText,
|
|
198
|
+
},
|
|
199
|
+
},
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* 给外部主题补上设置中心的顶栏配色。
|
|
205
|
+
*
|
|
206
|
+
* 顶栏那几个 token(`colorBgHeader`、`colorTextHeaderMenu` 系列、`Layout.headerBg`)
|
|
207
|
+
* 刻意不放进「简约」主题记录里:那条记录是通用的,业务端顶栏是深色的、logo 和图标都按
|
|
208
|
+
* 深底做的,一旦被改成白底就得连 logo 一起反色。设置中心的顶栏是白的,所以在这一层补。
|
|
209
|
+
*
|
|
210
|
+
* @param {ThemeConfig} theme 主题记录里读到的配置
|
|
211
|
+
* @param {GlobalToken} token 外层主题 token
|
|
212
|
+
* @returns {ThemeConfig} 补齐顶栏配色后的主题
|
|
213
|
+
*/
|
|
214
|
+
export function withSettingsHeaderTheme(theme: ThemeConfig, token: GlobalToken): ThemeConfig {
|
|
215
|
+
const colors = getSettingsHeaderColors(token);
|
|
216
|
+
|
|
217
|
+
return {
|
|
218
|
+
...theme,
|
|
219
|
+
token: {
|
|
220
|
+
...theme.token,
|
|
221
|
+
colorTextHeaderMenu: colors.text,
|
|
222
|
+
colorTextHeaderMenuHover: colors.textHover,
|
|
223
|
+
colorTextHeaderMenuActive: colors.textActive,
|
|
224
|
+
colorBgHeaderMenuHover: colors.bgHover,
|
|
225
|
+
colorBgHeaderMenuActive: colors.bgActive,
|
|
226
|
+
} as ThemeConfig['token'],
|
|
227
|
+
components: {
|
|
228
|
+
...theme.components,
|
|
229
|
+
Layout: {
|
|
230
|
+
...theme.components?.Layout,
|
|
231
|
+
headerBg: colors.bg,
|
|
232
|
+
headerColor: colors.text,
|
|
233
|
+
bodyBg: token.colorBgLayout,
|
|
234
|
+
},
|
|
235
|
+
},
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* 设置中心的全局补丁样式。
|
|
241
|
+
*
|
|
242
|
+
* 这几件事 antd 没给 token,只能落到 CSS:
|
|
243
|
+
* - 裸 `<a>` 的颜色。它吃的是**最外层** ConfigProvider 生成的全局 reset 样式,
|
|
244
|
+
* 我们这层嵌套 ConfigProvider 的 `colorLink` 够不着,只能在 CSS 里压一次。
|
|
245
|
+
* - 链接下划线(`colorLink` 只管颜色)
|
|
246
|
+
* - Switch 禁用态压得更淡,跟「未启用但可点」区分开
|
|
247
|
+
* - 顶栏选中项的下划线、左栏选中项的加粗
|
|
248
|
+
*
|
|
249
|
+
* @param {GlobalToken} token 外层主题 token
|
|
250
|
+
* @returns {string} 可直接塞进 `<style>` 的 CSS
|
|
251
|
+
*/
|
|
252
|
+
export function buildSettingsGlobalCss(token: GlobalToken): string {
|
|
253
|
+
return `
|
|
254
|
+
/* 最外层 body 的底色来自业务端主题(暗黑下是 #141414)。内容区一般把它整块盖住,
|
|
255
|
+
但内容不满屏、或者滚动橡皮筋回弹时会漏出来,所以这里一并接管。
|
|
256
|
+
设置中心是独立入口的单页应用,这条全局规则不会影响业务端。 */
|
|
257
|
+
body {
|
|
258
|
+
background: ${token.colorBgLayout};
|
|
259
|
+
}
|
|
260
|
+
.nb-settings-shell a:not(.ant-btn):not(.ant-menu-item):not(.ant-tabs-tab-btn):not([class*="ant-"]) {
|
|
261
|
+
color: ${token.colorTextSecondary};
|
|
262
|
+
}
|
|
263
|
+
.nb-settings-shell a:not(.ant-btn):not(.ant-menu-item):not(.ant-tabs-tab-btn):not([class*="ant-"]):hover {
|
|
264
|
+
color: ${token.colorText};
|
|
265
|
+
}
|
|
266
|
+
.nb-settings-shell a:not(.ant-btn):not(.ant-menu-item):not(.ant-tabs-tab-btn),
|
|
267
|
+
.nb-settings-shell .ant-btn-link:not(:disabled) {
|
|
268
|
+
text-decoration: underline;
|
|
269
|
+
text-underline-offset: 2px;
|
|
270
|
+
}
|
|
271
|
+
.nb-settings-shell .ant-layout-header .ant-menu-horizontal > .ant-menu-item::after,
|
|
272
|
+
.nb-settings-shell .ant-layout-header .ant-menu-horizontal > .ant-menu-submenu::after {
|
|
273
|
+
border-bottom: none !important;
|
|
274
|
+
}
|
|
275
|
+
.nb-settings-shell .ant-layout-header .ant-menu-horizontal > .ant-menu-item-selected,
|
|
276
|
+
.nb-settings-shell .ant-layout-header .ant-menu-horizontal > .ant-menu-submenu-selected {
|
|
277
|
+
font-weight: 600;
|
|
278
|
+
box-shadow: inset 0 -2px 0 0 ${token.colorText};
|
|
279
|
+
}
|
|
280
|
+
.nb-settings-shell .ant-layout-sider .ant-menu-item-selected {
|
|
281
|
+
font-weight: 600;
|
|
282
|
+
}
|
|
283
|
+
.nb-settings-shell .ant-layout-sider .ant-menu-item::after,
|
|
284
|
+
.nb-settings-shell .ant-layout-sider .ant-menu-submenu-title::after {
|
|
285
|
+
display: none !important;
|
|
286
|
+
}
|
|
287
|
+
/* 未启用态保持 antd 默认的实心灰滑轨:之前做成白底描边太淡,和「禁用」几乎看不出差别。
|
|
288
|
+
禁用态不用整体压暗(插件管理器里一整屏都是不可操作的内置插件,压狠了像坏了),
|
|
289
|
+
而是把滑轨调到跟「未启用」同一档灰、滑块的小白圆点也压成浅灰——
|
|
290
|
+
整体退成一片同色系的浅灰,跟纯黑的「开着且能关」拉开距离。 */
|
|
291
|
+
.nb-settings-shell .ant-switch-disabled {
|
|
292
|
+
opacity: 1;
|
|
293
|
+
}
|
|
294
|
+
.nb-settings-shell .ant-switch-disabled.ant-switch-checked {
|
|
295
|
+
background: ${token.colorTextQuaternary};
|
|
296
|
+
}
|
|
297
|
+
.nb-settings-shell .ant-switch-disabled .ant-switch-handle::before {
|
|
298
|
+
background: ${token.colorBorderSecondary};
|
|
299
|
+
}
|
|
300
|
+
`;
|
|
301
|
+
}
|