zmdms-webui 3.5.3 → 3.5.5

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.
@@ -67,6 +67,12 @@ interface ILayoutSiderProps {
67
67
  persist?: boolean;
68
68
  /** 自定义渲染主菜单图标,传入则替代默认 Icon 组件 */
69
69
  renderIcon?: (menuItem: any) => React__default.ReactNode;
70
+ /**
71
+ * 当前选中菜单变化回调(外部无需关心匹配的是主菜单还是二级菜单,匹配中即触发)
72
+ * - 点击主菜单/二级菜单:立即触发,menu 为菜单项原始数据(含 id、name、path 等),path 为完整路由
73
+ * - activeTab 变化:通过路由反查主菜单、二级菜单配置(支持动态路由、跨系统菜单),匹配中即触发
74
+ */
75
+ onMenuSelect?: (menu: any, path?: string) => void;
70
76
  }
71
77
  declare const _default: React__default.NamedExoticComponent<ILayoutSiderProps>;
72
78
 
@@ -4,6 +4,7 @@ import { memo, useState, useRef, useCallback } from 'react';
4
4
  import Menu from './menu/index.js';
5
5
  import { LayoutMenuContext } from './LayoutSiderContext.js';
6
6
  import { MENU_SEARCH_KEY, MENU_COLLECT_KEY } from './menu/constants.js';
7
+ import { useMenuSelect } from './useMenuSelect.js';
7
8
  import { Layout } from 'antd';
8
9
 
