@zat-design/sisyphus-react 4.6.0-beta.1 → 4.6.0-beta.3
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/ProLayout/components/TabsManager/hooks/useTabsState.js +20 -12
- package/es/ProLayout/components/TabsManager/hooks/useTabsUrlSync.js +29 -16
- package/es/ProLayout/components/TabsManager/propTypes.d.ts +2 -2
- package/es/ProLayout/components/TabsManager/utils/index.d.ts +5 -0
- package/es/ProLayout/components/TabsManager/utils/index.js +47 -2
- package/es/ProLayout/propTypes.d.ts +13 -2
- package/es/ProSelect/index.js +23 -8
- package/es/ProSelect/utils/index.d.ts +3 -2
- package/es/ProSelect/utils/index.js +19 -9
- package/es/ProTable/components/RenderTabs/index.js +6 -2
- package/package.json +1 -1
|
@@ -15,6 +15,7 @@ import {
|
|
|
15
15
|
getMenuRoute,
|
|
16
16
|
hasRelationKey,
|
|
17
17
|
findExistingTab,
|
|
18
|
+
getComparableTabUrl,
|
|
18
19
|
normalizeTabUrl,
|
|
19
20
|
isSafeExternalUrl,
|
|
20
21
|
isSafeHttpUrl,
|
|
@@ -30,6 +31,7 @@ const useTabsState = (options) => {
|
|
|
30
31
|
const finalConfig = { ...DEFAULT_TABS_CONFIG, ...config };
|
|
31
32
|
const urlSyncEnabled = finalConfig.urlSync === true || typeof finalConfig.urlSync === "object" && finalConfig.urlSync !== null;
|
|
32
33
|
const urlSyncIdentity = typeof finalConfig.urlSync === "object" && finalConfig.urlSync !== null ? finalConfig.urlSync.identity || "url" : "url";
|
|
34
|
+
const urlSyncBasename = typeof finalConfig.urlSync === "object" && finalConfig.urlSync !== null ? finalConfig.urlSync.basename : void 0;
|
|
33
35
|
const [state, setState] = useState(() => ({
|
|
34
36
|
tabsList: [],
|
|
35
37
|
activeKey: "",
|
|
@@ -71,8 +73,11 @@ const useTabsState = (options) => {
|
|
|
71
73
|
const normalizedUrl = normalizeTabUrl(tab.url);
|
|
72
74
|
if (!normalizedUrl)
|
|
73
75
|
return;
|
|
76
|
+
const comparableUrl = getComparableTabUrl(normalizedUrl, urlSyncBasename);
|
|
77
|
+
if (!comparableUrl)
|
|
78
|
+
return;
|
|
74
79
|
const relationKey = tab.relationKey ?? ((_a2 = tab.menuItem) == null ? void 0 : _a2.relationKey);
|
|
75
|
-
const identity = urlSyncIdentity === "relationKey" && hasRelationKey(relationKey) ? `relationKey:${typeof relationKey}:${String(relationKey)}` : `url:${
|
|
80
|
+
const identity = urlSyncIdentity === "relationKey" && hasRelationKey(relationKey) ? `relationKey:${typeof relationKey}:${String(relationKey)}` : `url:${comparableUrl}`;
|
|
76
81
|
const retainedTab = retainedTabByIdentity.get(identity);
|
|
77
82
|
if (retainedTab) {
|
|
78
83
|
retainedTabByIdentity.set(identity, { ...retainedTab, url: normalizedUrl });
|
|
@@ -93,7 +98,7 @@ const useTabsState = (options) => {
|
|
|
93
98
|
setState(restoredState);
|
|
94
99
|
}
|
|
95
100
|
setIsInitialized(true);
|
|
96
|
-
}, [restoreFromCache, urlSyncEnabled, urlSyncIdentity]);
|
|
101
|
+
}, [restoreFromCache, urlSyncEnabled, urlSyncIdentity, urlSyncBasename]);
|
|
97
102
|
const lastActiveKeyRef = useRef("");
|
|
98
103
|
const shouldSkipSaveRef = useRef(false);
|
|
99
104
|
const initialTabsAppliedRef = useRef(false);
|
|
@@ -132,7 +137,8 @@ const useTabsState = (options) => {
|
|
|
132
137
|
if (urlSyncEnabled || !forceNew) {
|
|
133
138
|
const existingTab = findExistingTab(state.tabsList, menuItem, {
|
|
134
139
|
urlSync: urlSyncEnabled,
|
|
135
|
-
identity: urlSyncIdentity
|
|
140
|
+
identity: urlSyncIdentity,
|
|
141
|
+
basename: urlSyncBasename
|
|
136
142
|
});
|
|
137
143
|
if (existingTab) {
|
|
138
144
|
return true;
|
|
@@ -143,7 +149,7 @@ const useTabsState = (options) => {
|
|
|
143
149
|
}
|
|
144
150
|
return true;
|
|
145
151
|
},
|
|
146
|
-
[state.tabsList, finalConfig, urlSyncEnabled, urlSyncIdentity]
|
|
152
|
+
[state.tabsList, finalConfig, urlSyncEnabled, urlSyncIdentity, urlSyncBasename]
|
|
147
153
|
);
|
|
148
154
|
const addTab = useCallback(
|
|
149
155
|
(menuItem, options2) => {
|
|
@@ -170,11 +176,12 @@ const useTabsState = (options) => {
|
|
|
170
176
|
const resolvedMenuItem = normalizedUrl ? { ...menuItem, url: normalizedUrl, redirectUrl: void 0 } : menuItem;
|
|
171
177
|
const existingTab = urlSyncEnabled || !forceNew ? findExistingTab(prevState.tabsList, resolvedMenuItem, {
|
|
172
178
|
urlSync: urlSyncEnabled,
|
|
173
|
-
identity: urlSyncIdentity
|
|
179
|
+
identity: urlSyncIdentity,
|
|
180
|
+
basename: urlSyncBasename
|
|
174
181
|
}) : void 0;
|
|
175
182
|
if (existingTab) {
|
|
176
183
|
const shouldFixClosable = resolvedMenuItem.closable === false && existingTab.closable !== false;
|
|
177
|
-
const shouldUpdateUrl = urlSyncIdentity === "relationKey" && normalizedUrl !== void 0 && existingTab.url !== normalizedUrl;
|
|
184
|
+
const shouldUpdateUrl = urlSyncIdentity === "relationKey" && normalizedUrl !== void 0 && getComparableTabUrl(existingTab.url, urlSyncBasename) !== getComparableTabUrl(normalizedUrl, urlSyncBasename);
|
|
178
185
|
const correctedTab = shouldFixClosable || shouldUpdateUrl ? {
|
|
179
186
|
...existingTab,
|
|
180
187
|
...shouldFixClosable ? { closable: false } : {},
|
|
@@ -216,7 +223,7 @@ const useTabsState = (options) => {
|
|
|
216
223
|
};
|
|
217
224
|
});
|
|
218
225
|
},
|
|
219
|
-
[finalConfig, urlSyncEnabled, urlSyncIdentity]
|
|
226
|
+
[finalConfig, urlSyncEnabled, urlSyncIdentity, urlSyncBasename]
|
|
220
227
|
);
|
|
221
228
|
useEffect(() => {
|
|
222
229
|
const initialTabs = finalConfig.initialTabs;
|
|
@@ -273,21 +280,22 @@ const useTabsState = (options) => {
|
|
|
273
280
|
(tabId) => {
|
|
274
281
|
setState((prevState) => {
|
|
275
282
|
var _a;
|
|
276
|
-
const
|
|
283
|
+
const targetTabId = tabId ?? prevState.activeKey;
|
|
284
|
+
const tabToRemove = prevState.tabsList.find((tab) => tab.id === targetTabId);
|
|
277
285
|
if (!tabToRemove || !tabToRemove.closable) {
|
|
278
286
|
return prevState;
|
|
279
287
|
}
|
|
280
|
-
const newTabs = prevState.tabsList.filter((tab) => tab.id !==
|
|
288
|
+
const newTabs = prevState.tabsList.filter((tab) => tab.id !== targetTabId);
|
|
281
289
|
let newActiveKey = prevState.activeKey;
|
|
282
290
|
let newActiveTabInfo = prevState.activeTabInfo;
|
|
283
291
|
let newActiveComponent = prevState.activeComponent;
|
|
284
|
-
if (prevState.activeKey ===
|
|
285
|
-
const currentIndex = prevState.tabsList.findIndex((tab) => tab.id ===
|
|
292
|
+
if (prevState.activeKey === targetTabId && newTabs.length > 0) {
|
|
293
|
+
const currentIndex = prevState.tabsList.findIndex((tab) => tab.id === targetTabId);
|
|
286
294
|
const newActiveIndex = Math.min(currentIndex, newTabs.length - 1);
|
|
287
295
|
newActiveTabInfo = newTabs[newActiveIndex];
|
|
288
296
|
newActiveKey = newActiveTabInfo.id;
|
|
289
297
|
newActiveComponent = ((_a = newActiveTabInfo.menuItem) == null ? void 0 : _a.code) || newActiveTabInfo.id;
|
|
290
|
-
} else if (prevState.activeKey ===
|
|
298
|
+
} else if (prevState.activeKey === targetTabId) {
|
|
291
299
|
newActiveKey = "";
|
|
292
300
|
newActiveTabInfo = void 0;
|
|
293
301
|
newActiveComponent = "";
|
|
@@ -3,6 +3,8 @@ import * as ReactRouterDom from "react-router-dom";
|
|
|
3
3
|
import {
|
|
4
4
|
canOpenAsTab,
|
|
5
5
|
flattenMenuData,
|
|
6
|
+
getBrowserTabUrl,
|
|
7
|
+
getComparableTabUrl,
|
|
6
8
|
getCurrentTabUrl,
|
|
7
9
|
getMenuRoute,
|
|
8
10
|
getTabUrlPathname,
|
|
@@ -92,6 +94,7 @@ const useTabsUrlSync = ({
|
|
|
92
94
|
const navigator = navigationContext == null ? void 0 : navigationContext.navigator;
|
|
93
95
|
const enabled = config.urlSync === true || typeof config.urlSync === "object" && config.urlSync !== null;
|
|
94
96
|
const historyMode = typeof config.urlSync === "object" ? config.urlSync.history || "replace" : "replace";
|
|
97
|
+
const basename = typeof config.urlSync === "object" ? config.urlSync.basename : void 0;
|
|
95
98
|
const bootstrappedRef = useRef(false);
|
|
96
99
|
const writingUrlRef = useRef(false);
|
|
97
100
|
const preserveUrlInteractionRef = useRef(null);
|
|
@@ -156,11 +159,14 @@ const useTabsUrlSync = ({
|
|
|
156
159
|
if (!enabled || !isInitialized || typeof window === "undefined")
|
|
157
160
|
return;
|
|
158
161
|
const currentUrl = normalizeTabUrl(locationChange.url);
|
|
159
|
-
|
|
162
|
+
const comparableCurrentUrl = getComparableTabUrl(currentUrl, basename);
|
|
163
|
+
if (!currentUrl || !comparableCurrentUrl)
|
|
160
164
|
return;
|
|
161
165
|
if (handledLocationSequenceRef.current === locationChange.sequence)
|
|
162
166
|
return;
|
|
163
|
-
const exactTab = state.tabsList.find(
|
|
167
|
+
const exactTab = state.tabsList.find(
|
|
168
|
+
(tab) => getComparableTabUrl(tab.url, basename) === comparableCurrentUrl
|
|
169
|
+
);
|
|
164
170
|
if (exactTab) {
|
|
165
171
|
handledLocationSequenceRef.current = locationChange.sequence;
|
|
166
172
|
bootstrappedRef.current = true;
|
|
@@ -170,16 +176,16 @@ const useTabsUrlSync = ({
|
|
|
170
176
|
}
|
|
171
177
|
return;
|
|
172
178
|
}
|
|
173
|
-
const currentPathname = getTabUrlPathname(
|
|
179
|
+
const currentPathname = getTabUrlPathname(comparableCurrentUrl);
|
|
174
180
|
const sourceMenus = getSourceMenus(dataSource);
|
|
175
181
|
const activeTab = state.tabsList.find((tab) => tab.id === state.activeKey);
|
|
176
182
|
const activeCode = ((_a = activeTab == null ? void 0 : activeTab.menuItem) == null ? void 0 : _a.code) || (activeTab == null ? void 0 : activeTab.code);
|
|
177
183
|
const activeSourceMenu = activeCode ? sourceMenus.find((item) => item.code === activeCode) : void 0;
|
|
178
184
|
const activeMenuItem = activeSourceMenu || (activeTab == null ? void 0 : activeTab.menuItem);
|
|
179
|
-
const activeBasePathname = activeMenuItem ? getTabUrlPathname(getMenuRoute(activeMenuItem)) : getTabUrlPathname(activeTab == null ? void 0 : activeTab.url);
|
|
185
|
+
const activeBasePathname = activeMenuItem ? getTabUrlPathname(getComparableTabUrl(getMenuRoute(activeMenuItem), basename)) : getTabUrlPathname(getComparableTabUrl(activeTab == null ? void 0 : activeTab.url, basename));
|
|
180
186
|
const targetMenu = (_b = sourceMenus.map((item) => ({
|
|
181
187
|
item,
|
|
182
|
-
pathname: getTabUrlPathname(getMenuRoute(item))
|
|
188
|
+
pathname: getTabUrlPathname(getComparableTabUrl(getMenuRoute(item), basename))
|
|
183
189
|
})).filter(
|
|
184
190
|
(entry) => !!entry.pathname && isSameOrNestedPath(entry.pathname, currentPathname)
|
|
185
191
|
).sort((left, right) => right.pathname.length - left.pathname.length)[0]) == null ? void 0 : _b.item;
|
|
@@ -223,7 +229,8 @@ const useTabsUrlSync = ({
|
|
|
223
229
|
addTab,
|
|
224
230
|
switchTab,
|
|
225
231
|
updateTabUrl,
|
|
226
|
-
interactionSequence
|
|
232
|
+
interactionSequence,
|
|
233
|
+
basename
|
|
227
234
|
]);
|
|
228
235
|
useEffect(() => {
|
|
229
236
|
if (!enabled || !isInitialized || !bootstrappedRef.current || typeof window === "undefined") {
|
|
@@ -231,13 +238,14 @@ const useTabsUrlSync = ({
|
|
|
231
238
|
}
|
|
232
239
|
const activeTab = state.tabsList.find((tab) => tab.id === state.activeKey);
|
|
233
240
|
const targetUrl = normalizeTabUrl(activeTab == null ? void 0 : activeTab.url);
|
|
241
|
+
const browserTargetUrl = getBrowserTabUrl(targetUrl, basename);
|
|
234
242
|
const commitActiveTab = () => {
|
|
235
243
|
committedActiveKeyRef.current = state.activeKey;
|
|
236
244
|
committedTabIdsRef.current = new Set(state.tabsList.map((tab) => tab.id));
|
|
237
245
|
};
|
|
238
246
|
if (preserveUrlInteractionRef.current !== null) {
|
|
239
247
|
if (preserveUrlInteractionRef.current === interactionSequence) {
|
|
240
|
-
if (
|
|
248
|
+
if (browserTargetUrl === getCurrentTabUrl()) {
|
|
241
249
|
preserveUrlInteractionRef.current = null;
|
|
242
250
|
}
|
|
243
251
|
commitActiveTab();
|
|
@@ -245,7 +253,7 @@ const useTabsUrlSync = ({
|
|
|
245
253
|
}
|
|
246
254
|
preserveUrlInteractionRef.current = null;
|
|
247
255
|
}
|
|
248
|
-
if (!targetUrl ||
|
|
256
|
+
if (!targetUrl || !browserTargetUrl || browserTargetUrl === getCurrentTabUrl()) {
|
|
249
257
|
const pendingNavigation = pendingDataNavigationRef.current;
|
|
250
258
|
navigationAttemptRef.current += 1;
|
|
251
259
|
pendingDataNavigationRef.current = false;
|
|
@@ -286,30 +294,34 @@ const useTabsUrlSync = ({
|
|
|
286
294
|
pendingDataNavigationRef.current = false;
|
|
287
295
|
pendingDataNavigationUrlRef.current = null;
|
|
288
296
|
const currentUrl = getCurrentTabUrl();
|
|
289
|
-
if (
|
|
297
|
+
if (browserTargetUrl === currentUrl || committedDataNavigationUrl === currentUrl) {
|
|
290
298
|
commitActiveTab();
|
|
291
299
|
return;
|
|
292
300
|
}
|
|
293
301
|
rollbackTabActivation(previousActiveKey, attemptedActiveKey, removeAttemptedTab);
|
|
294
302
|
};
|
|
295
303
|
pendingDataNavigationRef.current = true;
|
|
296
|
-
pendingDataNavigationUrlRef.current =
|
|
297
|
-
void dataRouterContext.router.navigate(getDataRouterUrl(
|
|
304
|
+
pendingDataNavigationUrlRef.current = browserTargetUrl;
|
|
305
|
+
void dataRouterContext.router.navigate(getDataRouterUrl(browserTargetUrl, dataRouterContext.basename), {
|
|
298
306
|
replace: historyMode === "replace",
|
|
299
307
|
state: routerState
|
|
300
308
|
}).then(settleNavigation);
|
|
301
309
|
} else if (navigator) {
|
|
302
310
|
if (historyMode === "push") {
|
|
303
|
-
navigator.push(
|
|
311
|
+
navigator.push(browserTargetUrl, routerState);
|
|
304
312
|
} else {
|
|
305
|
-
navigator.replace(
|
|
313
|
+
navigator.replace(browserTargetUrl, routerState);
|
|
306
314
|
}
|
|
307
315
|
commitActiveTab();
|
|
308
316
|
} else if (historyMode === "push") {
|
|
309
|
-
window.history.pushState(
|
|
317
|
+
window.history.pushState(
|
|
318
|
+
createPushHistoryState(window.history.state),
|
|
319
|
+
"",
|
|
320
|
+
browserTargetUrl
|
|
321
|
+
);
|
|
310
322
|
commitActiveTab();
|
|
311
323
|
} else {
|
|
312
|
-
window.history.replaceState(window.history.state, "",
|
|
324
|
+
window.history.replaceState(window.history.state, "", browserTargetUrl);
|
|
313
325
|
commitActiveTab();
|
|
314
326
|
}
|
|
315
327
|
} finally {
|
|
@@ -324,7 +336,8 @@ const useTabsUrlSync = ({
|
|
|
324
336
|
interactionSequence,
|
|
325
337
|
navigator,
|
|
326
338
|
dataRouterContext,
|
|
327
|
-
rollbackTabActivation
|
|
339
|
+
rollbackTabActivation,
|
|
340
|
+
basename
|
|
328
341
|
]);
|
|
329
342
|
};
|
|
330
343
|
export {
|
|
@@ -67,8 +67,8 @@ export interface UseTabsStateReturn {
|
|
|
67
67
|
addTab: (menuItem: MenusType, options?: AddTabOptions) => void;
|
|
68
68
|
/** 检查是否可以添加标签页 */
|
|
69
69
|
canAddTab: (menuItem: MenusType, options?: AddTabOptions) => boolean;
|
|
70
|
-
/**
|
|
71
|
-
removeTab: (tabId
|
|
70
|
+
/** 移除指定标签页;不传 tabId 时移除当前激活标签页 */
|
|
71
|
+
removeTab: (tabId?: string) => void;
|
|
72
72
|
/** 更新指定 id 的标签页字段(替换语义;找不到时 no-op) */
|
|
73
73
|
updateTab: (tabId: string, updates: UpdateTabParams) => void;
|
|
74
74
|
/** 更新页签当前 URL,保持 tab.id 不变 */
|
|
@@ -14,10 +14,15 @@ export declare const normalizeTabUrl: (value?: string) => string | undefined;
|
|
|
14
14
|
export declare const isSafeExternalUrl: (value?: string) => boolean;
|
|
15
15
|
export declare const isSafeHttpUrl: (value?: string) => boolean;
|
|
16
16
|
export declare const getCurrentTabUrl: () => string;
|
|
17
|
+
/** 将浏览器地址和 Tab 内部地址转为同一身份,仅按完整路径段移除 basename。 */
|
|
18
|
+
export declare const getComparableTabUrl: (value?: string, basename?: string) => string | undefined;
|
|
19
|
+
/** 将 Tab 内部地址转为浏览器地址,已带 basename 时不重复添加。 */
|
|
20
|
+
export declare const getBrowserTabUrl: (value?: string, basename?: string) => string | undefined;
|
|
17
21
|
export declare const getTabUrlPathname: (value?: string) => string | undefined;
|
|
18
22
|
export interface FindExistingTabOptions {
|
|
19
23
|
urlSync?: boolean;
|
|
20
24
|
identity?: TabsUrlSyncConfig['identity'];
|
|
25
|
+
basename?: TabsUrlSyncConfig['basename'];
|
|
21
26
|
}
|
|
22
27
|
export declare const findExistingTab: (tabsList: TabItem[], menuItem: MenusType, options?: FindExistingTabOptions) => TabItem | undefined;
|
|
23
28
|
/**
|
|
@@ -29,6 +29,49 @@ const isSafeExternalUrl = (value) => {
|
|
|
29
29
|
};
|
|
30
30
|
const isSafeHttpUrl = (value) => !!resolveHttpUrl(value);
|
|
31
31
|
const getCurrentTabUrl = () => `${window.location.pathname}${window.location.search}${window.location.hash}`;
|
|
32
|
+
const normalizeTabsBasename = (basename) => {
|
|
33
|
+
const normalized = String(basename ?? "").trim();
|
|
34
|
+
if (!normalized || normalized === "/")
|
|
35
|
+
return "";
|
|
36
|
+
return `/${normalized.replace(/^\/+|\/+$/g, "")}`;
|
|
37
|
+
};
|
|
38
|
+
const splitNormalizedTabUrl = (url) => {
|
|
39
|
+
const pathnameEnd = [url.indexOf("?"), url.indexOf("#")].filter((index) => index >= 0).reduce((current, index) => Math.min(current, index), url.length);
|
|
40
|
+
return {
|
|
41
|
+
pathname: url.slice(0, pathnameEnd),
|
|
42
|
+
suffix: url.slice(pathnameEnd)
|
|
43
|
+
};
|
|
44
|
+
};
|
|
45
|
+
const hasPathBasename = (pathname, basename) => {
|
|
46
|
+
const lowerPathname = pathname.toLowerCase();
|
|
47
|
+
const lowerBasename = basename.toLowerCase();
|
|
48
|
+
return lowerPathname === lowerBasename || lowerPathname.startsWith(`${lowerBasename}/`);
|
|
49
|
+
};
|
|
50
|
+
const getComparableTabUrl = (value, basename) => {
|
|
51
|
+
const normalizedUrl = normalizeTabUrl(value);
|
|
52
|
+
if (!normalizedUrl)
|
|
53
|
+
return void 0;
|
|
54
|
+
const normalizedBasename = normalizeTabsBasename(basename);
|
|
55
|
+
if (!normalizedBasename)
|
|
56
|
+
return normalizedUrl;
|
|
57
|
+
const { pathname, suffix } = splitNormalizedTabUrl(normalizedUrl);
|
|
58
|
+
if (!hasPathBasename(pathname, normalizedBasename))
|
|
59
|
+
return normalizedUrl;
|
|
60
|
+
const barePathname = pathname.slice(normalizedBasename.length) || "/";
|
|
61
|
+
return `${barePathname}${suffix}`;
|
|
62
|
+
};
|
|
63
|
+
const getBrowserTabUrl = (value, basename) => {
|
|
64
|
+
const normalizedUrl = normalizeTabUrl(value);
|
|
65
|
+
if (!normalizedUrl)
|
|
66
|
+
return void 0;
|
|
67
|
+
const normalizedBasename = normalizeTabsBasename(basename);
|
|
68
|
+
if (!normalizedBasename)
|
|
69
|
+
return normalizedUrl;
|
|
70
|
+
const { pathname, suffix } = splitNormalizedTabUrl(normalizedUrl);
|
|
71
|
+
if (hasPathBasename(pathname, normalizedBasename))
|
|
72
|
+
return normalizedUrl;
|
|
73
|
+
return `${normalizedBasename}${pathname}${suffix}`;
|
|
74
|
+
};
|
|
32
75
|
const getTabUrlPathname = (value) => {
|
|
33
76
|
const normalized = normalizeTabUrl(value);
|
|
34
77
|
if (!normalized || typeof window === "undefined")
|
|
@@ -45,10 +88,10 @@ const findExistingTab = (tabsList, menuItem, options = {}) => {
|
|
|
45
88
|
}
|
|
46
89
|
);
|
|
47
90
|
}
|
|
48
|
-
const targetUrl =
|
|
91
|
+
const targetUrl = getComparableTabUrl(getMenuRoute(menuItem), options.basename);
|
|
49
92
|
if (!targetUrl)
|
|
50
93
|
return void 0;
|
|
51
|
-
return tabsList.find((tab) =>
|
|
94
|
+
return tabsList.find((tab) => getComparableTabUrl(tab.url, options.basename) === targetUrl);
|
|
52
95
|
}
|
|
53
96
|
if (hasRelationKey(menuItem.relationKey)) {
|
|
54
97
|
return tabsList.find(
|
|
@@ -232,6 +275,8 @@ export {
|
|
|
232
275
|
findExistingTab,
|
|
233
276
|
flattenMenuData,
|
|
234
277
|
generateTabId,
|
|
278
|
+
getBrowserTabUrl,
|
|
279
|
+
getComparableTabUrl,
|
|
235
280
|
getCurrentTabUrl,
|
|
236
281
|
getMenuRoute,
|
|
237
282
|
getPinnedBoundary,
|
|
@@ -373,6 +373,11 @@ export interface TabsUrlSyncConfig {
|
|
|
373
373
|
* @default 'url'
|
|
374
374
|
*/
|
|
375
375
|
identity?: 'url' | 'relationKey';
|
|
376
|
+
/**
|
|
377
|
+
* @description 浏览器地址中的显式基础路径;Tab 内部 URL 可以不带该前缀
|
|
378
|
+
* @example '/wjs'
|
|
379
|
+
*/
|
|
380
|
+
basename?: string;
|
|
376
381
|
}
|
|
377
382
|
/**
|
|
378
383
|
* @description 添加标签页的业务参数
|
|
@@ -464,9 +469,15 @@ export interface ProLayoutTabsInstance {
|
|
|
464
469
|
*/
|
|
465
470
|
addTab: (params: AddTabParams, options?: AddTabOptions) => void;
|
|
466
471
|
/**
|
|
467
|
-
* @description
|
|
472
|
+
* @description 删除指定标签页;不传 tabId 时删除当前激活标签页
|
|
473
|
+
* @param tabId 目标标签页 id,省略时使用当前激活标签页
|
|
474
|
+
* @example
|
|
475
|
+
* ```tsx
|
|
476
|
+
* layoutTabs.removeTab();
|
|
477
|
+
* layoutTabs.removeTab('tab-id');
|
|
478
|
+
* ```
|
|
468
479
|
*/
|
|
469
|
-
removeTab: (tabId
|
|
480
|
+
removeTab: (tabId?: string) => void;
|
|
470
481
|
/**
|
|
471
482
|
* @description 更新已存在标签页的展示信息(不会卸载/重挂渲染组件)
|
|
472
483
|
* @param tabId 目标标签页 id
|
package/es/ProSelect/index.js
CHANGED
|
@@ -14,6 +14,7 @@ import React, {
|
|
|
14
14
|
import isFunction from "lodash/isFunction";
|
|
15
15
|
import omit from "lodash/omit";
|
|
16
16
|
import isNumber from "lodash/isNumber";
|
|
17
|
+
import isString from "lodash/isString";
|
|
17
18
|
import { useProConfig } from "../ProConfigProvider";
|
|
18
19
|
import Container from "../ProForm/components/Container";
|
|
19
20
|
import AdaptiveTooltip from "./components/AdaptiveTooltip";
|
|
@@ -190,9 +191,25 @@ const ProSelect = (props, ref) => {
|
|
|
190
191
|
const option = findOption(v);
|
|
191
192
|
if (!option)
|
|
192
193
|
return "-";
|
|
193
|
-
|
|
194
|
+
const optionLabel = option[label];
|
|
195
|
+
if (!selectProps.showCodeName)
|
|
196
|
+
return optionLabel;
|
|
197
|
+
if (isString(optionLabel) || isNumber(optionLabel)) {
|
|
198
|
+
return `${option[code]}-${optionLabel}`;
|
|
199
|
+
}
|
|
200
|
+
return /* @__PURE__ */ jsxs(React.Fragment, { children: [
|
|
201
|
+
option[code],
|
|
202
|
+
"-",
|
|
203
|
+
optionLabel
|
|
204
|
+
] });
|
|
194
205
|
});
|
|
195
|
-
|
|
206
|
+
if (labelList.every((item) => isString(item) || isNumber(item))) {
|
|
207
|
+
return labelList.join("、");
|
|
208
|
+
}
|
|
209
|
+
return labelList.map((item, index) => /* @__PURE__ */ jsxs(React.Fragment, { children: [
|
|
210
|
+
index > 0 ? "、" : null,
|
|
211
|
+
item
|
|
212
|
+
] }, `${String(value2[index] ?? "empty")}-${index}`));
|
|
196
213
|
};
|
|
197
214
|
const defaultFilterOption = useCallback(defaultFilterOptionFn, []);
|
|
198
215
|
const mergedShowSearch = useMemo(() => {
|
|
@@ -282,13 +299,11 @@ const ProSelect = (props, ref) => {
|
|
|
282
299
|
};
|
|
283
300
|
const isReactElement = (element) => isValidElement(element);
|
|
284
301
|
const selectOptionRender = (option) => {
|
|
285
|
-
var _a2, _b2
|
|
302
|
+
var _a2, _b2;
|
|
286
303
|
const currentRecord = ((_a2 = option == null ? void 0 : option.data) == null ? void 0 : _a2.record) || (option == null ? void 0 : option.record);
|
|
287
|
-
const
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
}
|
|
291
|
-
return /* @__PURE__ */ jsx(TooltipOption, { title: String(text), children: OptionRender ? /* @__PURE__ */ jsx("span", { children: OptionRender(currentRecord) }) : /* @__PURE__ */ jsx("span", { children: String(text) }) });
|
|
304
|
+
const optionLabel = ((_b2 = option == null ? void 0 : option.data) == null ? void 0 : _b2.label) ?? (option == null ? void 0 : option.label) ?? "";
|
|
305
|
+
const content = currentRecord && OptionRender ? OptionRender(currentRecord) : optionLabel;
|
|
306
|
+
return /* @__PURE__ */ jsx(TooltipOption, { children: /* @__PURE__ */ jsx("span", { children: content }) });
|
|
292
307
|
};
|
|
293
308
|
const mode = selectProps.mode;
|
|
294
309
|
const handleChange = (value2, rawOption) => {
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { ReactNode } from 'react';
|
|
1
2
|
import type { DataOptionType } from '../propsType';
|
|
2
3
|
/**
|
|
3
4
|
* 返回当前组件可以显示的数据源
|
|
@@ -27,7 +28,7 @@ export declare const getOptionText: ({ item, optionRender, showCodeName, code, l
|
|
|
27
28
|
showCodeName?: boolean;
|
|
28
29
|
code: string;
|
|
29
30
|
label: string;
|
|
30
|
-
}) =>
|
|
31
|
+
}) => ReactNode;
|
|
31
32
|
/** 构建单个 antd Select option 对象 */
|
|
32
33
|
export declare const buildOptionItem: (item: DataOptionType, index: number, config: {
|
|
33
34
|
optionRender?: (item: DataOptionType) => any;
|
|
@@ -36,7 +37,7 @@ export declare const buildOptionItem: (item: DataOptionType, index: number, conf
|
|
|
36
37
|
label: string;
|
|
37
38
|
}) => {
|
|
38
39
|
key: string;
|
|
39
|
-
label:
|
|
40
|
+
label: ReactNode;
|
|
40
41
|
value: any;
|
|
41
42
|
disabled: boolean;
|
|
42
43
|
title: string;
|
|
@@ -2,6 +2,7 @@ import difference from "lodash/difference";
|
|
|
2
2
|
import isNumber from "lodash/isNumber";
|
|
3
3
|
import isObject from "lodash/isObject";
|
|
4
4
|
import isString from "lodash/isString";
|
|
5
|
+
import { createElement, Fragment } from "react";
|
|
5
6
|
const findSelectNameValues = ({
|
|
6
7
|
list = [],
|
|
7
8
|
selectName,
|
|
@@ -130,22 +131,30 @@ const getOptionText = ({
|
|
|
130
131
|
code,
|
|
131
132
|
label
|
|
132
133
|
}) => {
|
|
134
|
+
const itemLabel = item[label];
|
|
133
135
|
if (optionRender) {
|
|
134
136
|
const rendered = optionRender(item);
|
|
135
|
-
return isString(rendered) ? rendered : String(
|
|
137
|
+
return isString(rendered) ? rendered : isObject(itemLabel) ? itemLabel : String(itemLabel ?? "");
|
|
136
138
|
}
|
|
137
|
-
|
|
138
|
-
|
|
139
|
+
if (!showCodeName) {
|
|
140
|
+
return isObject(itemLabel) ? itemLabel : String(itemLabel ?? "");
|
|
141
|
+
}
|
|
142
|
+
if (isString(itemLabel) || isNumber(itemLabel)) {
|
|
143
|
+
return `${item[code]}-${itemLabel}`;
|
|
144
|
+
}
|
|
145
|
+
return createElement(Fragment, null, item[code], "-", itemLabel);
|
|
139
146
|
};
|
|
140
147
|
const buildOptionItem = (item, index, config) => {
|
|
141
|
-
const
|
|
148
|
+
const optionLabel = getOptionText({ item, ...config });
|
|
149
|
+
const isTextLabel = isString(optionLabel) || isNumber(optionLabel);
|
|
150
|
+
const searchText = isTextLabel ? String(optionLabel) : isString(item[config.label]) || isNumber(item[config.label]) ? String(item[config.label]) : item[config.code] != null ? String(item[config.code]) : "";
|
|
142
151
|
return {
|
|
143
152
|
key: item[config.code] != null ? `${item[config.code]}-${index}` : `option-${index}`,
|
|
144
|
-
label:
|
|
153
|
+
label: optionLabel,
|
|
145
154
|
value: item[config.code] != null ? item[config.code] : void 0,
|
|
146
155
|
disabled: !!item.disabled,
|
|
147
|
-
title:
|
|
148
|
-
searchText
|
|
156
|
+
title: isTextLabel ? String(optionLabel) : void 0,
|
|
157
|
+
searchText,
|
|
149
158
|
record: item
|
|
150
159
|
};
|
|
151
160
|
};
|
|
@@ -156,9 +165,10 @@ const buildSelectOptions = (list, config) => {
|
|
|
156
165
|
return list.map((item, index) => {
|
|
157
166
|
const groupOptions = item.options;
|
|
158
167
|
if (Array.isArray(groupOptions)) {
|
|
168
|
+
const groupLabel = item[config.label];
|
|
159
169
|
return {
|
|
160
|
-
key: item[config.
|
|
161
|
-
label:
|
|
170
|
+
key: isString(groupLabel) || isNumber(groupLabel) ? String(groupLabel) : item[config.code] != null ? String(item[config.code]) : `group-${index}`,
|
|
171
|
+
label: groupLabel,
|
|
162
172
|
options: groupOptions.map(
|
|
163
173
|
(opt, optIndex) => buildOptionItem(opt, optIndex, config)
|
|
164
174
|
)
|
|
@@ -14,7 +14,8 @@ const RenderTabs = (props) => {
|
|
|
14
14
|
formTableProps,
|
|
15
15
|
transformParams
|
|
16
16
|
} = props;
|
|
17
|
-
const { form, onSearch } = formTableProps || {};
|
|
17
|
+
const { form, onSearch, loading } = formTableProps || {};
|
|
18
|
+
const requestLoading = Boolean(loading);
|
|
18
19
|
const [activeKey, setActiveKey] = useState(props.activeKey);
|
|
19
20
|
const [options] = useEnum(code || "__PRO_TABLE_NO_CODE__");
|
|
20
21
|
const fieldValue = Form.useWatch(name, form);
|
|
@@ -48,6 +49,8 @@ const RenderTabs = (props) => {
|
|
|
48
49
|
return null;
|
|
49
50
|
}
|
|
50
51
|
const handleChange = (key) => {
|
|
52
|
+
if (requestLoading)
|
|
53
|
+
return;
|
|
51
54
|
const fieldsValues = (form == null ? void 0 : form.getFieldsValue()) || {};
|
|
52
55
|
setActiveKey(key);
|
|
53
56
|
form.setFieldValue(name, key);
|
|
@@ -65,7 +68,7 @@ const RenderTabs = (props) => {
|
|
|
65
68
|
onChange: handleChange,
|
|
66
69
|
type: "card",
|
|
67
70
|
...tabsProps,
|
|
68
|
-
items: tabItems,
|
|
71
|
+
items: requestLoading ? tabItems.map((item) => ({ ...item, disabled: true })) : tabItems,
|
|
69
72
|
activeKey
|
|
70
73
|
}
|
|
71
74
|
);
|
|
@@ -76,6 +79,7 @@ const RenderTabs = (props) => {
|
|
|
76
79
|
className: "pro-table-segmented",
|
|
77
80
|
options: tabItems.map((item) => ({ label: item.label, value: item.key })),
|
|
78
81
|
...segmentedProps,
|
|
82
|
+
disabled: requestLoading || (segmentedProps == null ? void 0 : segmentedProps.disabled),
|
|
79
83
|
onChange: (value) => handleChange(value),
|
|
80
84
|
value: activeKey
|
|
81
85
|
}
|