@creekjs/web-components 1.0.5 → 1.0.7
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 +32 -16
- package/README.md +117 -66
- package/dist/creek-config-provider/CreekConfigContext.d.ts +4 -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.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.js +47 -4
- package/dist/creek-config-provider/index.js.map +3 -3
- package/dist/creek-hooks/useApp/index.d.ts +3 -3
- package/dist/creek-keep-alive/index.d.ts +24 -1
- 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.js +3 -1
- package/dist/creek-layout/ActionRender/FullScreen.js.map +2 -2
- package/dist/creek-layout/ActionRender/LayoutSettings.d.ts +5 -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.js.map +2 -2
- package/dist/creek-layout/ActionRender/index.d.ts +1 -0
- package/dist/creek-layout/ActionRender/index.js +3 -0
- package/dist/creek-layout/ActionRender/index.js.map +2 -2
- package/dist/creek-layout/index.d.ts +5 -5
- package/dist/creek-layout/index.js +79 -16
- package/dist/creek-layout/index.js.map +3 -3
- package/dist/creek-layout/useLayoutSettingsStore.d.ts +20 -0
- package/dist/creek-layout/useLayoutSettingsStore.js +45 -0
- package/dist/creek-layout/useLayoutSettingsStore.js.map +7 -0
- package/dist/creek-locale-button/index.d.ts +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.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.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.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.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.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.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.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.js +10 -0
- package/dist/creek-table/hooks/index.js.map +2 -2
- package/dist/creek-table/hooks/useAutoWidthColumns.d.ts +1 -1
- package/dist/creek-table/hooks/useAutoWidthColumns.js +76 -17
- package/dist/creek-table/hooks/useAutoWidthColumns.js.map +2 -2
- package/dist/creek-table/hooks/useEllipsisColumns.d.ts +8 -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.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.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.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.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.js +44 -5
- package/dist/creek-table/hooks/useTableScrollHeight.js.map +2 -2
- package/dist/creek-table/type.d.ts +4 -6
- package/dist/creek-table/type.js.map +1 -1
- package/dist/index.d.ts +4 -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.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.js +49 -0
- package/dist/locales/zh-CN.js.map +7 -0
- package/dist/utils/i18n.d.ts +2 -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 +22 -8
- 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/ActionRender/FullScreen.tsx +10 -6
- package/src/creek-layout/ActionRender/LayoutSettings.tsx +67 -0
- package/src/creek-layout/ActionRender/UserInfo.tsx +1 -1
- package/src/creek-layout/ActionRender/index.tsx +1 -0
- package/src/creek-layout/index.tsx +89 -22
- 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-config-provider/CreekConfigContext.d.ts.map +0 -1
- package/dist/creek-config-provider/index.d.ts.map +0 -1
- package/dist/creek-hooks/index.d.ts.map +0 -1
- package/dist/creek-hooks/useApp/DrawerHelper.d.ts.map +0 -1
- package/dist/creek-hooks/useApp/ModalHelper.d.ts.map +0 -1
- package/dist/creek-hooks/useApp/index.d.ts.map +0 -1
- package/dist/creek-hooks/useApp/types.d.ts.map +0 -1
- package/dist/creek-hooks/useViewportHeight.d.ts.map +0 -1
- package/dist/creek-icon/index.d.ts.map +0 -1
- package/dist/creek-keep-alive/index.d.ts.map +0 -1
- package/dist/creek-layout/ActionRender/FullScreen.d.ts.map +0 -1
- package/dist/creek-layout/ActionRender/UserInfo.d.ts.map +0 -1
- package/dist/creek-layout/ActionRender/index.d.ts.map +0 -1
- package/dist/creek-layout/CollapseButton.d.ts.map +0 -1
- package/dist/creek-layout/Exception/NotFound.d.ts.map +0 -1
- package/dist/creek-layout/Exception/NotFoundPage.d.ts.map +0 -1
- package/dist/creek-layout/Exception/index.d.ts.map +0 -1
- package/dist/creek-layout/index.d.ts.map +0 -1
- package/dist/creek-loading/index.d.ts.map +0 -1
- package/dist/creek-table/SearchTable.d.ts.map +0 -1
- package/dist/creek-table/TableOptionRender.d.ts +0 -9
- package/dist/creek-table/TableOptionRender.d.ts.map +0 -1
- package/dist/creek-table/TableOptionRender.js +0 -74
- package/dist/creek-table/TableOptionRender.js.map +0 -7
- package/dist/creek-table/hooks/index.d.ts.map +0 -1
- package/dist/creek-table/hooks/useAdaptiveToolBar.d.ts.map +0 -1
- package/dist/creek-table/hooks/useAutoWidthColumns.d.ts.map +0 -1
- package/dist/creek-table/hooks/useElementDistance.d.ts.map +0 -1
- package/dist/creek-table/hooks/useTableScrollHeight.d.ts.map +0 -1
- package/dist/creek-table/index.d.ts.map +0 -1
- package/dist/creek-table/toolBarRender.d.ts +0 -5
- package/dist/creek-table/toolBarRender.d.ts.map +0 -1
- package/dist/creek-table/toolBarRender.js +0 -58
- package/dist/creek-table/toolBarRender.js.map +0 -7
- package/dist/creek-table/type.d.ts.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/src/creek-table/TableOptionRender.tsx +0 -57
- package/src/creek-table/toolBarRender.tsx +0 -28
package/package.json
CHANGED
|
@@ -1,19 +1,33 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@creekjs/web-components",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.7",
|
|
4
4
|
"description": "",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
|
-
"scripts": {
|
|
7
|
-
"father:build": "father build",
|
|
8
|
-
"father:dev": "father dev --no-clean"
|
|
9
|
-
},
|
|
10
6
|
"keywords": [],
|
|
11
7
|
"author": "",
|
|
12
8
|
"license": "ISC",
|
|
13
9
|
"dependencies": {
|
|
14
10
|
"@ant-design/icons": "^5.5.1",
|
|
15
|
-
"@ant-design/pro-components": "^2.
|
|
11
|
+
"@ant-design/pro-components": "^2.8.10",
|
|
16
12
|
"antd-style": "^3.7.1",
|
|
17
|
-
"
|
|
13
|
+
"lodash": "^4.17.23",
|
|
14
|
+
"react-resizable": "^3.1.3",
|
|
15
|
+
"react-router-dom": "^7.13.0",
|
|
16
|
+
"zustand": "^5.0.1",
|
|
17
|
+
"@creekjs/i18n": "^1.0.0"
|
|
18
|
+
},
|
|
19
|
+
"devDependencies": {
|
|
20
|
+
"@types/lodash": "^4.17.20",
|
|
21
|
+
"@types/react-resizable": "^3.0.8",
|
|
22
|
+
"react-intl": "^6.0.0",
|
|
23
|
+
"@creekjs/i18n-extract": "^1.0.0"
|
|
24
|
+
},
|
|
25
|
+
"peerDependencies": {
|
|
26
|
+
"react-intl": ">=3.0.0"
|
|
27
|
+
},
|
|
28
|
+
"scripts": {
|
|
29
|
+
"father:build": "father build",
|
|
30
|
+
"father:dev": "father dev --no-clean",
|
|
31
|
+
"i18n-extract": "creek-i18n extract"
|
|
18
32
|
}
|
|
19
|
-
}
|
|
33
|
+
}
|
|
@@ -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
|
+
};
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Avatar, Dropdown, DropDownProps, Space } from "antd";
|
|
2
2
|
import { createStyles } from "antd-style";
|
|
3
3
|
|
|
4
|
-
const useStyles = createStyles(({ token
|
|
4
|
+
const useStyles = createStyles(({ token}) => ({
|
|
5
5
|
avatarContainer: {
|
|
6
6
|
backgroundColor: token.colorPrimary,
|
|
7
7
|
width: 24,
|