@zat-design/sisyphus-react 4.3.6 → 4.4.0

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.
@@ -182,8 +182,6 @@ const ListChangedWrapper = props => {
182
182
  ...filteredRest
183
183
  });
184
184
  }
185
-
186
- // const isFocus = useFocus(contentRef.current);
187
185
  const tipOpenCalc = () => {
188
186
  if (!toolTip) return false;
189
187
 
@@ -76,34 +76,35 @@ export declare const useFormItemProps: (column: FlexibleGroupColumnType, context
76
76
  show?: boolean | ReactiveFunction<any, boolean>;
77
77
  component?: React.ReactNode | ReactiveFunction<any, React.ReactNode>;
78
78
  children?: React.ReactNode | ((form: FormInstance<any>) => React.ReactNode);
79
- trim?: boolean;
80
- normalize?: (value: any, prevValue: any, allValues: import("@rc-component/form/lib/interface").Store) => any;
81
- prefixCls?: string;
82
- trigger?: string;
83
- id?: string;
84
79
  className?: string;
85
- rootClassName?: string;
80
+ hidden?: boolean;
81
+ id?: string;
86
82
  style?: React.CSSProperties;
83
+ onReset?: () => void;
84
+ validateTrigger?: string | false | string[];
85
+ preserve?: boolean;
86
+ prefixCls?: string;
87
+ rootClassName?: string;
88
+ status?: "" | "warning" | "error" | "success" | "validating";
87
89
  isView?: boolean;
88
- status?: "" | "success" | "warning" | "error" | "validating";
90
+ valueType?: import("../../../render/propsType").ProFormValueType;
91
+ viewType?: import("../../../render/propsType").ViewType;
92
+ trim?: boolean;
93
+ normalize?: (value: any, prevValue: any, allValues: import("@rc-component/form/lib/interface").Store) => any;
94
+ getValueProps?: ((value: any) => Record<string, unknown>) & ((value: any) => Record<string, unknown>);
95
+ desensitization?: [number, number] | ReactiveFunction<any, [number, number]>;
89
96
  vertical?: boolean;
90
- hidden?: boolean;
91
- layout?: import("antd/es/form/Form").FormItemLayout;
92
- help?: React.ReactNode;
93
- preserve?: boolean;
94
97
  colon?: boolean;
95
98
  htmlFor?: string;
96
99
  labelAlign?: import("antd/es/form/interface").FormLabelAlign;
97
100
  labelCol?: import("antd").ColProps;
98
101
  getValueFromEvent?: (...args: import("@rc-component/form/lib/interface").EventArgs) => any;
99
102
  shouldUpdate?: import("@rc-component/form/lib/Field").ShouldUpdate<any>;
100
- validateTrigger?: string | false | string[];
103
+ trigger?: string;
101
104
  validateDebounce?: number;
102
105
  valuePropName?: string;
103
- getValueProps?: ((value: any) => Record<string, unknown>) & ((value: any) => Record<string, unknown>);
104
106
  messageVariables?: Record<string, string>;
105
107
  initialValue?: any;
106
- onReset?: () => void;
107
108
  onMetaChange?: (meta: import("@rc-component/form/lib/Field").MetaEvent) => void;
108
109
  isListField?: boolean;
109
110
  isList?: boolean;
@@ -111,22 +112,21 @@ export declare const useFormItemProps: (column: FlexibleGroupColumnType, context
111
112
  hasFeedback?: boolean | {
112
113
  icons: import("antd/es/form/FormItem").FeedbackIcons;
113
114
  };
114
- validateStatus?: "" | "success" | "warning" | "error" | "validating";
115
+ validateStatus?: "" | "warning" | "error" | "success" | "validating";
116
+ layout?: import("antd/es/form/Form").FormItemLayout;
115
117
  wrapperCol?: import("antd").ColProps;
118
+ help?: React.ReactNode;
116
119
  fieldId?: string;
117
- valueType?: import("../../../render/propsType").ProFormValueType;
118
120
  switchValue?: [any, any];
119
121
  viewRender?: (value: any, record: any, { form, index, namePath, }: {
120
122
  [key: string]: any;
121
123
  form: FormInstance<any>;
122
124
  index?: number;
123
125
  }) => string | React.ReactElement<any, any>;
124
- viewType?: import("../../../render/propsType").ViewType;
125
126
  upperCase?: boolean;
126
127
  toISOString?: boolean;
127
128
  toCSTString?: boolean;
128
129
  clearNotShow?: boolean;
129
- desensitization?: [number, number] | ReactiveFunction<any, [number, number]>;
130
130
  name: any;
131
131
  dependencies: any[];
132
132
  tooltip: string | {
@@ -141,7 +141,7 @@ export declare const useFormItemProps: (column: FlexibleGroupColumnType, context
141
141
  * 创建组件属性
142
142
  */
143
143
  export declare const createComponentProps: (column: FlexibleGroupColumnType, formItemProps: any) => {
144
- componentProps: import("lodash").Omit<any, "format" | "valueType" | "switchValue" | "dependNames" | "toISOString" | "toCSTString" | "clearNotShow" | "precision">;
144
+ componentProps: import("lodash").Omit<any, "precision" | "valueType" | "format" | "switchValue" | "dependNames" | "toISOString" | "toCSTString" | "clearNotShow">;
145
145
  formItemTransform: {
146
146
  getValueProps: any;
147
147
  normalize: any;
@@ -25,7 +25,6 @@ export declare const isTrim: (type: string, trim: boolean, configData: any) => a
25
25
  * @returns {}
26
26
  */
27
27
  export declare const isUpperCase: (type: string, upperCase: boolean) => any;
28
- export declare const weedOutProps: (data: Record<string, any>, keys: string[]) => {}[];
29
28
  export declare const diffField: (prevValues: any, curValues: any, names: NamePath[]) => boolean;
30
29
  export declare const splitNameStr: (name: string) => string[][];
31
30
  export declare const filterInternalFields: (values: any, optimize?: boolean) => any;
@@ -75,7 +74,6 @@ export declare const view: (obj: any) => boolean;
75
74
  * @returns 位数
76
75
  */
77
76
  export declare const getDecimalDigits: (num: number) => number;
78
- export declare const deleteForPath: (values: any, namePath: string | number | (string | number)[]) => void;
79
77
  /**
80
78
  * 获取树状结构option
81
79
  * @param treeData 树数据源
@@ -88,10 +86,6 @@ export declare const findOptionByValue: (treeData: any[], value: string | number
88
86
  value?: string;
89
87
  children?: string;
90
88
  }) => any;
91
- /**
92
- * 对比依赖列表中的 值是否变更
93
- */
94
- export declare const equalDependencies: (dependencies: any, prevValues: any, currentValues: any) => any;
95
89
  /** 解析namePath */
96
90
  export declare const parseNamePath: (input: string) => any;
97
91
  /**
@@ -95,21 +95,6 @@ export const isUpperCase = (type, upperCase) => {
95
95
  return result;
96
96
  };
97
97
 
98
- // 剔除otherProps 组成对象返回
99
- export const weedOutProps = (data, keys) => {
100
- const obj = {};
101
- const weedOut = {};
102
- Object.keys(data).forEach(key => {
103
- const included = keys.includes(key);
104
- if (included) {
105
- weedOut[key] = data[key];
106
- } else {
107
- obj[key] = data[key];
108
- }
109
- });
110
- return [obj, weedOut];
111
- };
112
-
113
98
  // 对比字段变化
114
99
  export const diffField = (prevValues, curValues, names) => {
115
100
  return names.some(name => {
@@ -331,24 +316,6 @@ export const getDecimalDigits = num => {
331
316
  const match = num.toString().match(/\.(\d+)/);
332
317
  return match ? match[1].length : 0;
333
318
  };
334
- export const deleteForPath = (values, namePath) => {
335
- if (Array.isArray(namePath)) {
336
- let current = values;
337
- namePath.every((nameItem, index) => {
338
- if (index === namePath.length - 1) {
339
- delete current[nameItem];
340
- return false;
341
- }
342
- if (typeof current[nameItem] !== 'object') {
343
- return false;
344
- }
345
- current = current[nameItem];
346
- return true;
347
- });
348
- return;
349
- }
350
- delete values[namePath];
351
- };
352
319
 
353
320
  /**
354
321
  * 获取树状结构option
@@ -374,20 +341,6 @@ export const findOptionByValue = (treeData, value, fieldNames) => {
374
341
  }
375
342
  };
376
343
 
377
- /**
378
- * 对比依赖列表中的 值是否变更
379
- */
380
- export const equalDependencies = (dependencies, prevValues, currentValues) => {
381
- if (dependencies?.length) {
382
- return dependencies.some(namePath => {
383
- const prevValue = _get(prevValues, namePath);
384
- const currentValue = _get(currentValues, namePath);
385
- return prevValue !== currentValue;
386
- });
387
- }
388
- return false;
389
- };
390
-
391
344
  /** 解析namePath */
392
345
  export const parseNamePath = input => {
393
346
  const parts = input.split('_');
@@ -0,0 +1,25 @@
1
+ import type { IframeConfig } from '../../../propTypes';
2
+ export interface UseIframeRouteOptions {
3
+ /** iframe 嵌入配置 */
4
+ config: IframeConfig | undefined;
5
+ /** 命中路由后调用的 addTab(由外部 tabsInstance.addTab 注入) */
6
+ addTab: (params: {
7
+ code: string;
8
+ name: string;
9
+ extra?: Record<string, any>;
10
+ }) => void;
11
+ }
12
+ export interface UseIframeRouteReturn {
13
+ /** 当前是否处于 iframe 嵌入环境(通过 URL query `?isIframe` 判定,大小写不敏感) */
14
+ isIframe: boolean;
15
+ /** 是否启用纯净渲染(隐藏 Header/Menu/TabsBar);非 iframe 或未配置 iframe 时始终为 false */
16
+ pure: boolean;
17
+ }
18
+ /**
19
+ * iframe 嵌入模式 hook
20
+ * - 通过 URL query `?isIframe`(大小写不敏感)判定是否处于 iframe 嵌入场景
21
+ * - 命中时按 routes 静态路径匹配 window.location.pathname,调用 addTab
22
+ * - query string 自动作为 extra 透传给业务组件
23
+ * - 暴露 pure 标志供外部 layout 决定渲染范围
24
+ */
25
+ export declare const useIframeRoute: (options: UseIframeRouteOptions) => UseIframeRouteReturn;
@@ -0,0 +1,63 @@
1
+ import { useEffect, useMemo, useRef } from 'react';
2
+ const detectIframe = () => {
3
+ if (typeof window === 'undefined') return false;
4
+ // URL 上带 ?isIframe(大小写不敏感)即视为 iframe 嵌入
5
+ const params = new URLSearchParams(window.location.search);
6
+ for (const key of params.keys()) {
7
+ if (key.toLowerCase() === 'isiframe') return true;
8
+ }
9
+ return false;
10
+ };
11
+ const stripSlashes = s => s.replace(/^\/+|\/+$/g, '');
12
+
13
+ /**
14
+ * iframe 嵌入模式 hook
15
+ * - 通过 URL query `?isIframe`(大小写不敏感)判定是否处于 iframe 嵌入场景
16
+ * - 命中时按 routes 静态路径匹配 window.location.pathname,调用 addTab
17
+ * - query string 自动作为 extra 透传给业务组件
18
+ * - 暴露 pure 标志供外部 layout 决定渲染范围
19
+ */
20
+ export const useIframeRoute = options => {
21
+ const {
22
+ config,
23
+ addTab
24
+ } = options;
25
+ const isIframe = useMemo(detectIframe, []);
26
+ // pure 仅在显式配置 iframe 时才考虑;非 iframe 嵌入(URL 无 ?isIframe)始终为 false
27
+ const pure = isIframe && config != null && config.pure !== false;
28
+
29
+ // 用 ref 持有最新的 addTab/config,避免依赖变化触发副作用重跑
30
+ const addTabRef = useRef(addTab);
31
+ addTabRef.current = addTab;
32
+ const configRef = useRef(config);
33
+ configRef.current = config;
34
+ useEffect(() => {
35
+ if (!isIframe) return;
36
+ const cfg = configRef.current;
37
+ if (!cfg?.routes?.length) return;
38
+ const {
39
+ pathname,
40
+ search
41
+ } = window.location;
42
+ const searchParams = {};
43
+ new URLSearchParams(search).forEach((value, key) => {
44
+ searchParams[key] = value;
45
+ });
46
+ const normalizedPath = stripSlashes(pathname);
47
+ for (let i = 0; i < cfg.routes.length; i += 1) {
48
+ const route = cfg.routes[i];
49
+ if (stripSlashes(route.path) === normalizedPath) {
50
+ addTabRef.current({
51
+ code: route.code,
52
+ name: route.name,
53
+ extra: searchParams
54
+ });
55
+ break;
56
+ }
57
+ }
58
+ }, [isIframe]);
59
+ return {
60
+ isIframe,
61
+ pure
62
+ };
63
+ };
@@ -13,7 +13,8 @@ export const useTabsState = options => {
13
13
  const {
14
14
  initialState = {},
15
15
  config,
16
- dataSource
16
+ dataSource,
17
+ cacheEnabled = true
17
18
  } = options;
18
19
 
19
20
  // 合并默认配置
@@ -40,7 +41,7 @@ export const useTabsState = options => {
40
41
  } = useTabsCache({
41
42
  cacheKey: finalConfig.cacheKey,
42
43
  storage: finalConfig.storage,
43
- enabled: true
44
+ enabled: cacheEnabled
44
45
  });
45
46
 
46
47
  // 初始化标记,确保只在初始化后保存缓存
@@ -140,7 +141,8 @@ export const useTabsState = options => {
140
141
  */
141
142
  const addTab = useCallback((menuItem, options) => {
142
143
  const {
143
- forceNew = false
144
+ forceNew = false,
145
+ silent = false
144
146
  } = options || {};
145
147
  setState(prevState => {
146
148
  if (!canOpenAsTab(menuItem)) return prevState;
@@ -177,13 +179,16 @@ export const useTabsState = options => {
177
179
  const tabId = generateTabId(menuItem, existingIds);
178
180
  const newTab = createTabFromMenu(menuItem, prevState.newTabIndex);
179
181
  newTab.id = tabId;
182
+
183
+ // silent=true 时,若已有 active 则保留;无 active(首次进入)仍激活以保留默认行为
184
+ const shouldActivate = !silent || prevState.activeKey === '';
180
185
  return {
181
186
  ...prevState,
182
187
  tabsList: [...prevState.tabsList, newTab],
183
- activeKey: tabId,
184
- activeTabInfo: newTab,
188
+ activeKey: shouldActivate ? tabId : prevState.activeKey,
189
+ activeTabInfo: shouldActivate ? newTab : prevState.activeTabInfo,
185
190
  newTabIndex: prevState.newTabIndex + 1,
186
- activeComponent: menuItem.code || tabId
191
+ activeComponent: shouldActivate ? menuItem.code || tabId : prevState.activeComponent
187
192
  };
188
193
  });
189
194
  }, [finalConfig]);
@@ -1,6 +1,7 @@
1
1
  import { useCallback, useMemo, forwardRef, useImperativeHandle, useState, useEffect, useRef } from 'react';
2
2
  import { createPortal } from 'react-dom';
3
3
  import { useTabsState } from "./hooks/useTabsState";
4
+ import { useIframeRoute } from "./hooks/useIframeRoute";
4
5
  import { flattenMenuData } from "./utils";
5
6
  import { TabItemComponent } from "./components/TabItem";
6
7
  import { TabsHeader } from "./components/TabsHeader";
@@ -16,8 +17,23 @@ const TabsManager = /*#__PURE__*/forwardRef(({
16
17
  dataSource,
17
18
  originalOnMenuClick,
18
19
  tabsBarContainer,
19
- onTabsChange
20
+ onTabsChange,
21
+ onIframePureChange
20
22
  }, ref) => {
23
+ // iframe 模式检测(在 useTabsState 之前,因为 cacheEnabled 依赖 pure)
24
+ // addTab 由后续 useTabsState 提供,此处先用占位,后面用 ref 透传
25
+ const iframeAddTabRef = useRef(() => {});
26
+ const {
27
+ pure: isIframePure
28
+ } = useIframeRoute({
29
+ config: config?.iframe,
30
+ addTab: params => iframeAddTabRef.current(params)
31
+ });
32
+
33
+ // 通知父级(ProLayout)pure 状态变化
34
+ useEffect(() => {
35
+ onIframePureChange?.(isIframePure);
36
+ }, [isIframePure, onIframePureChange]);
21
37
  const {
22
38
  state,
23
39
  isInitialized,
@@ -32,9 +48,28 @@ const TabsManager = /*#__PURE__*/forwardRef(({
32
48
  reorderTabs
33
49
  } = useTabsState({
34
50
  config,
35
- dataSource
51
+ dataSource,
52
+ cacheEnabled: !isIframePure
36
53
  });
37
54
 
55
+ // 桥接 useIframeRoute 的 { code, name, extra } → addTab(menuItem)
56
+ iframeAddTabRef.current = params => {
57
+ const sourceMenus = Array.isArray(dataSource?.menus) ? flattenMenuData(dataSource.menus) : [];
58
+ const matchedMenu = sourceMenus.find(item => item.code === params.code);
59
+ const menuItem = matchedMenu ? {
60
+ ...matchedMenu,
61
+ name: params.name || matchedMenu.name,
62
+ extra: params.extra ?? matchedMenu.extra
63
+ } : {
64
+ id: Date.now(),
65
+ code: params.code,
66
+ name: params.name,
67
+ url: `/${params.code}`,
68
+ extra: params.extra
69
+ };
70
+ addTab(menuItem);
71
+ };
72
+
38
73
  // 记录已访问过的 Tab ID,用于懒加载
39
74
  const [visitedTabIds, setVisitedTabIds] = useState(new Set());
40
75
 
@@ -60,12 +95,24 @@ const TabsManager = /*#__PURE__*/forwardRef(({
60
95
  const addTabRef = useRef(addTab);
61
96
  addTabRef.current = addTab;
62
97
  useEffect(() => {
98
+ // iframe pure 模式下不应用 fixed(嵌入页面只显示路由命中的单个 tab)
99
+ if (isIframePure) return;
63
100
  if (!isInitialized || !config.fixed || config.fixed.length === 0) return;
64
101
  const sourceMenus = Array.isArray(dataSource?.menus) ? flattenMenuData(dataSource.menus) : [];
65
102
 
66
103
  // menus 为空说明异步数据尚未返回,等 dataSource 变化后重跑,避免用 code 创建占位 tab
67
104
  if (sourceMenus.length === 0) return;
68
105
  config.fixed.forEach(code => {
106
+ // 已存在(如缓存恢复):仅校正 closable,不调 addTab 以保留缓存中的 activeKey
107
+ const existing = state.tabsList.find(tab => tab.menuItem?.code === code || tab.code === code);
108
+ if (existing) {
109
+ if (existing.closable !== false) {
110
+ updateTab(existing.id, {
111
+ closable: false
112
+ });
113
+ }
114
+ return;
115
+ }
69
116
  const matchedMenu = sourceMenus.find(item => item.code === code);
70
117
  const menuItem = matchedMenu ? {
71
118
  ...matchedMenu,
@@ -77,9 +124,12 @@ const TabsManager = /*#__PURE__*/forwardRef(({
77
124
  url: `/${code}`,
78
125
  closable: false
79
126
  };
80
- addTabRef.current(menuItem);
127
+ // silent: 已有 active(如 iframe 路由命中)时不抢 active;无 active 时仍激活以保留默认行为
128
+ addTabRef.current(menuItem, {
129
+ silent: true
130
+ });
81
131
  });
82
- }, [isInitialized, config.fixed, dataSource]);
132
+ }, [isInitialized, config.fixed, dataSource, state.tabsList, updateTab, isIframePure]);
83
133
 
84
134
  // 处理菜单点击 - 拦截原有的菜单点击逻辑
85
135
  const handleMenuClick = useCallback(params => {
@@ -191,7 +241,8 @@ const TabsManager = /*#__PURE__*/forwardRef(({
191
241
  };
192
242
 
193
243
  // TabsHeader 节点:有 tabsBarContainer 时用 Portal 渲染到外部,否则原位 fallback
194
- const tabsHeaderNode = state.tabsList.length > 0 ? /*#__PURE__*/_jsx(TabsHeader, {
244
+ // iframe pure 模式下完全不渲染 tab bar
245
+ const tabsHeaderNode = state.tabsList.length > 0 && !isIframePure ? /*#__PURE__*/_jsx(TabsHeader, {
195
246
  activeKey: state.activeKey || undefined,
196
247
  tabsItems: tabsItems,
197
248
  onTabChange: handleTabChange,
@@ -18,6 +18,8 @@ export interface TabsManagerProps {
18
18
  tabsBarContainer?: HTMLElement | null;
19
19
  /** Tab 有无变化回调,供 ProLayout 控制 wrapper 动画 */
20
20
  onTabsChange?: (hasTabs: boolean) => void;
21
+ /** iframe pure 模式变化回调,供 ProLayout 决定是否隐藏外壳 */
22
+ onIframePureChange?: (pure: boolean) => void;
21
23
  }
22
24
  export interface TabContextMenuProps {
23
25
  tabId: string;
@@ -47,6 +49,8 @@ export interface UseTabsStateOptions {
47
49
  config: TabsManagerProps['config'];
48
50
  /** 数据源 */
49
51
  dataSource?: TabsManagerProps['dataSource'];
52
+ /** 是否启用缓存(读+写),默认 true。iframe pure 模式下传 false */
53
+ cacheEnabled?: boolean;
50
54
  }
51
55
  export interface UseTabsStateReturn {
52
56
  /** 当前状态 */
@@ -27,10 +27,6 @@ export declare const handleExternalOpen: (menuItem: MenusType) => void;
27
27
  * @returns 如果超出限制返回 true,否则返回 false
28
28
  */
29
29
  export declare const checkTabLimit: (currentTabs: TabItem[], maxTabs?: number | null) => boolean;
30
- /**
31
- * 移除最旧的标签页(根据访问时间或创建时间)
32
- */
33
- export declare const removeOldestTab: (tabs: TabItem[]) => TabItem[];
34
30
  /**
35
31
  * 获取右侧标签页
36
32
  */
@@ -44,10 +40,6 @@ export declare const flattenMenuData: (menus?: MenusType[]) => MenusType[];
44
40
  * 只有当菜单项没有子菜单或子菜单为空时,才认为是叶子节点
45
41
  */
46
42
  export declare const isLeafMenuItem: (menuItem: MenusType) => boolean;
47
- /**
48
- * 判断菜单项是否为根节点
49
- */
50
- export declare const isRootMenuItem: (menuItem: MenusType) => boolean;
51
43
  /**
52
44
  * 判断菜单项是否可以在 Tabs 模式下打开
53
45
  * 规则:只有叶子节点(无 children)才可打开
@@ -72,21 +72,6 @@ export const checkTabLimit = (currentTabs, maxTabs) => {
72
72
  return currentTabs.length >= maxTabs;
73
73
  };
74
74
 
75
- /**
76
- * 移除最旧的标签页(根据访问时间或创建时间)
77
- */
78
- export const removeOldestTab = tabs => {
79
- if (tabs.length === 0) return tabs;
80
-
81
- // 找到可关闭的最旧标签页
82
- const closableTabs = tabs.filter(tab => tab.closable);
83
- if (closableTabs.length === 0) return tabs;
84
-
85
- // 移除第一个可关闭的标签页
86
- const oldestTab = closableTabs[0];
87
- return tabs.filter(tab => tab.id !== oldestTab.id);
88
- };
89
-
90
75
  /**
91
76
  * 获取右侧标签页
92
77
  */
@@ -122,17 +107,6 @@ export const isLeafMenuItem = menuItem => {
122
107
  return !children || Array.isArray(children) && children.length === 0;
123
108
  };
124
109
 
125
- /**
126
- * 判断菜单项是否为根节点
127
- */
128
- export const isRootMenuItem = menuItem => {
129
- if (!menuItem) return false;
130
- if (menuItem.parentId === null || menuItem.parentId === undefined) {
131
- return true;
132
- }
133
- return Array.isArray(menuItem.keyIdPath) && menuItem.keyIdPath.length === 1;
134
- };
135
-
136
110
  /**
137
111
  * 判断菜单项是否可以在 Tabs 模式下打开
138
112
  * 规则:只有叶子节点(无 children)才可打开
@@ -55,6 +55,8 @@ const ProLayout = props => {
55
55
  // Tab 栏 Portal 挂载目标及动画状态
56
56
  const [tabsBarEl, setTabsBarEl] = useState(null);
57
57
  const [hasTabs, setHasTabs] = useState(false);
58
+ // TabsManager 通过 onIframePureChange 报告的 pure 状态(iframe 嵌入 + 默认纯净渲染)
59
+ const [isIframePure, setIsIframePure] = useState(false);
58
60
  const [{
59
61
  notice,
60
62
  menus,
@@ -135,6 +137,7 @@ const ProLayout = props => {
135
137
  originalOnMenuClick: onMenuClick,
136
138
  tabsBarContainer: tabsBarEl,
137
139
  onTabsChange: setHasTabs,
140
+ onIframePureChange: setIsIframePure,
138
141
  children: children
139
142
  });
140
143
  };
@@ -233,7 +236,18 @@ const ProLayout = props => {
233
236
  onMenuClick: handleMenuClick,
234
237
  appsOptions
235
238
  };
236
- const layoutDom = /*#__PURE__*/_jsxs("div", {
239
+ const layoutDom = isIframePure ?
240
+ /*#__PURE__*/
241
+ /* iframe pure 模式:仅渲染 TabsManager 内容,不渲染 Header/Notice/Menu/tabsBar */
242
+ _jsx("div", {
243
+ className: classNames({
244
+ 'pro-layout': true,
245
+ 'pro-layout-light': true,
246
+ 'pro-layout-iframe-pure': true,
247
+ [`${className}`]: className
248
+ }),
249
+ children: renderContent()
250
+ }) : /*#__PURE__*/_jsxs("div", {
237
251
  className: classNames({
238
252
  'pro-layout': true,
239
253
  'pro-layout-light': true,
@@ -370,6 +370,12 @@ export interface AddTabOptions {
370
370
  * @description 是否强制创建新标签页(忽略已存在的相同标签页),默认 false
371
371
  */
372
372
  forceNew?: boolean;
373
+ /**
374
+ * @description 是否静默追加:true 时新建 tab 不切换 active;当前已有 active 时保留原 active。
375
+ * 列表为空(activeKey 为空)时仍会激活,以保留首次进入默认激活的行为。
376
+ * 用于 fixed 声明式注入等不应抢用户 active 的场景。默认 false。
377
+ */
378
+ silent?: boolean;
373
379
  }
374
380
  /**
375
381
  * @description 更新标签页的字段(仅允许更新展示与扩展数据)
@@ -426,6 +432,51 @@ export interface ProLayoutTabsInstance {
426
432
  */
427
433
  getTabInfo: () => TabsInfoResult;
428
434
  }
435
+ /**
436
+ * @description iframe 路由表配置项
437
+ */
438
+ export interface IframeRoute {
439
+ /**
440
+ * @description 静态路径,与 `window.location.pathname` 完全匹配(忽略首尾斜杠)。
441
+ * 不支持 `:param` 动态片段;动态参数通过 query string 传递,
442
+ * 自动作为 props 透传给业务组件。
443
+ * 业务方需保证 URL query 字段名与业务组件 props 名一致。
444
+ * @example '/wjs/policy/view'
445
+ * 配合 URL: `/wjs/policy/view?taskId=123&policyId=456`
446
+ */
447
+ path: string;
448
+ /**
449
+ * @description 命中后用于解析 activeComponent 的业务 code
450
+ */
451
+ code: string;
452
+ /**
453
+ * @description 命中后的标签名称
454
+ */
455
+ name: string;
456
+ }
457
+ /**
458
+ * @description iframe 嵌入配置
459
+ *
460
+ * **触发条件**:URL query 上带 `?isIframe`(大小写不敏感,值不限,只看 key 是否存在)。
461
+ * 第三方系统通过在 iframe src 上拼 `?isIframe=1` 来触发组件库的嵌入模式。
462
+ *
463
+ * 未带该 query 时(独立访问),即使配置了 `tabs.iframe`,组件库也完全不读取,
464
+ * 维持完整 Layout 渲染。
465
+ */
466
+ export interface IframeConfig {
467
+ /**
468
+ * @description 路由表:按 URL pathname 顺序匹配,命中后自动 addTab
469
+ * - URL query 自动合并进 extra,作为业务组件 props 透传
470
+ */
471
+ routes: IframeRoute[];
472
+ /**
473
+ * @description 嵌入时是否使用纯净渲染(只显示内容区域)
474
+ * - true (默认): 隐藏 Header / Menu / TabsBar,仅渲染当前 tab 内容
475
+ * - false: 完整 layout 渲染
476
+ * @default true
477
+ */
478
+ pure?: boolean;
479
+ }
429
480
  export interface TabsConfig {
430
481
  max?: number;
431
482
  storage?: TabsStorageStrategy;
@@ -461,6 +512,11 @@ export interface TabsConfig {
461
512
  * @example fixed: ['Workspace', 'PolicyInput']
462
513
  */
463
514
  fixed?: string[];
515
+ /**
516
+ * @description iframe 嵌入配置,允许第三方系统通过 URL 定位到具体 tab
517
+ * 仅在 URL 带 `?isIframe`(大小写不敏感)时生效
518
+ */
519
+ iframe?: IframeConfig;
464
520
  }
465
521
  export type ProLayoutMode = 'normal' | 'tabs';
466
522
  export interface ProLayoutTabsProps extends ProLayoutBaseProps {
@@ -30,6 +30,20 @@
30
30
  * @description ProLayout 标签页实例接口(类似 FormInstance)
31
31
  */
32
32
 
33
+ /**
34
+ * @description iframe 路由表配置项
35
+ */
36
+
37
+ /**
38
+ * @description iframe 嵌入配置
39
+ *
40
+ * **触发条件**:URL query 上带 `?isIframe`(大小写不敏感,值不限,只看 key 是否存在)。
41
+ * 第三方系统通过在 iframe src 上拼 `?isIframe=1` 来触发组件库的嵌入模式。
42
+ *
43
+ * 未带该 query 时(独立访问),即使配置了 `tabs.iframe`,组件库也完全不读取,
44
+ * 维持完整 Layout 渲染。
45
+ */
46
+
33
47
  export const isTabsMode = props => props.mode === 'tabs';
34
48
  export const validateTabsProps = (mode = 'normal', tabs) => {
35
49
  if (mode === 'tabs' && !tabs) {
@@ -361,6 +361,11 @@
361
361
  }
362
362
  }
363
363
 
364
+ // iframe 嵌入 pure 模式:背景固定为白色,覆盖默认 #f7f9fd 与外部全局样式
365
+ &.pro-layout-iframe-pure {
366
+ background: #fff;
367
+ }
368
+
364
369
  .pro-layout-row-pure {
365
370
  .pro-layout-content {
366
371
  margin: 0 !important;
@@ -18,4 +18,3 @@ export declare const isEmpty: (value: any, emptyValue?: string) => boolean;
18
18
  * @returns {boolean} 如果内容溢出返回 true,否则返回 false
19
19
  */
20
20
  export declare const isEllipsisActive: (element: Element) => boolean;
21
- export declare const useFocus: (element: Element) => boolean;