a2bei4-utils 1.0.4 → 1.0.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/a2bei4.utils.cjs.js +221 -7
- package/dist/a2bei4.utils.cjs.js.map +1 -1
- package/dist/a2bei4.utils.cjs.min.js +1 -1
- package/dist/a2bei4.utils.cjs.min.js.map +1 -1
- package/dist/a2bei4.utils.esm.js +220 -8
- package/dist/a2bei4.utils.esm.js.map +1 -1
- package/dist/a2bei4.utils.esm.min.js +1 -1
- package/dist/a2bei4.utils.esm.min.js.map +1 -1
- package/dist/a2bei4.utils.umd.js +221 -7
- package/dist/a2bei4.utils.umd.js.map +1 -1
- package/dist/a2bei4.utils.umd.min.js +1 -1
- package/dist/a2bei4.utils.umd.min.js.map +1 -1
- package/dist/menu.cjs +202 -0
- package/dist/menu.cjs.map +1 -0
- package/dist/menu.js +200 -0
- package/dist/menu.js.map +1 -0
- package/dist/tree.cjs +22 -7
- package/dist/tree.cjs.map +1 -1
- package/dist/tree.js +22 -8
- package/dist/tree.js.map +1 -1
- package/package.json +12 -7
- package/types/index.d.ts +85 -2
- package/types/menu.d.ts +62 -0
- package/types/tree.d.ts +24 -2
package/dist/a2bei4.utils.esm.js
CHANGED
|
@@ -1467,6 +1467,204 @@ class MyId {
|
|
|
1467
1467
|
}
|
|
1468
1468
|
}
|
|
1469
1469
|
|
|
1470
|
+
/**
|
|
1471
|
+
* 处理数据库菜单项,生成 UI 菜单树和路由配置。
|
|
1472
|
+
*
|
|
1473
|
+
* @param {Array<Object>} menuItems - 原始菜单项数组,树形结构
|
|
1474
|
+
* @param {Object} [options] - 配置选项
|
|
1475
|
+
* @param {string} [options.idKey='id'] - 数据源 ID 键名
|
|
1476
|
+
* @param {string} [options.codeKey='code'] - 数据源编码键名, 用于拼接路由名称和路由路径
|
|
1477
|
+
* @param {string} [options.labelKey='text'] - 数据源标签键名
|
|
1478
|
+
* @param {string} [options.childrenKey='children'] - 数据源子节点键名
|
|
1479
|
+
* @param {string} [options.extendKey='extend'] - 数据源扩展对象键名
|
|
1480
|
+
* @param {string} [options.menuIdKey='key'] - 输出菜单 ID 键名
|
|
1481
|
+
* @param {string} [options.menuLabelKey='label'] - 输出菜单标签键名
|
|
1482
|
+
* @param {string} [options.menuChildrenKey='children'] - 输出菜单子节点键名
|
|
1483
|
+
* @param {string} [options.menuExtendKey='extend'] - 输出菜单扩展对象键名
|
|
1484
|
+
* @param {string} [options.routeNameKey='name'] - 路由名称键名
|
|
1485
|
+
* @param {string} [options.routePathKey='path'] - 路由路径键名
|
|
1486
|
+
* @param {string} [options.routeMetaKey='meta'] - 路由元数据键名
|
|
1487
|
+
* @param {Function} [options.handleNodeItem] - 节点处理钩子,参数:(nodeSimple) => void
|
|
1488
|
+
* @param {Function} [options.handleMenuItem] - 菜单项处理钩子,参数:(uiMenuItem, nodeSimple) => void
|
|
1489
|
+
* @param {Function} [options.handleRouteItem] - 路由项处理钩子,参数:(routeItem, nodeSimple) => void
|
|
1490
|
+
*
|
|
1491
|
+
* @returns {Object} 处理结果
|
|
1492
|
+
* @returns {Array<Object>} returns.uiMenuItems - UI 菜单树形结构数组
|
|
1493
|
+
* @returns {Array<Object>} returns.routeItems - 扁平化路由配置数组
|
|
1494
|
+
* @returns {Map<string, Object>} returns.nodeMap - 节点 ID 到节点数据的映射表
|
|
1495
|
+
*
|
|
1496
|
+
* @example
|
|
1497
|
+
* const menuItems = [
|
|
1498
|
+
* { id: '1', code: 'system', text: '系统管理', children: [
|
|
1499
|
+
* { id: '1-1', code: 'user', text: '用户管理' }
|
|
1500
|
+
* ]}
|
|
1501
|
+
* ];
|
|
1502
|
+
*
|
|
1503
|
+
* const result = handleDbMenuItems(menuItems, {
|
|
1504
|
+
* handleRouteItem: (route, node) => {
|
|
1505
|
+
* route.component = () => import(`./views/${node.code}.vue`);
|
|
1506
|
+
* }
|
|
1507
|
+
* });
|
|
1508
|
+
*
|
|
1509
|
+
* // result.uiMenuItems: [{ key: '1', label: '系统管理', children: [...] }]
|
|
1510
|
+
* // result.routeItems: [{ name: 'system.user', path: '/system/user', meta: { id: '1-1' } }]
|
|
1511
|
+
* // result.nodeMap: Map { '1' => {...}, '1-1' => {...} }
|
|
1512
|
+
*/
|
|
1513
|
+
function handleDbMenuItems(menuItems, options = {}) {
|
|
1514
|
+
// 1. 统一配置项,简化调用
|
|
1515
|
+
const {
|
|
1516
|
+
// 数据源键名
|
|
1517
|
+
idKey = "id",
|
|
1518
|
+
codeKey = "code",
|
|
1519
|
+
labelKey = "text",
|
|
1520
|
+
childrenKey = "children",
|
|
1521
|
+
extendKey = "extend",
|
|
1522
|
+
|
|
1523
|
+
// 输出目标键名
|
|
1524
|
+
menuIdKey = "key",
|
|
1525
|
+
menuLabelKey = "label",
|
|
1526
|
+
menuChildrenKey = "children",
|
|
1527
|
+
menuExtendKey = "extend",
|
|
1528
|
+
|
|
1529
|
+
routeNameKey = "name",
|
|
1530
|
+
routePathKey = "path",
|
|
1531
|
+
routeMetaKey = "meta",
|
|
1532
|
+
|
|
1533
|
+
// 钩子函数
|
|
1534
|
+
handleNodeItem,
|
|
1535
|
+
handleMenuItem,
|
|
1536
|
+
handleRouteItem
|
|
1537
|
+
} = options;
|
|
1538
|
+
|
|
1539
|
+
const uiMenuItems = [];
|
|
1540
|
+
const routeItems = [];
|
|
1541
|
+
const nodeMap = new Map(); // id -> node (纯净的节点数据)
|
|
1542
|
+
const nodePathMap = new Map(); // id -> [{ code, id }] (仅存储路径计算所需的轻量数据)
|
|
1543
|
+
|
|
1544
|
+
/* ---------- 1. 建立索引:扁平化节点 & 构建路径索引 ---------- */
|
|
1545
|
+
// 使用栈进行深度优先遍历,记录路径
|
|
1546
|
+
const stack = [];
|
|
1547
|
+
if (Array.isArray(menuItems)) {
|
|
1548
|
+
menuItems.forEach((n) => stack.push({ node: n, parentPath: [] }));
|
|
1549
|
+
}
|
|
1550
|
+
|
|
1551
|
+
while (stack.length) {
|
|
1552
|
+
const { node, parentPath } = stack.pop();
|
|
1553
|
+
const idValue = node[idKey];
|
|
1554
|
+
|
|
1555
|
+
// 构建当前节点的路径片段
|
|
1556
|
+
const currentPathSegment = { [idKey]: idValue };
|
|
1557
|
+
const fullPath = [...parentPath, currentPathSegment];
|
|
1558
|
+
|
|
1559
|
+
// 存储路径信息用于后续路由生成
|
|
1560
|
+
nodePathMap.set(idValue, fullPath);
|
|
1561
|
+
|
|
1562
|
+
// 创建轻量级的节点对象存入 nodeMap
|
|
1563
|
+
// 注意:这里先创建基础结构,路由信息在第二步补充
|
|
1564
|
+
const nodeSimple = {
|
|
1565
|
+
...node,
|
|
1566
|
+
[extendKey]: {}, // 初始化扩展对象
|
|
1567
|
+
[childrenKey]: null // 去除原始 children,避免引用污染
|
|
1568
|
+
};
|
|
1569
|
+
nodeMap.set(idValue, nodeSimple);
|
|
1570
|
+
|
|
1571
|
+
// 处理子节点
|
|
1572
|
+
const childrenNodes = node[childrenKey];
|
|
1573
|
+
if (Array.isArray(childrenNodes)) {
|
|
1574
|
+
// 逆序压栈,保持原序
|
|
1575
|
+
for (let i = childrenNodes.length - 1; i >= 0; i--) {
|
|
1576
|
+
stack.push({ node: childrenNodes[i], parentPath: fullPath });
|
|
1577
|
+
}
|
|
1578
|
+
}
|
|
1579
|
+
}
|
|
1580
|
+
|
|
1581
|
+
/* ---------- 2. 构建树形结构 & 生成路由 ---------- */
|
|
1582
|
+
const buildStack = [];
|
|
1583
|
+
if (Array.isArray(menuItems)) {
|
|
1584
|
+
for (let i = menuItems.length - 1; i >= 0; i--) {
|
|
1585
|
+
buildStack.push({ node: menuItems[i], parentUi: undefined });
|
|
1586
|
+
}
|
|
1587
|
+
}
|
|
1588
|
+
|
|
1589
|
+
while (buildStack.length) {
|
|
1590
|
+
const { node, parentUi } = buildStack.pop();
|
|
1591
|
+
const idValue = node[idKey];
|
|
1592
|
+
|
|
1593
|
+
// 从 Map 中取出之前创建好的节点对象
|
|
1594
|
+
const nodeSimple = nodeMap.get(idValue);
|
|
1595
|
+
|
|
1596
|
+
// 2.1 构建 UI 菜单项
|
|
1597
|
+
const uiMenuItem = {
|
|
1598
|
+
[menuIdKey]: idValue,
|
|
1599
|
+
[menuLabelKey]: node[labelKey],
|
|
1600
|
+
[menuExtendKey]: {} // UI 菜单专用扩展
|
|
1601
|
+
};
|
|
1602
|
+
|
|
1603
|
+
// 处理子节点
|
|
1604
|
+
const childrenNodes = node[childrenKey];
|
|
1605
|
+
const hasChildren = Array.isArray(childrenNodes) && childrenNodes.length > 0;
|
|
1606
|
+
|
|
1607
|
+
if (hasChildren) {
|
|
1608
|
+
// 有子节点 -> 继续压栈,传递当前 uiMenuItem 作为父级
|
|
1609
|
+
for (let i = childrenNodes.length - 1; i >= 0; i--) {
|
|
1610
|
+
buildStack.push({ node: childrenNodes[i], parentUi: uiMenuItem });
|
|
1611
|
+
}
|
|
1612
|
+
} else {
|
|
1613
|
+
// 2.2 无子节点 -> 生成路由配置
|
|
1614
|
+
|
|
1615
|
+
// 提取 code 和 id 数组
|
|
1616
|
+
const codePaths = [],
|
|
1617
|
+
keyPaths = [];
|
|
1618
|
+
nodePathMap.get(idValue).forEach((item) => {
|
|
1619
|
+
const idValue = item[idKey];
|
|
1620
|
+
const nodeSimple = nodeMap.get(idValue);
|
|
1621
|
+
keyPaths.push(idValue);
|
|
1622
|
+
codePaths.push(nodeSimple[codeKey]);
|
|
1623
|
+
});
|
|
1624
|
+
|
|
1625
|
+
const routeName = codePaths.join(".");
|
|
1626
|
+
const routePath = "/" + codePaths.join("/");
|
|
1627
|
+
|
|
1628
|
+
const routeItem = {
|
|
1629
|
+
[routeNameKey]: routeName,
|
|
1630
|
+
[routePathKey]: routePath,
|
|
1631
|
+
[routeMetaKey]: { [idKey]: idValue }
|
|
1632
|
+
};
|
|
1633
|
+
|
|
1634
|
+
// 执行路由钩子
|
|
1635
|
+
if (typeof handleRouteItem === "function") {
|
|
1636
|
+
handleRouteItem(routeItem, nodeSimple);
|
|
1637
|
+
}
|
|
1638
|
+
routeItems.push(routeItem);
|
|
1639
|
+
|
|
1640
|
+
// 补充扩展信息
|
|
1641
|
+
uiMenuItem[menuExtendKey].routeName = routeName;
|
|
1642
|
+
uiMenuItem[menuExtendKey].keyPaths = keyPaths;
|
|
1643
|
+
|
|
1644
|
+
nodeSimple[extendKey].routeName = routeName;
|
|
1645
|
+
nodeSimple[extendKey].keyPaths = keyPaths;
|
|
1646
|
+
}
|
|
1647
|
+
|
|
1648
|
+
// 2.3 挂载到父级 UI 或根数组
|
|
1649
|
+
if (parentUi) {
|
|
1650
|
+
// 逻辑赋值,确保 children 数组存在
|
|
1651
|
+
(parentUi[menuChildrenKey] || (parentUi[menuChildrenKey] = [])).push(uiMenuItem);
|
|
1652
|
+
} else {
|
|
1653
|
+
if (typeof handleMenuItem === "function") {
|
|
1654
|
+
handleMenuItem(uiMenuItem, nodeSimple);
|
|
1655
|
+
}
|
|
1656
|
+
uiMenuItems.push(uiMenuItem);
|
|
1657
|
+
}
|
|
1658
|
+
|
|
1659
|
+
// 执行节点钩子
|
|
1660
|
+
if (typeof handleNodeItem === "function") {
|
|
1661
|
+
handleNodeItem(nodeSimple);
|
|
1662
|
+
}
|
|
1663
|
+
}
|
|
1664
|
+
|
|
1665
|
+
return { uiMenuItems, routeItems, nodeMap };
|
|
1666
|
+
}
|
|
1667
|
+
|
|
1470
1668
|
/**
|
|
1471
1669
|
* 基于 `setTimeout` 的“间隔循环”定时器。
|
|
1472
1670
|
* 每次任务执行完成后才计算下一次间隔,避免任务堆积。
|
|
@@ -1587,31 +1785,45 @@ function flatCompleteTree2NestedTree(nodes, parentId = 0, { idKey = "id", parent
|
|
|
1587
1785
|
}
|
|
1588
1786
|
|
|
1589
1787
|
/**
|
|
1590
|
-
* 在嵌套树中按 `id`
|
|
1788
|
+
* 在嵌套树中按 `id` 递归查找节点
|
|
1591
1789
|
*
|
|
1592
1790
|
* @template T extends Record<PropertyKey, any>
|
|
1593
1791
|
* @param {string | number} id - 要查找的 id
|
|
1594
1792
|
* @param {T[]} arr - 嵌套树森林
|
|
1595
|
-
* @param {string} [resultKey='name'] - 需要返回的字段
|
|
1596
1793
|
* @param {string} [idKey='id'] - 主键字段
|
|
1597
1794
|
* @param {string} [childrenKey='children'] - 子节点字段
|
|
1598
|
-
* @returns {
|
|
1795
|
+
* @returns {T | undefined} 找到的节点;未找到返回 `undefined`
|
|
1599
1796
|
*/
|
|
1600
|
-
|
|
1797
|
+
function findTreeNodeById(id, arr, idKey = "id", childrenKey = "children") {
|
|
1601
1798
|
if (Array.isArray(arr) && arr.length > 0) {
|
|
1602
1799
|
for (let i = 0; i < arr.length; i++) {
|
|
1603
1800
|
const item = arr[i];
|
|
1604
1801
|
if (item[idKey]?.toString() === id?.toString()) {
|
|
1605
|
-
return item
|
|
1802
|
+
return item;
|
|
1606
1803
|
} else if (Array.isArray(item[childrenKey]) && item[childrenKey].length > 0) {
|
|
1607
|
-
const result =
|
|
1804
|
+
const result = findTreeNodeById(id, item[childrenKey], idKey, childrenKey);
|
|
1608
1805
|
if (result) {
|
|
1609
1806
|
return result;
|
|
1610
1807
|
}
|
|
1611
1808
|
}
|
|
1612
1809
|
}
|
|
1613
1810
|
}
|
|
1614
|
-
}
|
|
1811
|
+
}
|
|
1812
|
+
|
|
1813
|
+
/**
|
|
1814
|
+
* 在嵌套树中按 `id` 递归查找节点,并返回其指定属性值。
|
|
1815
|
+
*
|
|
1816
|
+
* @template T extends Record<PropertyKey, any>
|
|
1817
|
+
* @param {string | number} id - 要查找的 id
|
|
1818
|
+
* @param {T[]} arr - 嵌套树森林
|
|
1819
|
+
* @param {string} [resultKey='name'] - 需要返回的字段
|
|
1820
|
+
* @param {string} [idKey='id'] - 主键字段
|
|
1821
|
+
* @param {string} [childrenKey='children'] - 子节点字段
|
|
1822
|
+
* @returns {any} 找到的值;未找到返回 `undefined`
|
|
1823
|
+
*/
|
|
1824
|
+
function findObjAttrValueById(id, arr, resultKey = "name", idKey = "id", childrenKey = "children") {
|
|
1825
|
+
return findTreeNodeById(id, arr, idKey, childrenKey)?.[resultKey];
|
|
1826
|
+
}
|
|
1615
1827
|
|
|
1616
1828
|
/**
|
|
1617
1829
|
* 从服务端返回的已选 id 数组里,提取出
|
|
@@ -2092,5 +2304,5 @@ class WebSocketManager {
|
|
|
2092
2304
|
}
|
|
2093
2305
|
}
|
|
2094
2306
|
|
|
2095
|
-
export { AudioStreamResampler, IntervalTimer, MyEvent, MyEvent_CrossPagePlugin, MyId, WebSocketManager, assignExisting, debounce, deepCloneByJSON, downloadByBlob, downloadByData, downloadByUrl, downloadExcel, downloadJSON, extractFullyCheckedKeys, fetchOrDownloadByUrl, findObjAttrValueById, flatCompleteTree2NestedTree, formatTimeForLocale, getAllSearchParams, getDataType, getFunctionArgNames, getGUID, getSearchParam, getTimePeriodName, getViewportSize, isBlob, isDate, isFunction, isNonEmptyString, isPlainObject, isPromise, millisecond2Duration, millisecond2DurationMaxDay, millisecond2DurationMaxHour, moveItem, nestedTree2IdMap, pcmToWavBlob, randomDateInRange, randomEnLetter, randomHan, randomHanOrEn, randomIntInRange, readBlobAsText, second2Duration, second2DurationMaxDay, second2DurationMaxHour, shuffle, throttle, toDate };
|
|
2307
|
+
export { AudioStreamResampler, IntervalTimer, MyEvent, MyEvent_CrossPagePlugin, MyId, WebSocketManager, assignExisting, debounce, deepCloneByJSON, downloadByBlob, downloadByData, downloadByUrl, downloadExcel, downloadJSON, extractFullyCheckedKeys, fetchOrDownloadByUrl, findObjAttrValueById, findTreeNodeById, flatCompleteTree2NestedTree, formatTimeForLocale, getAllSearchParams, getDataType, getFunctionArgNames, getGUID, getSearchParam, getTimePeriodName, getViewportSize, handleDbMenuItems, isBlob, isDate, isFunction, isNonEmptyString, isPlainObject, isPromise, millisecond2Duration, millisecond2DurationMaxDay, millisecond2DurationMaxHour, moveItem, nestedTree2IdMap, pcmToWavBlob, randomDateInRange, randomEnLetter, randomHan, randomHanOrEn, randomIntInRange, readBlobAsText, second2Duration, second2DurationMaxDay, second2DurationMaxHour, shuffle, throttle, toDate };
|
|
2096
2308
|
//# sourceMappingURL=a2bei4.utils.esm.js.map
|