9
10
  /**
@@ -16,7 +17,7 @@ var AppLayoutSider = function (props) {
16
17
  // 118 宽度改成 134宽度
17
18
  _b = props.siderWidth,
18
19
  // 118 宽度改成 134宽度
19
- siderWidth = _b === void 0 ? 134 : _b, _c = props.subMenuWidth, subMenuWidth = _c === void 0 ? 162 : _c, addTab = props.addTab, siderBg = props.siderBg, logo = props.logo, logoStyle = props.logoStyle, subMenuLogo = props.subMenuLogo, _d = props.isShowSearchInput, isShowSearchInput = _d === void 0 ? true : _d, apps = props.apps, activeTab = props.activeTab, userMenus = props.userMenus, userSubMenus = props.userSubMenus, mergeSystemIds = props.mergeSystemIds, postMergeSystemIds = props.postMergeSystemIds, drawerTitle = props.drawerTitle, selectMainMenu = props.selectMainMenu, menuJoinRule = props.menuJoinRule, renderItem = props.renderItem, defaultVisible = props.defaultVisible, defaultFixed = props.defaultFixed, openOnlyHasSubmenus = props.openOnlyHasSubmenus, hideSubMenuBg = props.hideSubMenuBg, menuItemLine = props.menuItemLine, subMenuItemLine = props.subMenuItemLine, bottomContainer = props.bottomContainer, onLogoClick = props.onLogoClick, filterMenuHandle = props.filterMenuHandle, logoTitle = props.logoTitle, maxDeepIndex = props.maxDeepIndex, persist = props.persist, renderIcon = props.renderIcon;
20
+ siderWidth = _b === void 0 ? 134 : _b, _c = props.subMenuWidth, subMenuWidth = _c === void 0 ? 162 : _c, addTab = props.addTab, siderBg = props.siderBg, logo = props.logo, logoStyle = props.logoStyle, subMenuLogo = props.subMenuLogo, _d = props.isShowSearchInput, isShowSearchInput = _d === void 0 ? true : _d, apps = props.apps, activeTab = props.activeTab, userMenus = props.userMenus, userSubMenus = props.userSubMenus, mergeSystemIds = props.mergeSystemIds, postMergeSystemIds = props.postMergeSystemIds, drawerTitle = props.drawerTitle, selectMainMenu = props.selectMainMenu, menuJoinRule = props.menuJoinRule, renderItem = props.renderItem, defaultVisible = props.defaultVisible, defaultFixed = props.defaultFixed, openOnlyHasSubmenus = props.openOnlyHasSubmenus, hideSubMenuBg = props.hideSubMenuBg, menuItemLine = props.menuItemLine, subMenuItemLine = props.subMenuItemLine, bottomContainer = props.bottomContainer, onLogoClick = props.onLogoClick, filterMenuHandle = props.filterMenuHandle, logoTitle = props.logoTitle, maxDeepIndex = props.maxDeepIndex, persist = props.persist, renderIcon = props.renderIcon, onMenuSelect = props.onMenuSelect;
20
21
  // 当前选中的主菜单item
21
22
  var _e = useState([]), mainMenuSelectedKeys = _e[0], setMainMenuSelectedKeys = _e[1];
22
23
  // 内部二级菜单管理
@@ -55,6 +56,15 @@ var AppLayoutSider = function (props) {
55
56
  ? "/".concat(prefix).concat(path)
56
57
  : path;
57
58
  }, [menuJoinRule]);
59
+ // 菜单选中通知:点击菜单时由 Menu 的 onSelect 直通携带菜单数据,
60
+ // activeTab 变化时通过路由反查主菜单、二级菜单配置(见 useMenuSelect)
61
+ var notifyMenuSelect = useMenuSelect({
62
+ userMenus: userMenus,
63
+ userSubMenus: userSubMenus,
64
+ apps: apps,
65
+ activeTab: activeTab,
66
+ onMenuSelect: onMenuSelect,
67
+ }).notifyMenuSelect;
58
68
  return (jsx(Layout.Sider, __assign({ className: "zmdms-sider", collapsedWidth: "0", collapsible: true, trigger: null, width: siderWidth, ref: layoutSiderRef, style: {
59
69
  backgroundImage: "url(".concat(siderBg, ")"),
60
70
  } }, { children: jsx(LayoutMenuContext.Provider, __assign({ value: {
@@ -75,6 +85,7 @@ var AppLayoutSider = function (props) {
75
85
  getNewPath: getNewPath,
76
86
  renderItem: renderItem,
77
87
  filterMenuHandle: filterMenuHandle,
88
+ notifyMenuSelect: notifyMenuSelect,
78
89
  } }, { children: jsx(Menu, { width: siderWidth, subMenuWidth: subMenuWidth, layoutSiderRef: layoutSiderRef, addTab: addTab, logo: logo, logoStyle: logoStyle, subMenuLogo: subMenuLogo, getPrefixByAppId: getPrefixByAppId, defaultVisible: defaultVisible, openOnlyHasSubmenus: openOnlyHasSubmenus, defaultFixed: defaultFixed, isShowSearchInput: isShowSearchInput, hideSubMenuBg: hideSubMenuBg, menuItemLine: menuItemLine, subMenuItemLine: subMenuItemLine, bottomContainer: bottomContainer, onLogoClick: onLogoClick, logoTitle: logoTitle, maxDeepIndex: maxDeepIndex, persist: persist, renderIcon: renderIcon }) })) })));
79
90
  };
80
91
  AppLayoutSider.displayName = "ZTXK_WEBUI_AppLayoutSider";
@@ -0,0 +1,123 @@
1
+ import { matchPath } from 'react-router-dom';
2
+
3
+ /**
4
+ * 根据当前路由和微应用列表解析路由匹配上下文
5
+ *
6
+ * 路由第一段若命中某个微应用的 name,则视为系统前缀:
7
+ * 该应用 appId 作为 systemId,路由去掉前缀后用于常规匹配;
8
+ * 未命中前缀(非微前端场景)时,完整路由即菜单配置路由
9
+ */
10
+ function resolveRouteContext(pathname, apps) {
11
+ var systemPrefix = "/".concat(pathname.split("/")[1]);
12
+ var matchedApp = Array.isArray(apps)
13
+ ? apps.find(function (item) { return "/".concat(item.name) === systemPrefix; })
14
+ : undefined;
15
+ if (matchedApp) {
16
+ return {
17
+ pathname: pathname,
18
+ pathWithoutPrefix: pathname.replace(systemPrefix, "") || "/",
19
+ systemId: matchedApp.appId,
20
+ };
21
+ }
22
+ return { pathname: pathname, pathWithoutPrefix: pathname, systemId: undefined };
23
+ }
24
+ /**
25
+ * 判断单个菜单项是否命中当前路由
26
+ *
27
+ * 两种命中方式(满足其一即可):
28
+ * 1. 常规匹配:本系统配置的菜单。微前端下菜单配置的路由通常没有系统前缀
29
+ * (实际访问路由才会带上前缀),因此用双条件匹配:
30
+ * 「菜单项 system === 当前系统 id && 菜单 path 匹配 去前缀后的路由」
31
+ * 2. 跨系统匹配:A 系统下配置了 B 系统的路由。这种菜单隶属 A(system 是 A 的 id),
32
+ * 但配置的 path 本身就是带 B 系统前缀的完整路由,规则 1 的 system 条件注定不成立,
33
+ * 改用「菜单 path 匹配 完整 pathname」。
34
+ *
35
+ * 每种方式的路径匹配均支持两级:先全等匹配;菜单配置的是动态路由模式
36
+ * (如 /xx/:id)时,再用 matchPath 模式匹配实际路由(如 /xx/123)。
37
+ */
38
+ function isMenuMatchRoute(menu, context) {
39
+ var menuPath = menu === null || menu === void 0 ? void 0 : menu.path;
40
+ if (typeof menuPath !== "string" || !menuPath) {
41
+ return false;
42
+ }
43
+ // path 为 '/' 的菜单(首页类占位菜单)不参与路由匹配
44
+ if (menuPath === "/") {
45
+ return false;
46
+ }
47
+ // 1. 常规:系统 id + 无前缀路由 双条件匹配(全等 或 动态路由模式匹配)
48
+ if (context.systemId !== undefined &&
49
+ menu.system + "" === context.systemId + "") {
50
+ if (menuPath === context.pathWithoutPrefix ||
51
+ !!matchPath({ path: menuPath }, context.pathWithoutPrefix)) {
52
+ return true;
53
+ }
54
+ }
55
+ // 2. 跨系统:带前缀的完整路由匹配(全等 或 动态路由模式匹配)
56
+ return (menuPath === context.pathname ||
57
+ !!matchPath({ path: menuPath }, context.pathname));
58
+ }
59
+ /**
60
+ * 递归遍历菜单列表,查找与当前路由匹配的菜单项
61
+ */
62
+ function findMenuInList(menus, context) {
63
+ if (!Array.isArray(menus)) {
64
+ return undefined;
65
+ }
66
+ for (var _i = 0, menus_1 = menus; _i < menus_1.length; _i++) {
67
+ var menu = menus_1[_i];
68
+ if (isMenuMatchRoute(menu, context)) {
69
+ return menu;
70
+ }
71
+ // children 下面可能还有 children,递归查找
72
+ if (Array.isArray(menu === null || menu === void 0 ? void 0 : menu.children) && menu.children.length > 0) {
73
+ var matched = findMenuInList(menu.children, context);
74
+ if (matched) {
75
+ return matched;
76
+ }
77
+ }
78
+ }
79
+ return undefined;
80
+ }
81
+ /**
82
+ * 从主菜单、二级菜单配置中匹配当前路由对应的菜单数据
83
+ *
84
+ * 菜单数据结构:
85
+ * userMenus: [{ id: 系统id, children: [二级菜单项, ...] }]
86
+ * userSubMenus: { 二级菜单id: [三级菜单项(自身可能继续嵌套 children), ...] }
87
+ * 注意:二级菜单项自身的 children 已被剥离到 userSubMenus(按其 id 为 key),
88
+ * 因此 userMenus.children 中的二级菜单项与 userSubMenus 中的更深子级都需要遍历。
89
+ *
90
+ * @param userMenus 主菜单配置
91
+ * @param userSubMenus 二级菜单配置
92
+ * @param context 路由匹配上下文(完整路由 / 去前缀路由 / 当前系统 id)
93
+ * @returns 匹配到的菜单信息;未匹配到返回 undefined
94
+ */
95
+ function findMenuByRoute(userMenus, userSubMenus, context) {
96
+ // 去前缀后为 '/'(即当前路由就是系统前缀本身,如 /zmd-itoc)属于系统落点/首页场景,不做菜单匹配
97
+ if (context.pathWithoutPrefix === "/") {
98
+ return undefined;
99
+ }
100
+ // 1. 先从主菜单中查找(一级系统菜单的 children,即二级菜单项)
101
+ if (Array.isArray(userMenus)) {
102
+ for (var _i = 0, userMenus_1 = userMenus; _i < userMenus_1.length; _i++) {
103
+ var mainMenu = userMenus_1[_i];
104
+ var matched = findMenuInList(mainMenu === null || mainMenu === void 0 ? void 0 : mainMenu.children, context);
105
+ if (matched) {
106
+ return matched;
107
+ }
108
+ }
109
+ }
110
+ // 2. 再从二级菜单配置中查找(其子级可能继续嵌套 children,内部递归处理)
111
+ if (userSubMenus && typeof userSubMenus === "object") {
112
+ for (var _a = 0, _b = Object.keys(userSubMenus); _a < _b.length; _a++) {
113
+ var key = _b[_a];
114
+ var matched = findMenuInList(userSubMenus[key], context);
115
+ if (matched) {
116
+ return matched;
117
+ }
118
+ }
119
+ }
120
+ return undefined;
121
+ }
122
+
123
+ export { findMenuByRoute, resolveRouteContext };
@@ -11,7 +11,7 @@ import { Menu } from 'antd';
11
11
  // 是否隐藏菜单 isHiddenMenu 1 是 2 否
