@creekjs/web-components 1.0.4 → 1.0.6
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/.turbo/turbo-father$colon$build.log +33 -17
- package/README.md +97 -18
- package/dist/creek-config-provider/CreekConfigContext.d.ts +4 -0
- package/dist/creek-config-provider/CreekConfigContext.d.ts.map +1 -0
- package/dist/creek-config-provider/CreekConfigContext.js.map +2 -2
- package/dist/creek-config-provider/CreekI18nProvider.d.ts +22 -0
- package/dist/creek-config-provider/CreekI18nProvider.d.ts.map +1 -0
- package/dist/creek-config-provider/CreekI18nProvider.js +92 -0
- package/dist/creek-config-provider/CreekI18nProvider.js.map +7 -0
- package/dist/creek-config-provider/index.d.ts +5 -3
- package/dist/creek-config-provider/index.d.ts.map +1 -0
- package/dist/creek-config-provider/index.js +47 -4
- package/dist/creek-config-provider/index.js.map +3 -3
- package/dist/creek-hooks/index.d.ts.map +1 -0
- package/dist/creek-hooks/useApp/DrawerHelper.d.ts.map +1 -0
- package/dist/creek-hooks/useApp/ModalHelper.d.ts.map +1 -0
- package/dist/creek-hooks/useApp/index.d.ts +3 -3
- package/dist/creek-hooks/useApp/index.d.ts.map +1 -0
- package/dist/creek-hooks/useApp/types.d.ts.map +1 -0
- package/dist/creek-hooks/useViewportHeight.d.ts.map +1 -0
- package/dist/creek-icon/index.d.ts.map +1 -0
- package/dist/creek-keep-alive/index.d.ts +24 -1
- package/dist/creek-keep-alive/index.d.ts.map +1 -0
- package/dist/creek-keep-alive/index.js +141 -4
- package/dist/creek-keep-alive/index.js.map +2 -2
- package/dist/creek-layout/ActionRender/FullScreen.d.ts.map +1 -0
- package/dist/creek-layout/{HeaderContent → ActionRender}/FullScreen.js +4 -2
- package/dist/creek-layout/ActionRender/FullScreen.js.map +7 -0
- package/dist/creek-layout/ActionRender/LayoutSettings.d.ts +5 -0
- package/dist/creek-layout/ActionRender/LayoutSettings.d.ts.map +1 -0
- package/dist/creek-layout/ActionRender/LayoutSettings.js +73 -0
- package/dist/creek-layout/ActionRender/LayoutSettings.js.map +7 -0
- package/dist/creek-layout/ActionRender/UserInfo.d.ts +8 -0
- package/dist/creek-layout/ActionRender/UserInfo.d.ts.map +1 -0
- package/dist/creek-layout/{HeaderContent → ActionRender}/UserInfo.js +7 -29
- package/dist/creek-layout/ActionRender/UserInfo.js.map +7 -0
- package/dist/creek-layout/ActionRender/index.d.ts +3 -0
- package/dist/creek-layout/ActionRender/index.d.ts.map +1 -0
- package/dist/creek-layout/ActionRender/index.js +36 -0
- package/dist/creek-layout/ActionRender/index.js.map +7 -0
- package/dist/creek-layout/CollapseButton.d.ts.map +1 -0
- package/dist/creek-layout/Exception/NotFound.d.ts.map +1 -0
- package/dist/creek-layout/Exception/NotFoundPage.d.ts.map +1 -0
- package/dist/creek-layout/Exception/index.d.ts.map +1 -0
- package/dist/creek-layout/index.d.ts +9 -2
- package/dist/creek-layout/index.d.ts.map +1 -0
- package/dist/creek-layout/index.js +96 -39
- package/dist/creek-layout/index.js.map +3 -3
- package/dist/creek-layout/useLayoutSettingsStore.d.ts +20 -0
- package/dist/creek-layout/useLayoutSettingsStore.d.ts.map +1 -0
- package/dist/creek-layout/useLayoutSettingsStore.js +45 -0
- package/dist/creek-layout/useLayoutSettingsStore.js.map +7 -0
- package/dist/creek-loading/index.d.ts.map +1 -0
- package/dist/creek-locale-button/index.d.ts +1 -0
- package/dist/creek-locale-button/index.d.ts.map +1 -0
- package/dist/creek-locale-button/index.js +66 -0
- package/dist/creek-locale-button/index.js.map +7 -0
- package/dist/creek-page-container/index.d.ts +4 -0
- package/dist/creek-page-container/index.d.ts.map +1 -0
- package/dist/creek-page-container/index.js +68 -0
- package/dist/creek-page-container/index.js.map +7 -0
- package/dist/creek-style/index.d.ts +1 -0
- package/dist/creek-style/index.d.ts.map +1 -0
- package/dist/creek-style/index.js +24 -0
- package/dist/creek-style/index.js.map +7 -0
- package/dist/creek-style/scrollbar.d.ts +2 -0
- package/dist/creek-style/scrollbar.d.ts.map +1 -0
- package/dist/creek-style/scrollbar.js +55 -0
- package/dist/creek-style/scrollbar.js.map +7 -0
- package/dist/creek-table/SearchTable.d.ts +9 -0
- package/dist/creek-table/SearchTable.d.ts.map +1 -0
- package/dist/creek-table/SearchTable.js +109 -72
- package/dist/creek-table/SearchTable.js.map +3 -3
- package/dist/creek-table/components/DensityIcon.d.ts +9 -0
- package/dist/creek-table/components/DensityIcon.d.ts.map +1 -0
- package/dist/creek-table/components/DensityIcon.js +77 -0
- package/dist/creek-table/components/DensityIcon.js.map +7 -0
- package/dist/creek-table/components/EllipsisTooltip.d.ts +9 -0
- package/dist/creek-table/components/EllipsisTooltip.d.ts.map +1 -0
- package/dist/creek-table/components/EllipsisTooltip.js +122 -0
- package/dist/creek-table/components/EllipsisTooltip.js.map +7 -0
- package/dist/creek-table/components/index.d.ts +2 -0
- package/dist/creek-table/components/index.d.ts.map +1 -0
- package/dist/creek-table/components/index.js +26 -0
- package/dist/creek-table/components/index.js.map +7 -0
- package/dist/creek-table/hooks/index.d.ts +5 -0
- package/dist/creek-table/hooks/index.d.ts.map +1 -0
- package/dist/creek-table/hooks/index.js +10 -0
- package/dist/creek-table/hooks/index.js.map +2 -2
- package/dist/creek-table/hooks/useAdaptiveToolBar.d.ts.map +1 -0
- package/dist/creek-table/hooks/useAutoWidthColumns.d.ts +1 -1
- package/dist/creek-table/hooks/useAutoWidthColumns.d.ts.map +1 -0
- package/dist/creek-table/hooks/useAutoWidthColumns.js +76 -17
- package/dist/creek-table/hooks/useAutoWidthColumns.js.map +2 -2
- package/dist/creek-table/hooks/useElementDistance.d.ts.map +1 -0
- package/dist/creek-table/hooks/useEllipsisColumns.d.ts +8 -0
- package/dist/creek-table/hooks/useEllipsisColumns.d.ts.map +1 -0
- package/dist/creek-table/hooks/useEllipsisColumns.js +58 -0
- package/dist/creek-table/hooks/useEllipsisColumns.js.map +7 -0
- package/dist/creek-table/hooks/useIndexColumn.d.ts +2 -0
- package/dist/creek-table/hooks/useIndexColumn.d.ts.map +1 -0
- package/dist/creek-table/hooks/useIndexColumn.js +52 -0
- package/dist/creek-table/hooks/useIndexColumn.js.map +7 -0
- package/dist/creek-table/hooks/useResizableColumns.d.ts +20 -0
- package/dist/creek-table/hooks/useResizableColumns.d.ts.map +1 -0
- package/dist/creek-table/hooks/useResizableColumns.js +279 -0
- package/dist/creek-table/hooks/useResizableColumns.js.map +7 -0
- package/dist/creek-table/hooks/useStatusColumns.d.ts +2 -0
- package/dist/creek-table/hooks/useStatusColumns.d.ts.map +1 -0
- package/dist/creek-table/hooks/useStatusColumns.js +215 -0
- package/dist/creek-table/hooks/useStatusColumns.js.map +7 -0
- package/dist/creek-table/hooks/useTableOptions.d.ts +15 -0
- package/dist/creek-table/hooks/useTableOptions.d.ts.map +1 -0
- package/dist/creek-table/hooks/useTableOptions.js +78 -0
- package/dist/creek-table/hooks/useTableOptions.js.map +7 -0
- package/dist/creek-table/hooks/useTableScrollHeight.d.ts +6 -1
- package/dist/creek-table/hooks/useTableScrollHeight.d.ts.map +1 -0
- package/dist/creek-table/hooks/useTableScrollHeight.js +44 -5
- package/dist/creek-table/hooks/useTableScrollHeight.js.map +2 -2
- package/dist/creek-table/index.d.ts.map +1 -0
- package/dist/creek-table/type.d.ts +4 -6
- package/dist/creek-table/type.d.ts.map +1 -0
- package/dist/creek-table/type.js.map +1 -1
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +8 -0
- package/dist/index.js.map +2 -2
- package/dist/locales/en-US.d.ts +25 -0
- package/dist/locales/en-US.d.ts.map +1 -0
- package/dist/locales/en-US.js +49 -0
- package/dist/locales/en-US.js.map +7 -0
- package/dist/locales/zh-CN.d.ts +25 -0
- package/dist/locales/zh-CN.d.ts.map +1 -0
- package/dist/locales/zh-CN.js +49 -0
- package/dist/locales/zh-CN.js.map +7 -0
- package/dist/utils/i18n.d.ts +2 -0
- package/dist/utils/i18n.d.ts.map +1 -0
- package/dist/utils/i18n.js +34 -0
- package/dist/utils/i18n.js.map +7 -0
- package/i18n.config.ts +27 -0
- package/package.json +17 -3
- package/src/creek-config-provider/CreekConfigContext.tsx +5 -1
- package/src/creek-config-provider/CreekI18nProvider.tsx +87 -0
- package/src/creek-config-provider/index.tsx +53 -4
- package/src/creek-keep-alive/index.tsx +225 -6
- package/src/creek-layout/{HeaderContent → ActionRender}/FullScreen.tsx +10 -6
- package/src/creek-layout/ActionRender/LayoutSettings.tsx +67 -0
- package/src/creek-layout/ActionRender/UserInfo.tsx +43 -0
- package/src/creek-layout/ActionRender/index.tsx +4 -0
- package/src/creek-layout/index.tsx +121 -53
- package/src/creek-layout/useLayoutSettingsStore.ts +25 -0
- package/src/creek-locale-button/index.tsx +42 -0
- package/src/creek-page-container/index.tsx +32 -0
- package/src/creek-style/index.ts +1 -0
- package/src/creek-style/scrollbar.ts +29 -0
- package/src/creek-table/SearchTable.tsx +125 -72
- package/src/creek-table/components/DensityIcon.tsx +63 -0
- package/src/creek-table/components/EllipsisTooltip.tsx +116 -0
- package/src/creek-table/components/index.tsx +3 -0
- package/src/creek-table/hooks/index.ts +5 -1
- package/src/creek-table/hooks/useAutoWidthColumns.tsx +93 -19
- package/src/creek-table/hooks/useEllipsisColumns.tsx +47 -0
- package/src/creek-table/hooks/useIndexColumn.tsx +27 -0
- package/src/creek-table/hooks/useResizableColumns.tsx +323 -0
- package/src/creek-table/hooks/useStatusColumns.tsx +252 -0
- package/src/creek-table/hooks/useTableOptions.tsx +81 -0
- package/src/creek-table/hooks/useTableScrollHeight.tsx +61 -6
- package/src/creek-table/type.ts +5 -7
- package/src/index.tsx +4 -0
- package/src/locales/en-US.ts +24 -0
- package/src/locales/zh-CN.ts +24 -0
- package/src/utils/i18n.ts +4 -0
- package/dist/creek-layout/HeaderContent/FullScreen.js.map +0 -7
- package/dist/creek-layout/HeaderContent/UserInfo.d.ts +0 -1
- package/dist/creek-layout/HeaderContent/UserInfo.js.map +0 -7
- package/dist/creek-layout/HeaderContent/index.d.ts +0 -1
- package/dist/creek-layout/HeaderContent/index.js +0 -49
- package/dist/creek-layout/HeaderContent/index.js.map +0 -7
- package/dist/creek-table/TableOptionRender.d.ts +0 -9
- package/dist/creek-table/TableOptionRender.js +0 -74
- package/dist/creek-table/TableOptionRender.js.map +0 -7
- package/dist/creek-table/toolBarRender.d.ts +0 -5
- package/dist/creek-table/toolBarRender.js +0 -58
- package/dist/creek-table/toolBarRender.js.map +0 -7
- package/src/creek-layout/HeaderContent/UserInfo.tsx +0 -54
- package/src/creek-layout/HeaderContent/index.tsx +0 -24
- package/src/creek-table/TableOptionRender.tsx +0 -57
- package/src/creek-table/toolBarRender.tsx +0 -28
- /package/dist/creek-layout/{HeaderContent → ActionRender}/FullScreen.d.ts +0 -0
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
import { createContext } from 'react';
|
|
2
2
|
|
|
3
3
|
export type CreekConfigContextProps = {
|
|
4
|
-
iconFontCNs?: string[]
|
|
4
|
+
iconFontCNs?: string[];
|
|
5
|
+
/**
|
|
6
|
+
* 国际化语言包
|
|
7
|
+
*/
|
|
8
|
+
locale?: Record<string, string>;
|
|
5
9
|
};
|
|
6
10
|
|
|
7
11
|
export const CreekConfigContext = createContext<CreekConfigContextProps>({});
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import type { ReactNode } from 'react';
|
|
2
|
+
import { createContext, useCallback, useContext, useEffect, useState } from 'react';
|
|
3
|
+
|
|
4
|
+
import { IntlContext, IntlProvider, getIntl, getLocale, setLocale, setLocaleMessages } from '@creekjs/i18n/react';
|
|
5
|
+
|
|
6
|
+
import enUS from '../locales/en-US';
|
|
7
|
+
import zhCN from '../locales/zh-CN';
|
|
8
|
+
|
|
9
|
+
const DEFAULT_LOCALE = 'zh-CN';
|
|
10
|
+
|
|
11
|
+
export interface CreekI18nProviderProps {
|
|
12
|
+
children?: ReactNode;
|
|
13
|
+
/**
|
|
14
|
+
* 语言标识
|
|
15
|
+
* @default 'zh-CN'
|
|
16
|
+
*/
|
|
17
|
+
locale?: string;
|
|
18
|
+
/**
|
|
19
|
+
* 国际化语言包,透传给 react-intl
|
|
20
|
+
*/
|
|
21
|
+
messages?: Record<string, string>;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const MESSAGES_MAP: Record<string, Record<string, string>> = {
|
|
25
|
+
[DEFAULT_LOCALE]: zhCN,
|
|
26
|
+
'en-US': enUS,
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
export const LocaleContext = createContext({
|
|
30
|
+
locale: getLocale() || DEFAULT_LOCALE,
|
|
31
|
+
changeLocale: (lang: string) => {},
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
export const useAppLocale = () => useContext(LocaleContext);
|
|
35
|
+
|
|
36
|
+
export const CreekI18nProvider = (props: CreekI18nProviderProps) => {
|
|
37
|
+
const { children, locale, messages } = props;
|
|
38
|
+
|
|
39
|
+
// Try to get parent intl context
|
|
40
|
+
const parentIntl = useContext(IntlContext);
|
|
41
|
+
|
|
42
|
+
const [intl, setIntl] = useState(() => getIntl());
|
|
43
|
+
|
|
44
|
+
const changeLocale = useCallback((lang: string) => {
|
|
45
|
+
setLocale(lang, false);
|
|
46
|
+
setIntl(getIntl());
|
|
47
|
+
}, []);
|
|
48
|
+
|
|
49
|
+
// 1. 确定最终生效的 locale
|
|
50
|
+
// 优先级:当前组件内部的 state > props.locale > parentIntl.locale > 全局默认
|
|
51
|
+
const currentLocale = intl?.locale || locale || parentIntl?.locale || DEFAULT_LOCALE;
|
|
52
|
+
|
|
53
|
+
// 2. 提取父级上下文的安全配置 (避免将 IntlShape 的内部方法直接传给 IntlProvider)
|
|
54
|
+
const intlConfig = parentIntl || intl || {};
|
|
55
|
+
const safeConfig = {
|
|
56
|
+
formats: intlConfig.formats,
|
|
57
|
+
defaultLocale: intlConfig.defaultLocale,
|
|
58
|
+
defaultFormats: intlConfig.defaultFormats,
|
|
59
|
+
onError: intlConfig.onError,
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
// 3. 确定最终的 messages
|
|
63
|
+
// 避免使用 stale 的 parentIntl.messages
|
|
64
|
+
let baseMessages = {};
|
|
65
|
+
if (parentIntl && parentIntl.locale === currentLocale) {
|
|
66
|
+
baseMessages = parentIntl.messages;
|
|
67
|
+
} else {
|
|
68
|
+
baseMessages = getIntl()?.messages || {};
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const finalMessages = {
|
|
72
|
+
...baseMessages,
|
|
73
|
+
...(MESSAGES_MAP[currentLocale] || zhCN),
|
|
74
|
+
...(messages || {}),
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
// 4. 同步给全局 globalIntl,确保非 React 组件能够拿到
|
|
78
|
+
useEffect(() => {
|
|
79
|
+
setLocaleMessages(currentLocale, finalMessages);
|
|
80
|
+
}, [currentLocale, finalMessages]);
|
|
81
|
+
|
|
82
|
+
return (
|
|
83
|
+
<IntlProvider {...safeConfig} locale={currentLocale} messages={finalMessages}>
|
|
84
|
+
<LocaleContext.Provider value={{ locale: currentLocale, changeLocale }}>{children}</LocaleContext.Provider>
|
|
85
|
+
</IntlProvider>
|
|
86
|
+
);
|
|
87
|
+
};
|
|
@@ -1,12 +1,61 @@
|
|
|
1
|
+
import type { ConfigProviderProps } from 'antd';
|
|
2
|
+
import { App, ConfigProvider } from 'antd';
|
|
3
|
+
import enUS_antd from 'antd/locale/en_US';
|
|
4
|
+
import zhCN_antd from 'antd/locale/zh_CN';
|
|
5
|
+
import merge from 'lodash/merge';
|
|
6
|
+
|
|
7
|
+
import { AppProvider } from '../creek-hooks';
|
|
8
|
+
import { useLayoutSettingsStore } from '../creek-layout/useLayoutSettingsStore';
|
|
1
9
|
import { CreekConfigContext, CreekConfigContextProps } from './CreekConfigContext';
|
|
10
|
+
import { CreekI18nProvider, CreekI18nProviderProps, LocaleContext, useAppLocale } from './CreekI18nProvider';
|
|
11
|
+
|
|
12
|
+
export type CreekConfigProviderProps = CreekConfigContextProps & Omit<ConfigProviderProps, 'locale'> & CreekI18nProviderProps;
|
|
13
|
+
|
|
14
|
+
export { CreekI18nProvider, LocaleContext, useAppLocale };
|
|
15
|
+
export type { CreekI18nProviderProps };
|
|
16
|
+
|
|
17
|
+
const InnerConfigProvider = (props: Omit<CreekConfigProviderProps, 'locale' | 'messages'>) => {
|
|
18
|
+
const { children, theme, ...more } = props;
|
|
19
|
+
const { locale } = useAppLocale();
|
|
20
|
+
const settingsStore = useLayoutSettingsStore();
|
|
2
21
|
|
|
3
|
-
|
|
4
|
-
|
|
22
|
+
const activeColorPrimary = settingsStore.colorPrimary || theme?.token?.colorPrimary;
|
|
23
|
+
|
|
24
|
+
let finalTheme = merge(
|
|
25
|
+
{},
|
|
26
|
+
theme,
|
|
27
|
+
activeColorPrimary
|
|
28
|
+
? {
|
|
29
|
+
token: {
|
|
30
|
+
colorPrimary: activeColorPrimary,
|
|
31
|
+
colorLink: activeColorPrimary,
|
|
32
|
+
colorLinkHover: activeColorPrimary,
|
|
33
|
+
colorLinkActive: activeColorPrimary,
|
|
34
|
+
},
|
|
35
|
+
}
|
|
36
|
+
: {}
|
|
37
|
+
);
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
return (
|
|
41
|
+
<ConfigProvider locale={locale === 'en-US' ? enUS_antd : zhCN_antd} theme={finalTheme} {...more}>
|
|
42
|
+
<CreekConfigContext.Provider value={more as any}>
|
|
43
|
+
<App>
|
|
44
|
+
<AppProvider>{children}</AppProvider>
|
|
45
|
+
</App>
|
|
46
|
+
</CreekConfigContext.Provider>
|
|
47
|
+
</ConfigProvider>
|
|
48
|
+
);
|
|
5
49
|
};
|
|
6
50
|
|
|
7
51
|
export const CreekConfigProvider = (props: CreekConfigProviderProps) => {
|
|
8
|
-
const { children, ...more } = props;
|
|
9
|
-
|
|
52
|
+
const { children, locale, messages, ...more } = props;
|
|
53
|
+
|
|
54
|
+
return (
|
|
55
|
+
<CreekI18nProvider locale={locale} messages={messages}>
|
|
56
|
+
<InnerConfigProvider {...more}>{children}</InnerConfigProvider>
|
|
57
|
+
</CreekI18nProvider>
|
|
58
|
+
);
|
|
10
59
|
};
|
|
11
60
|
|
|
12
61
|
CreekConfigProvider.CreekConfigContext = CreekConfigContext;
|
|
@@ -1,11 +1,230 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { useMemoizedFn } from 'ahooks';
|
|
2
|
+
import { Dropdown, MenuProps, Tabs } from 'antd';
|
|
3
|
+
import { isRegExp, isString, omit } from 'lodash';
|
|
4
|
+
import React, { useEffect, useState } from 'react';
|
|
5
|
+
import { useLocation, useNavigate, useOutlet } from 'react-router-dom';
|
|
2
6
|
|
|
3
|
-
|
|
4
|
-
const [isMounted, setIsMounted] = useState(false);
|
|
7
|
+
import { useT } from '@/utils/i18n';
|
|
5
8
|
|
|
9
|
+
export interface CreekKeepAliveProps {
|
|
10
|
+
/**
|
|
11
|
+
* 不需要缓存的路径
|
|
12
|
+
*/
|
|
13
|
+
exclude?: (string | RegExp)[];
|
|
14
|
+
/**
|
|
15
|
+
* 自定义Tab标题获取方法
|
|
16
|
+
*/
|
|
17
|
+
getTabTitle?: (pathname: string) => React.ReactNode;
|
|
18
|
+
/**
|
|
19
|
+
* 默认首页路径
|
|
20
|
+
*/
|
|
21
|
+
homePath?: string;
|
|
22
|
+
/**
|
|
23
|
+
* Tabs的样式
|
|
24
|
+
*/
|
|
25
|
+
tabBarStyle?: React.CSSProperties;
|
|
26
|
+
/**
|
|
27
|
+
* 最大缓存数量,默认为 20
|
|
28
|
+
*/
|
|
29
|
+
maxTabCount?: number;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
interface TabItem {
|
|
33
|
+
key: string;
|
|
34
|
+
label: React.ReactNode;
|
|
35
|
+
closable?: boolean;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export const CreekKeepAlive: React.FC<CreekKeepAliveProps> = (props) => {
|
|
39
|
+
const { exclude = [], getTabTitle, homePath = '/', tabBarStyle, maxTabCount = 20 } = props;
|
|
40
|
+
|
|
41
|
+
const t = useT();
|
|
42
|
+
const outlet = useOutlet();
|
|
43
|
+
const location = useLocation();
|
|
44
|
+
const navigate = useNavigate();
|
|
45
|
+
|
|
46
|
+
const [tabItems, setTabItems] = useState<TabItem[]>([]);
|
|
47
|
+
const [activeKey, setActiveKey] = useState<string>('');
|
|
48
|
+
const [cachedPages, setCachedPages] = useState<Record<string, React.ReactNode>>({});
|
|
49
|
+
|
|
50
|
+
// 判断是否不需要缓存
|
|
51
|
+
const isPathExcluded = (path: string) => {
|
|
52
|
+
return exclude.some((item) => {
|
|
53
|
+
if (isString(item)) {
|
|
54
|
+
return item === path;
|
|
55
|
+
}
|
|
56
|
+
if (isRegExp(item)) {
|
|
57
|
+
return item.test(path);
|
|
58
|
+
}
|
|
59
|
+
return false;
|
|
60
|
+
});
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
// 初始化或路由变化时更新
|
|
6
64
|
useEffect(() => {
|
|
7
|
-
|
|
65
|
+
const currentPath = location.pathname;
|
|
66
|
+
setActiveKey(currentPath);
|
|
67
|
+
|
|
68
|
+
// 更新页面内容缓存
|
|
69
|
+
setCachedPages((prev) => {
|
|
70
|
+
if (prev[currentPath]) {
|
|
71
|
+
return prev;
|
|
72
|
+
}
|
|
73
|
+
return {
|
|
74
|
+
...prev,
|
|
75
|
+
[currentPath]: outlet,
|
|
76
|
+
};
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
// 更新 Tab 列表
|
|
80
|
+
setTabItems((prev) => {
|
|
81
|
+
if (prev.find((i) => i.key === currentPath)) {
|
|
82
|
+
return prev;
|
|
83
|
+
}
|
|
84
|
+
const title = getTabTitle?.(currentPath) || currentPath;
|
|
85
|
+
const newItems = [...prev, { key: currentPath, label: title, closable: currentPath !== homePath }];
|
|
86
|
+
|
|
87
|
+
// 超过最大数量限制
|
|
88
|
+
if (newItems.length > maxTabCount) {
|
|
89
|
+
// 找到第一个可以关闭的 Tab(非首页、非当前页)
|
|
90
|
+
// 这里策略是移除最早加入的那个可关闭 Tab。prev[0] 是最早的。
|
|
91
|
+
// 但要注意不要移除当前页(currentPath),虽然 currentPath 是刚加进去的,但在极端情况下(比如 max=1)
|
|
92
|
+
// 简单策略:移除第一个 closable 且 key !== currentPath 的 item
|
|
93
|
+
const indexToRemove = newItems.findIndex((item) => item.closable && item.key !== currentPath);
|
|
94
|
+
if (indexToRemove !== -1) {
|
|
95
|
+
const itemToRemove = newItems[indexToRemove];
|
|
96
|
+
// 顺便移除缓存
|
|
97
|
+
setCachedPages((currentCached) => omit(currentCached, [itemToRemove.key]));
|
|
98
|
+
newItems.splice(indexToRemove, 1);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
return newItems;
|
|
102
|
+
});
|
|
103
|
+
}, [location.pathname, outlet, getTabTitle, homePath, maxTabCount]);
|
|
104
|
+
|
|
105
|
+
// 清理不需要缓存的页面
|
|
106
|
+
useEffect(() => {
|
|
107
|
+
// 监听路由离开
|
|
108
|
+
// 这里比较 tricky,因为 useEffect 拿到的 activeKey 已经是新的了
|
|
109
|
+
// 我们需要知道"上一个"路径
|
|
110
|
+
// 简化处理:每次 render 时,检查 pages 里哪些是不需要缓存且不处于 active 状态的,将其移除?
|
|
111
|
+
// 但如果在 setState 里做会导致死循环。
|
|
112
|
+
// 另一种策略:不缓存 = 离开时销毁。
|
|
113
|
+
// 我们可以在 pages 渲染时控制。
|
|
8
114
|
}, []);
|
|
9
115
|
|
|
10
|
-
|
|
11
|
-
|
|
116
|
+
const closeTab = useMemoizedFn((targetKey: string) => {
|
|
117
|
+
const targetIndex = tabItems.findIndex((item) => item.key === targetKey);
|
|
118
|
+
const newTabItems = tabItems.filter((item) => item.key !== targetKey);
|
|
119
|
+
|
|
120
|
+
// 移除缓存
|
|
121
|
+
setCachedPages((prev) => omit(prev, [targetKey]));
|
|
122
|
+
setTabItems(newTabItems);
|
|
123
|
+
|
|
124
|
+
// 如果关闭的是当前页,跳转到临近页
|
|
125
|
+
if (targetKey === activeKey) {
|
|
126
|
+
if (newTabItems.length > 0) {
|
|
127
|
+
// 尝试跳到右边,没有则左边
|
|
128
|
+
const nextIndex = targetIndex >= newTabItems.length ? newTabItems.length - 1 : targetIndex;
|
|
129
|
+
const nextKey = newTabItems[nextIndex].key;
|
|
130
|
+
navigate(nextKey);
|
|
131
|
+
} else {
|
|
132
|
+
navigate(homePath);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
const closeOtherTabs = useMemoizedFn((currentKey: string) => {
|
|
138
|
+
const newTabItems = tabItems.filter((item) => item.key === currentKey || item.key === homePath);
|
|
139
|
+
setTabItems(newTabItems);
|
|
140
|
+
|
|
141
|
+
const keepKeys = newTabItems.map((i) => i.key);
|
|
142
|
+
setCachedPages((prev) => {
|
|
143
|
+
const newCachedPages: Record<string, React.ReactNode> = {};
|
|
144
|
+
keepKeys.forEach((k) => {
|
|
145
|
+
if (prev[k]) newCachedPages[k] = prev[k];
|
|
146
|
+
});
|
|
147
|
+
return newCachedPages;
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
if (activeKey !== currentKey) {
|
|
151
|
+
navigate(currentKey);
|
|
152
|
+
}
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
const closeRightTabs = useMemoizedFn((currentKey: string) => {
|
|
156
|
+
const currentIndex = tabItems.findIndex((i) => i.key === currentKey);
|
|
157
|
+
const rightItems = tabItems.slice(currentIndex + 1);
|
|
158
|
+
const rightKeys = rightItems.map((i) => i.key);
|
|
159
|
+
|
|
160
|
+
const newTabItems = tabItems.filter((i) => !rightKeys.includes(i.key));
|
|
161
|
+
setTabItems(newTabItems);
|
|
162
|
+
|
|
163
|
+
setCachedPages((prev) => omit(prev, rightKeys));
|
|
164
|
+
|
|
165
|
+
if (rightKeys.includes(activeKey)) {
|
|
166
|
+
navigate(currentKey);
|
|
167
|
+
}
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
const handleTabEdit = (targetKey: React.MouseEvent | React.KeyboardEvent | string, action: 'add' | 'remove') => {
|
|
171
|
+
if (action === 'remove' && isString(targetKey)) {
|
|
172
|
+
closeTab(targetKey);
|
|
173
|
+
}
|
|
174
|
+
};
|
|
175
|
+
|
|
176
|
+
const handleTabClick = (key: string) => {
|
|
177
|
+
navigate(key);
|
|
178
|
+
};
|
|
179
|
+
|
|
180
|
+
const renderTabLabel = (item: TabItem) => {
|
|
181
|
+
const menuItems: MenuProps['items'] = [
|
|
182
|
+
{
|
|
183
|
+
key: 'close',
|
|
184
|
+
label: t('creek-keep-alive.index.guanBiDangQian', '关闭当前'),
|
|
185
|
+
disabled: item.key === homePath,
|
|
186
|
+
onClick: () => closeTab(item.key),
|
|
187
|
+
},
|
|
188
|
+
{
|
|
189
|
+
key: 'closeOthers',
|
|
190
|
+
label: t('creek-keep-alive.index.guanBiQiTa', '关闭其他'),
|
|
191
|
+
onClick: () => closeOtherTabs(item.key),
|
|
192
|
+
},
|
|
193
|
+
{
|
|
194
|
+
key: 'closeRight',
|
|
195
|
+
label: t('creek-keep-alive.index.guanBiYouCe', '关闭右侧'),
|
|
196
|
+
onClick: () => closeRightTabs(item.key),
|
|
197
|
+
},
|
|
198
|
+
];
|
|
199
|
+
|
|
200
|
+
return (
|
|
201
|
+
<Dropdown menu={{ items: menuItems }} trigger={['contextMenu']}>
|
|
202
|
+
<span>{item.label}</span>
|
|
203
|
+
</Dropdown>
|
|
204
|
+
);
|
|
205
|
+
};
|
|
206
|
+
|
|
207
|
+
return (
|
|
208
|
+
<div className="creek-keep-alive">
|
|
209
|
+
<Tabs
|
|
210
|
+
activeKey={activeKey}
|
|
211
|
+
type="editable-card"
|
|
212
|
+
hideAdd
|
|
213
|
+
onChange={handleTabClick}
|
|
214
|
+
onEdit={handleTabEdit}
|
|
215
|
+
tabBarStyle={{ margin: 0, ...tabBarStyle }}
|
|
216
|
+
items={tabItems.map((item) => ({
|
|
217
|
+
...item,
|
|
218
|
+
label: renderTabLabel(item),
|
|
219
|
+
children: (
|
|
220
|
+
<div key={item.key} style={{ height: '100%', display: activeKey === item.key ? 'block' : 'none' }}>
|
|
221
|
+
{/* 如果是不缓存的页面,且不是当前页,则不渲染(销毁) */}
|
|
222
|
+
{/* 如果是缓存页面,或者是当前页,则渲染 */}
|
|
223
|
+
{!isPathExcluded(item.key) || activeKey === item.key ? cachedPages[item.key] : null}
|
|
224
|
+
</div>
|
|
225
|
+
),
|
|
226
|
+
}))}
|
|
227
|
+
/>
|
|
228
|
+
</div>
|
|
229
|
+
);
|
|
230
|
+
};
|
|
@@ -1,7 +1,10 @@
|
|
|
1
|
-
import { FullscreenExitOutlined, FullscreenOutlined } from
|
|
2
|
-
import { useFullscreen, useMemoizedFn } from
|
|
3
|
-
import { Tooltip } from
|
|
4
|
-
|
|
1
|
+
import { FullscreenExitOutlined, FullscreenOutlined } from '@ant-design/icons';
|
|
2
|
+
import { useFullscreen, useMemoizedFn } from 'ahooks';
|
|
3
|
+
import { Tooltip } from 'antd';
|
|
4
|
+
|
|
5
|
+
import { create } from 'zustand';
|
|
6
|
+
|
|
7
|
+
import { useT } from '@/utils/i18n';
|
|
5
8
|
|
|
6
9
|
export type FullScreenStore = {
|
|
7
10
|
isFullScreen: boolean;
|
|
@@ -21,6 +24,7 @@ export const useFullScreenStore = create<FullScreenStore>((set, get) => {
|
|
|
21
24
|
});
|
|
22
25
|
|
|
23
26
|
export const FullScreen = () => {
|
|
27
|
+
const t = useT();
|
|
24
28
|
const [, { toggleFullscreen }] = useFullscreen(document.body);
|
|
25
29
|
|
|
26
30
|
const { isFullScreen, changeFullScreen } = useFullScreenStore.getState();
|
|
@@ -33,11 +37,11 @@ export const FullScreen = () => {
|
|
|
33
37
|
return (
|
|
34
38
|
<>
|
|
35
39
|
{isFullScreen ? (
|
|
36
|
-
<Tooltip title=
|
|
40
|
+
<Tooltip title={t('creek-layout.ActionRender.FullScreen.tuiChuQuanPing', '退出全屏')} placement="top">
|
|
37
41
|
<FullscreenExitOutlined onClick={handleFullScreen} />
|
|
38
42
|
</Tooltip>
|
|
39
43
|
) : (
|
|
40
|
-
<Tooltip title=
|
|
44
|
+
<Tooltip title={t('creek-layout.ActionRender.FullScreen.quanPing', '全屏')} placement="top">
|
|
41
45
|
<FullscreenOutlined onClick={handleFullScreen} />
|
|
42
46
|
</Tooltip>
|
|
43
47
|
)}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { SettingOutlined } from '@ant-design/icons';
|
|
2
|
+
import { ColorPicker, Form, Switch, Tooltip } from 'antd';
|
|
3
|
+
|
|
4
|
+
import { useT } from '@creekjs/i18n/react';
|
|
5
|
+
|
|
6
|
+
import { useApp } from '../../creek-hooks';
|
|
7
|
+
import { useLayoutSettingsStore } from '../useLayoutSettingsStore';
|
|
8
|
+
|
|
9
|
+
const SettingsForm = ({ defaultShowFullScreen, defaultShowLocaleButton, defaultKeepAlive }: { defaultShowFullScreen?: boolean; defaultShowLocaleButton?: boolean; defaultKeepAlive?: boolean }) => {
|
|
10
|
+
const t = useT();
|
|
11
|
+
const { colorPrimary, showFullScreen, showLocaleButton, keepAlive, setSettings } = useLayoutSettingsStore();
|
|
12
|
+
|
|
13
|
+
const currentShowFullScreen = showFullScreen ?? defaultShowFullScreen ?? false;
|
|
14
|
+
const currentShowLocaleButton = showLocaleButton ?? defaultShowLocaleButton ?? true;
|
|
15
|
+
const currentKeepAlive = keepAlive ?? defaultKeepAlive ?? true;
|
|
16
|
+
|
|
17
|
+
return (
|
|
18
|
+
<Form layout="vertical">
|
|
19
|
+
<Form.Item label={t('creek-layout.ActionRender.LayoutSettings.themeColor', '主题色')}>
|
|
20
|
+
<ColorPicker
|
|
21
|
+
value={colorPrimary}
|
|
22
|
+
onChange={(color, hex) => {
|
|
23
|
+
setSettings({ colorPrimary: hex || undefined });
|
|
24
|
+
}}
|
|
25
|
+
allowClear
|
|
26
|
+
/>
|
|
27
|
+
</Form.Item>
|
|
28
|
+
<Form.Item label={t('creek-layout.ActionRender.LayoutSettings.showFullScreen', '展示全屏按钮')}>
|
|
29
|
+
<Switch checked={currentShowFullScreen} onChange={(checked) => setSettings({ showFullScreen: checked })} />
|
|
30
|
+
</Form.Item>
|
|
31
|
+
<Form.Item label={t('creek-layout.ActionRender.LayoutSettings.showLocaleButton', '展示国际化按钮')}>
|
|
32
|
+
<Switch checked={currentShowLocaleButton} onChange={(checked) => setSettings({ showLocaleButton: checked })} />
|
|
33
|
+
</Form.Item>
|
|
34
|
+
<Form.Item label={t('creek-layout.ActionRender.LayoutSettings.keepAlive', '开启页面缓存 (Keep Alive)')}>
|
|
35
|
+
<Switch checked={currentKeepAlive} onChange={(checked) => setSettings({ keepAlive: checked })} />
|
|
36
|
+
</Form.Item>
|
|
37
|
+
</Form>
|
|
38
|
+
);
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
export const LayoutSettings = ({
|
|
42
|
+
defaultShowFullScreen,
|
|
43
|
+
defaultShowLocaleButton,
|
|
44
|
+
defaultKeepAlive,
|
|
45
|
+
}: {
|
|
46
|
+
defaultShowFullScreen?: boolean;
|
|
47
|
+
defaultShowLocaleButton?: boolean;
|
|
48
|
+
defaultKeepAlive?: boolean;
|
|
49
|
+
}) => {
|
|
50
|
+
const t = useT();
|
|
51
|
+
const { drawer } = useApp();
|
|
52
|
+
|
|
53
|
+
const handleOpenSettings = () => {
|
|
54
|
+
drawer.open({
|
|
55
|
+
title: t('creek-layout.ActionRender.LayoutSettings.title', '系统设置'),
|
|
56
|
+
placement: 'right',
|
|
57
|
+
|
|
58
|
+
content: <SettingsForm defaultShowFullScreen={defaultShowFullScreen} defaultShowLocaleButton={defaultShowLocaleButton} defaultKeepAlive={defaultKeepAlive} />,
|
|
59
|
+
});
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
return (
|
|
63
|
+
<Tooltip title={t('creek-layout.ActionRender.LayoutSettings.title', '系统设置')} placement="top">
|
|
64
|
+
<SettingOutlined onClick={handleOpenSettings} />
|
|
65
|
+
</Tooltip>
|
|
66
|
+
);
|
|
67
|
+
};
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { Avatar, Dropdown, DropDownProps, Space } from "antd";
|
|
2
|
+
import { createStyles } from "antd-style";
|
|
3
|
+
|
|
4
|
+
const useStyles = createStyles(({ token}) => ({
|
|
5
|
+
avatarContainer: {
|
|
6
|
+
backgroundColor: token.colorPrimary,
|
|
7
|
+
width: 24,
|
|
8
|
+
height: 24,
|
|
9
|
+
},
|
|
10
|
+
userInfoDropdownOverlay: {
|
|
11
|
+
".ant-dropdown-menu": {
|
|
12
|
+
padding: "8px 0",
|
|
13
|
+
},
|
|
14
|
+
".ant-dropdown-menu-item": {
|
|
15
|
+
".ant-dropdown-menu-item-icon": {
|
|
16
|
+
fontSize: "18px",
|
|
17
|
+
marginRight: "8px",
|
|
18
|
+
},
|
|
19
|
+
},
|
|
20
|
+
},
|
|
21
|
+
}));
|
|
22
|
+
|
|
23
|
+
export interface UserInfoProps {
|
|
24
|
+
name?: React.ReactNode;
|
|
25
|
+
avatar?: string;
|
|
26
|
+
menu?: DropDownProps['menu'];
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export const UserInfo = (props: UserInfoProps) => {
|
|
30
|
+
const { styles } = useStyles();
|
|
31
|
+
const { name, avatar, menu } = props;
|
|
32
|
+
|
|
33
|
+
return (
|
|
34
|
+
<Dropdown arrow placement="bottom" overlayClassName={styles.userInfoDropdownOverlay} menu={menu}>
|
|
35
|
+
<Space size={4} align="center">
|
|
36
|
+
<Avatar className={styles.avatarContainer} src={avatar}>
|
|
37
|
+
{name}
|
|
38
|
+
</Avatar>
|
|
39
|
+
<span>{name}</span>
|
|
40
|
+
</Space>
|
|
41
|
+
</Dropdown>
|
|
42
|
+
);
|
|
43
|
+
};
|