12
12
  var MainMenu = function (props) {
13
13
  var onMouseEnter = props.onMouseEnter, addTab = props.addTab, getPrefixByAppId = props.getPrefixByAppId, subMenu = props.subMenu, hideSubMenuBg = props.hideSubMenuBg, menuItemLine = props.menuItemLine, renderIcon = props.renderIcon;
14
- var _a = useContext(LayoutMenuContext), mainMenuSelectedKeys = _a.mainMenuSelectedKeys, setMainMenuSelectedKeys = _a.setMainMenuSelectedKeys, userMenus = _a.userMenus, apps = _a.apps, mergeSystemIds = _a.mergeSystemIds, postMergeSystemIds = _a.postMergeSystemIds, getNewPath = _a.getNewPath, renderItem = _a.renderItem, filterMenuHandle = _a.filterMenuHandle;
14
+ var _a = useContext(LayoutMenuContext), mainMenuSelectedKeys = _a.mainMenuSelectedKeys, setMainMenuSelectedKeys = _a.setMainMenuSelectedKeys, userMenus = _a.userMenus, apps = _a.apps, mergeSystemIds = _a.mergeSystemIds, postMergeSystemIds = _a.postMergeSystemIds, getNewPath = _a.getNewPath, renderItem = _a.renderItem, filterMenuHandle = _a.filterMenuHandle, notifyMenuSelect = _a.notifyMenuSelect;
15
15
  // 获取主菜单展开的内容
16
16
  var _b = useMainMenuOpenKeys(userMenus, mainMenuSelectedKeys, { apps: apps, mergeSystemIds: mergeSystemIds, postMergeSystemIds: postMergeSystemIds }), mainMenuOpenKeys = _b.mainMenuOpenKeys, onMainMenuOpenChange = _b.onMainMenuOpenChange;
17
17
  // 创建主菜单标签
@@ -85,6 +85,8 @@ var MainMenu = function (props) {
85
85
  var _a;
86
86
  return {
87
87
  label: getLabel(userMenu),
88
+ // 给菜单匹配新增一个属性,以便onSelect方法能直接取到菜单信息
89
+ menuitem: userMenu,
88
90
  title: (_a = userMenu.name) === null || _a === void 0 ? void 0 : _a.trim(),
89
91
  key: userMenu.id,
90
92
  icon: getIcon(userMenu),
@@ -106,6 +108,8 @@ var MainMenu = function (props) {
106
108
  title: (_b = userMenu.name) === null || _b === void 0 ? void 0 : _b.trim(),
107
109
  key: userMenu.id,
108
110
  icon: getIcon(userMenu),
111
+ // 给菜单匹配新增一个属性,以便onSelect方法能直接取到菜单信息
112
+ menuitem: userMenu,
109
113
  children: children
110
114
  ? filterMenus.map(function (item) {
111
115
  var _a;
@@ -114,6 +118,8 @@ var MainMenu = function (props) {
114
118
  title: (_a = item.name) === null || _a === void 0 ? void 0 : _a.trim(),
115
119
  key: item.id,
116
120
  icon: getIcon(item, true),
121
+ // 给菜单匹配新增一个属性,以便onSelect方法能直接取到菜单信息
122
+ menuitem: item,
117
123
  };
118
124
  })
119
125
  : null,
@@ -122,11 +128,22 @@ var MainMenu = function (props) {
122
128
  }, [userMenus, getLabel, filterMenuHandle, getIcon]);
123
129
  // 主菜单选中时调用
124
130
  var onSelectHandle = useCallback(function (info) {
125
- var _a, _b;
131
+ var _a, _b, _c, _d;
126
132
  setMainMenuSelectedKeys && setMainMenuSelectedKeys(info.selectedKeys);
127
133
  // 选中主菜单修改openKeys
128
134
  subMenu && ((_b = (_a = subMenu.current) === null || _a === void 0 ? void 0 : _a.onSubMenuOpenChange) === null || _b === void 0 ? void 0 : _b.call(_a, info.selectedKeys));
129
- }, [setMainMenuSelectedKeys, subMenu]);
135
+ // 点击时直接从菜单项 props 上拿到原始菜单数据通知外部(无需递归查找)
136
+ var menuItem = (_d = (_c = info === null || info === void 0 ? void 0 : info.item) === null || _c === void 0 ? void 0 : _c.props) === null || _d === void 0 ? void 0 : _d.menuitem;
137
+ if (menuItem && notifyMenuSelect) {
138
+ notifyMenuSelect(menuItem, getNewPath(menuItem, getPrefixByAppId(menuItem.system)));
139
+ }
140
+ }, [
141
+ setMainMenuSelectedKeys,
142
+ subMenu,
143
+ notifyMenuSelect,
144
+ getNewPath,
145
+ getPrefixByAppId,
146
+ ]);
130
147
  var classes = classNames("zmdms-menu--main", {
131
148
  "zmdms-menu--main-hide-bg": hideSubMenuBg,
132
149
  "zmdms-menu--main-item-line": menuItemLine,
@@ -19,7 +19,7 @@ var defaultSubMenu = [];
19
19
  var SubMenu = function (props, ref) {
20
20
  var marginLeft = props.marginLeft, subMenuWidth = props.subMenuWidth, layoutSiderRef = props.layoutSiderRef, addTab = props.addTab, getPrefixByAppId = props.getPrefixByAppId, subMenuLogo = props.subMenuLogo, visible = props.visible, setVisible = props.setVisible, _a = props.defaultFixed, defaultFixed = _a === void 0 ? false : _a, hideSubMenuBg = props.hideSubMenuBg, subMenuItemLine = props.subMenuItemLine, openOnlyHasSubmenus = props.openOnlyHasSubmenus, _b = props.maxDeepIndex, maxDeepIndex = _b === void 0 ? 3 : _b, persist = props.persist;
21
21
  var locale = useLocale().AppLayoutSider;
22
- var _c = useContext(LayoutMenuContext), mainMenuSelectedKeys = _c.mainMenuSelectedKeys, setMainMenuSelectedKeys = _c.setMainMenuSelectedKeys, activeTab = _c.activeTab, userMenus = _c.userMenus, userSubMenus = _c.userSubMenus, innerUserSubMenus = _c.innerUserSubMenus, drawerTitle = _c.drawerTitle, selectMainMenu = _c.selectMainMenu, getNewPath = _c.getNewPath, renderItem = _c.renderItem, filterMenuHandle = _c.filterMenuHandle;
22
+ var _c = useContext(LayoutMenuContext), mainMenuSelectedKeys = _c.mainMenuSelectedKeys, setMainMenuSelectedKeys = _c.setMainMenuSelectedKeys, activeTab = _c.activeTab, userMenus = _c.userMenus, userSubMenus = _c.userSubMenus, innerUserSubMenus = _c.innerUserSubMenus, drawerTitle = _c.drawerTitle, selectMainMenu = _c.selectMainMenu, getNewPath = _c.getNewPath, renderItem = _c.renderItem, filterMenuHandle = _c.filterMenuHandle, notifyMenuSelect = _c.notifyMenuSelect;
23
23
  // 二级菜单当前选中项
24
24
  var subMenusSelectedKeys = useMemo(function () {
25
25
  return activeTab ? [activeTab] : [];
@@ -180,6 +180,8 @@ var SubMenu = function (props, ref) {
180
180
  var subMenuItem = {
181
181
  label: getLabel(),
182
182
  title: menuItem.name,
183
+ // 给菜单匹配新增一个属性,以便onSelect方法能直接取到菜单信息
184
+ menuitem: menuItem,
183
185
  // key: menuItem.id,
184
186
  // 这里为什么要用newPath 而不是menuItem.id
185
187
  // 因为如果要选到叶子节点,是直接拿路由去做匹配
@@ -233,12 +235,21 @@ var SubMenu = function (props, ref) {
233
235
  }
234
236
  }
235
237
  }, [openOnlyHasSubmenus, items]);
238
+ // 二级菜单选中时调用(点击叶子菜单项触发;带子项的父级走 onOpenChange 展开/收起,不触发)
239
+ var onSelectHandle = useCallback(function (info) {
240
+ var _a, _b;
241
+ // 点击时直接从菜单项 props 上拿到原始菜单数据通知外部(无需递归查找)
242
+ var menuItem = (_b = (_a = info === null || info === void 0 ? void 0 : info.item) === null || _a === void 0 ? void 0 : _a.props) === null || _b === void 0 ? void 0 : _b.menuitem;
243
+ if (menuItem && notifyMenuSelect) {
244
+ notifyMenuSelect(menuItem, info.key);
245
+ }
246
+ }, [notifyMenuSelect]);
236
247
  var classes = classNames("zmdms-menu--sub", {
237
248
  "zmdms-menu--sub-margin": !drawerTitle,
238
249
  "zmdms-menu--sub-hide-bg": hideSubMenuBg,
239
250
  "zmdms-menu--sub-item-line": subMenuItemLine,
240
251
  });
241
- return (jsx(Drawer, __assign({ mask: false, placement: "left", width: subMenuWidth, zIndex: 101, closable: false, open: visible, className: classes, style: { marginLeft: marginLeft }, footer: footerDom, title: subMenuLogo ? (jsx("img", { src: subMenuLogo, title: locale.subMenuLogoTitle, alt: "cico_logo" })) : (jsx("div", {})) }, { children: jsx(Menu, { inlineIndent: 5, selectedKeys: subMenusSelectedKeys, mode: "inline", items: items, openKeys: subMenuOpenKeys, onOpenChange: onSubMenuOpenChange }) })));
252
+ return (jsx(Drawer, __assign({ mask: false, placement: "left", width: subMenuWidth, zIndex: 101, closable: false, open: visible, className: classes, style: { marginLeft: marginLeft }, footer: footerDom, title: subMenuLogo ? (jsx("img", { src: subMenuLogo, title: locale.subMenuLogoTitle, alt: "cico_logo" })) : (jsx("div", {})) }, { children: jsx(Menu, { inlineIndent: 5, selectedKeys: subMenusSelectedKeys, mode: "inline", items: items, onSelect: onSelectHandle, openKeys: subMenuOpenKeys, onOpenChange: onSubMenuOpenChange }) })));
242
253
  };
243
254
  var SubMenu$1 = memo(forwardRef(SubMenu));
244
255
 
@@ -0,0 +1,67 @@
1
+ import { useRef, useCallback, useEffect } from 'react';
2
+ import { findMenuByRoute, resolveRouteContext } from './findMenuByRoute.js';
3
+
4
+ /**
5
+ * 菜单选中通知(统一入口,外部无需关心匹配的是主菜单还是二级菜单)
6
+ *
7
+ * 两条触发路径:
8
+ * 1. 点击菜单:Menu 的 onSelect 中直通调用 notifyMenuSelect,
9
+ * 菜单项原始数据从菜单项 props 上直接携带(items 构造时挂的 menuitem),无需遍历。
10
+ * 通知成功后记录点击路由,activeTab 随点击变化的那次反查直接跳过
11
+ * 2. activeTab 变化:通过路由反查主菜单、二级菜单配置(findMenuByRoute),匹配中即通知。
12
+ * 仅在非点击引起的变化(切页签、刷新恢复、菜单数据异步到达)时执行
13
+ *
14
+ * 去重:同一菜单id通知成功后不重复通知;当前路由不属于任何菜单时清空标记,
15
+ * 保证从非菜单路由切回、或菜单数据异步到达后仍能正常通知
16
+ */
17
+ function useMenuSelect(options) {
18
+ var userMenus = options.userMenus, userSubMenus = options.userSubMenus, apps = options.apps, activeTab = options.activeTab, onMenuSelect = options.onMenuSelect;
19
+ // 上次已通知的菜单id
20
+ var notifiedMenuIdRef = useRef(null);
21
+ // 点击直通已通知的路由(一次性标记:activeTab 随点击变化的那次 effect 无需再反查)
22
+ var clickHandledPathRef = useRef(null);
23
+ // 通知外部选中菜单变化(menu 为菜单项原始数据,path 为对应的完整路由,菜单无路由时可缺省)
24
+ var notifyMenuSelect = useCallback(function (menu, path) {
25
+ if (!onMenuSelect || !menu) {
26
+ return;
27
+ }
28
+ var menuId = menu.id + "";
29
+ if (notifiedMenuIdRef.current === menuId) {
30
+ return;
31
+ }
32
+ notifiedMenuIdRef.current = menuId;
33
+ if (path) {
34
+ clickHandledPathRef.current = path;
35
+ }
36
+ onMenuSelect(menu, path);
37
+ }, [onMenuSelect]);
38
+ // activeTab 变化时通过路由反查菜单兜底通知
39
+ useEffect(function () {
40
+ if (!onMenuSelect || !activeTab || activeTab === "/") {
41
+ return;
42
+ }
43
+ // 当前路由正是点击直通已通知过的:无需反查(消费标记,
44
+ // 后续切走再切回该路由时仍会走反查重新通知)
45
+ if (clickHandledPathRef.current === activeTab) {
46
+ clickHandledPathRef.current = null;
47
+ return;
48
+ }
49
+ var matchedMenu = findMenuByRoute(userMenus, userSubMenus, resolveRouteContext(activeTab, apps));
50
+ if (!matchedMenu) {
51
+ // 当前路由不属于任何菜单(如临时详情页路由),清空标记以便切回菜单路由时重新通知
52
+ notifiedMenuIdRef.current = null;
53
+ return;
54
+ }
55
+ notifyMenuSelect(matchedMenu, activeTab);
56
+ }, [
57
+ activeTab,
58
+ userMenus,
59
+ userSubMenus,
60
+ apps,
61
+ onMenuSelect,
62
+ notifyMenuSelect,
63
+ ]);
64
+ return { notifyMenuSelect: notifyMenuSelect };
65
+ }
66
+
67
+ export { useMenuSelect };
@@ -39,6 +39,7 @@ import { useExcelExport } from '../table/excel.js';
39
39
  import { getTableColumns } from '../table/utils.js';
40
40
  import DynamicSetting from '../dynamicsetting/dynamicSetting.js';
41
41
  import '../dynamicsetting/useTemplate.js';
42
+ import { useSelectionSnapshot } from './hooks/useSelectionSnapshot.js';
42
43
  import useLocale from '../config/useLocale.js';
43
44
  import '../config/ZtxkContext.js';
44
45
  import '../config/MyStorage.js';
@@ -367,6 +368,18 @@ function CanvasTable(props) {
367
368
  columns: processedColumns,
368
369
  containerRef: containerRef,
369
370
  }), getSelectedCellsText = _9.getSelectedCellsText, copyToClipboard = _9.copyToClipboard;
371
+ useSelectionSnapshot({
372
+ selection: state.cellSelection,
373
+ selecting: state.isSelecting,
374
+ columns: processedColumns,
375
+ rows: processedDataSource,
376
+ scrollLeft: scrollState.scrollLeft,
377
+ scrollTop: scrollState.scrollTop,
378
+ containerRef: containerRef,
379
+ getText: getSelectedCellsText,
380
+ headerHeight: calculatedHeaderHeight,
381
+ onChange: props.onCellSelectionChange,
382
+ });
370
383
  // 处理右键菜单的复制操作
371
384
  var handleCopy = useCallback(function () {
372
385
  var text = getSelectedCellsText();
@@ -0,0 +1,115 @@
1
+ import { __assign, __spreadArray } from '../../_virtual/_tslib.js';
2
+ import { useRef, useEffect } from 'react';
3
+ import { flattenHeaders } from '../utils/multiHeaderHelpers.js';
4
+
5
+ /** 按显示顺序收集叶子列标题和明确配置的单位,保留多级表头路径。 */
6
+ function selectionColumns(columns, parents, parentUnit) {
7
+ if (parents === void 0) { parents = []; }
8
+ return columns.flatMap(function (column) {
9
+ var _a, _b, _c, _d, _e, _f;
10
+ var title = (_c = (_b = (_a = column.aiContext) === null || _a === void 0 ? void 0 : _a.columnTitle) !== null && _b !== void 0 ? _b : column.dynamicTitle) !== null && _c !== void 0 ? _c : (typeof column.title === "string" ? column.title : column.key);
11
+ var path = __spreadArray(__spreadArray([], parents, true), [title], false);
12
+ var unit = (_e = (_d = column.aiContext) === null || _d === void 0 ? void 0 : _d.displayUnit) !== null && _e !== void 0 ? _e : parentUnit;
13
+ return ((_f = column.children) === null || _f === void 0 ? void 0 : _f.length)
14
+ ? selectionColumns(column.children, path, unit)
15
+ : [
16
+ {
17
+ key: column.key,
18
+ fieldKey: column.dataIndex,
19
+ columnTitle: path.join(" / "),
20
+ displayUnit: unit || undefined,
21
+ },
22
+ ];
23
+ });
24
+ }
25
+ /** 框选完成才发布快照;数据、列、滚动变化清除旧快照,避免旧位置指向新记录。 */
26
+ function useSelectionSnapshot(options) {
27
+ var selection = options.selection, selecting = options.selecting, columns = options.columns, rows = options.rows, scrollLeft = options.scrollLeft, scrollTop = options.scrollTop, headerHeight = options.headerHeight, containerRef = options.containerRef, getText = options.getText, onChange = options.onChange;
28
+ var point = useRef({ x: 0, y: 0 });
29
+ var previous = useRef({ selection: selection, selecting: selecting });
30
+ var callback = useRef(onChange);
31
+ callback.current = onChange;
32
+ var enabled = !!onChange;
33
+ useEffect(function () {
34
+ if (!enabled)
35
+ return;
36
+ /** 在 Canvas mouseup 状态提交前记录最终鼠标坐标。 */
37
+ var rememberPoint = function (event) {
38
+ point.current = { x: event.clientX, y: event.clientY };
39
+ };
40
+ document.addEventListener("pointerup", rememberPoint, true);
41
+ return function () { return document.removeEventListener("pointerup", rememberPoint, true); };
42
+ }, [enabled]);
43
+ useEffect(function () {
44
+ var _a, _b, _c, _d, _e, _f;
45
+ var old = previous.current;
46
+ previous.current = { selection: selection, selecting: selecting };
47
+ if (!callback.current)
48
+ return;
49
+ if (!selection ||
50
+ selecting ||
51
+ (!old.selecting && selection === old.selection) ||
52
+ !containerRef.current) {
53
+ callback.current(null);
54
+ return;
55
+ }
56
+ var count = (Math.abs(selection.endRow - selection.startRow) + 1) *
57
+ (Math.abs(selection.endCol - selection.startCol) + 1);
58
+ // ponytail: 最多同步读取 200 格;更大范围分析应使用分页数据接口。
59
+ if (count > 200) {
60
+ callback.current(null);
61
+ return;
62
+ }
63
+ var metadata = selectionColumns(columns)
64
+ .slice(Math.min(selection.startCol, selection.endCol), Math.max(selection.startCol, selection.endCol) + 1)
65
+ .filter(function (column) {
66
+ return column.key !== "__selection__" && column.key !== "__index__";
67
+ });
68
+ var text = getText();
69
+ // 单击分组表头时解释实际表头,不能把所在叶子列误当成点击目标。
70
+ if (selection.startRow === -1 &&
71
+ selection.endRow === -1 &&
72
+ selection.startCol === selection.endCol) {
73
+ var headers = flattenHeaders(columns);
74
+ var y = point.current.y - containerRef.current.getBoundingClientRect().top;
75
+ var depth = Math.min(headers.length - 1, Math.max(0, Math.floor(y / (headerHeight / headers.length))));
76
+ var header = (_a = headers[depth]) === null || _a === void 0 ? void 0 : _a.find(function (cell) {
77
+ return selection.startCol >= cell.colIndex &&
78
+ selection.startCol < cell.colIndex + cell.colSpan;
79
+ });
80
+ if ((_b = header === null || header === void 0 ? void 0 : header.column.children) === null || _b === void 0 ? void 0 : _b.length) {
81
+ var column = header.column;
82
+ text =
83
+ (_e = (_d = (_c = column.aiContext) === null || _c === void 0 ? void 0 : _c.columnTitle) !== null && _d !== void 0 ? _d : column.dynamicTitle) !== null && _e !== void 0 ? _e : (typeof column.title === "string" ? column.title : column.key);
84
+ metadata.splice(0, metadata.length, {
85
+ key: column.key,
86
+ fieldKey: column.dataIndex,
87
+ columnTitle: text,
88
+ displayUnit: (_f = column.aiContext) === null || _f === void 0 ? void 0 : _f.displayUnit,
89
+ });
90
+ }
91
+ }
92
+ if (!text.trim()) {
93
+ callback.current(null);
94
+ return;
95
+ }
96
+ callback.current({
97
+ range: __assign({}, selection),
98
+ columns: metadata,
99
+ text: text,
100
+ anchor: __assign({}, point.current),
101
+ });
102
+ }, [
103
+ selection,
104
+ selecting,
105
+ columns,
106
+ rows,
107
+ scrollLeft,
108
+ scrollTop,
109
+ headerHeight,
110
+ containerRef,
111
+ getText,
112
+ ]);
113
+ }
114
+
115
+ export { useSelectionSnapshot };
@@ -7,6 +7,11 @@ interface FilterConfig {
7
7
  }
8
8
  type ColumnDataType = "text" | "number" | "date" | "boolean";
9
9
  interface ICanvasColumnType<RecordType = any> {
10
+ /** 选区的展示标题和单位;子列继承父列单位,空字符串取消继承。 */
11
+ aiContext?: {
12
+ columnTitle?: string;
13
+ displayUnit?: string;
14
+ };
10
15
  /**
11
16
  * 列的key,唯一标识
12
17
  */
@@ -479,6 +484,8 @@ interface ICanvasTableProps<RecordType = any> {
479
484
  * 表格id(用于自动高度计算时定位元素)
480
485
  */
481
486
  canvasTableId?: string;
487
+ /** 框选结束发布展示快照,开始框选、数据或滚动变化传 null 使旧快照失效。 */
488
+ onCellSelectionChange?: (selection: ICanvasCellSelection | null) => void;
482
489
  /**
483
490
  * 表格ref句柄,用于暴露表格的一些方法
484
491
  */
@@ -590,6 +597,27 @@ interface ICanvasTableRefHandle {
590
597
  * 清除过滤
591
598
  */
592
599
  clearFilter: () => void;
600
+ }
601
+ /** 完成选区的展示快照,不暴露完整记录;超过 200 格不生成快照。 */
602
+ interface ICanvasCellSelection {
603
+ range: ICellSelection;
604
+ columns: {
605
+ fieldKey?: string;
606
+ columnTitle: string;
607
+ displayUnit?: string;
608
+ }[];
609
+ text: string;
610
+ /** 屏幕坐标,用于定位选区操作入口。 */
611
+ anchor: {
612
+ x: number;
613
+ y: number;
614
+ };
615
+ }
616
+ interface ICellSelection {
617
+ startRow: number;
618
+ endRow: number;
619
+ startCol: number;
620
+ endCol: number;
593
621
  }
594
622
 
595
- export { ColumnDataType, FilterConfig, IBadgeProps, ICanvasColumnType, ICanvasColumnsType, ICanvasRowSelection, ICanvasTableProps, ICanvasTableRefHandle, IExpandable, SortOrder };
623
+ export { ColumnDataType, FilterConfig, IBadgeProps, ICanvasCellSelection, ICanvasColumnType, ICanvasColumnsType, ICanvasRowSelection, ICanvasTableProps, ICanvasTableRefHandle, ICellSelection, IExpandable, SortOrder };
@@ -55,7 +55,7 @@ export { default as Sortable } from './es/sortable/sortable.js';
55
55
  export { default as ElectronSignatures } from './es/electronsignatures/index.js';
56
56
  export { default as message } from './es/message/index.js';
57
57
  export { default as CanvasTable } from './es/canvastable/canvasTable.js';
58
- export { ICanvasColumnType, ICanvasColumnsType, ICanvasTableProps, ICanvasTableRefHandle } from './es/canvastable/interface.js';
58
+ export { ICanvasCellSelection, ICanvasColumnType, ICanvasColumnsType, ICanvasTableProps, ICanvasTableRefHandle } from './es/canvastable/interface.js';
59
59
  export { default as TranslationButton } from './es/translationbutton/translationButton.js';
60
60
  export { default as PreviewFile } from './es/translationbutton/PreviewFile.js';
61
61
  export { Affix, Anchor, AutoComplete, Avatar, BackTop, Badge, Breadcrumb, Card, Carousel, Cascader, Checkbox, Col, Comment, ConfigProvider, Divider, Drawer, Dropdown, Grid, Image, Layout, List, Mentions, Menu, PageHeader, Popconfirm, Popover, Progress, Radio, Rate, Result, Row, Segmented, Skeleton, Slider, Space, Spin, Statistic, Steps, Switch, Timeline, Tooltip, Transfer, Typography, Upload, notification } from 'antd';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zmdms-webui",
3
- "version": "3.5.3",
3
+ "version": "3.5.5",
4
4
  "private": false,
5
5
  "main": "dist/index.es.js",
6
6
  "module": "dist/index.es.js",