a2bei4-utils 1.0.3 → 1.0.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.
- package/dist/a2bei4.utils.cjs.js +415 -110
- 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 +408 -107
- 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 +415 -110
- 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/date.cjs +191 -100
- package/dist/date.cjs.map +1 -1
- package/dist/date.js +186 -97
- package/dist/date.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 +21 -6
- package/dist/tree.cjs.map +1 -1
- package/dist/tree.js +21 -7
- package/dist/tree.js.map +1 -1
- package/package.json +12 -7
- package/types/date.d.ts +186 -47
- package/types/index.d.ts +259 -48
- package/types/menu.d.ts +62 -0
- package/types/tree.d.ts +14 -2
package/dist/a2bei4.utils.umd.js
CHANGED
|
@@ -803,133 +803,222 @@ registerProcessor('audio-stream-resampler-processor', AudioStreamResamplerProces
|
|
|
803
803
|
return new Date(v1 + Math.floor(Math.random() * (v2 - v1 + 1)));
|
|
804
804
|
}
|
|
805
805
|
|
|
806
|
+
//#region 持续时间相关
|
|
807
|
+
|
|
808
|
+
/**
|
|
809
|
+
* 时间持续对象(完整版本,包含年月日时分秒毫秒)
|
|
810
|
+
* @typedef {Object} DurationObject
|
|
811
|
+
* @property {number} years - 年数
|
|
812
|
+
* @property {number} months - 月数(0-11)
|
|
813
|
+
* @property {number} days - 天数(0-29,取决于 monthDays)
|
|
814
|
+
* @property {number} hours - 小时数(0-23)
|
|
815
|
+
* @property {number} minutes - 分钟数(0-59)
|
|
816
|
+
* @property {number} seconds - 秒数(0-59)
|
|
817
|
+
* @property {number} milliseconds - 毫秒数(0-999)
|
|
818
|
+
*/
|
|
819
|
+
|
|
806
820
|
/**
|
|
807
|
-
*
|
|
821
|
+
* 时间持续对象(最大单位为天)
|
|
822
|
+
* @typedef {Object} DurationMaxDayObject
|
|
823
|
+
* @property {number} days - 天数
|
|
824
|
+
* @property {number} hours - 小时数(0-23)
|
|
825
|
+
* @property {number} minutes - 分钟数(0-59)
|
|
826
|
+
* @property {number} seconds - 秒数(0-59)
|
|
827
|
+
* @property {number} milliseconds - 毫秒数(0-999)
|
|
828
|
+
*/
|
|
829
|
+
|
|
830
|
+
/**
|
|
831
|
+
* 时间持续对象(最大单位为小时)
|
|
832
|
+
* @typedef {Object} DurationMaxHourObject
|
|
833
|
+
* @property {number} hours - 小时数
|
|
834
|
+
* @property {number} minutes - 分钟数(0-59)
|
|
835
|
+
* @property {number} seconds - 秒数(0-59)
|
|
836
|
+
* @property {number} milliseconds - 毫秒数(0-999)
|
|
837
|
+
*/
|
|
838
|
+
|
|
839
|
+
/**
|
|
840
|
+
* 将毫秒转换为时间持续对象。
|
|
808
841
|
*
|
|
809
|
-
* @param {
|
|
810
|
-
* @param {
|
|
811
|
-
* @
|
|
842
|
+
* @param {number} milliseconds - 毫秒数(非负整数)
|
|
843
|
+
* @param {Object} [options] - 选项对象。
|
|
844
|
+
* @param {number} [options.yearDays=365] - 一年的天数。
|
|
845
|
+
* @param {number} [options.monthDays=30] - 一月的天数。
|
|
846
|
+
* @returns {DurationObject} 时间持续对象
|
|
847
|
+
* @throws {TypeError} 当 milliseconds 不是有效数字
|
|
848
|
+
* @throws {RangeError} 当 milliseconds 为负数
|
|
849
|
+
* @throws {RangeError} 当 yearDays 或 monthDays 不是正整数
|
|
850
|
+
*
|
|
851
|
+
* @example
|
|
852
|
+
* // 基本用法
|
|
853
|
+
* millisecond2Duration(42070000500);
|
|
854
|
+
* // 返回: { years: 1, months: 4, days: 1, hours: 22, minutes: 6, seconds: 40, milliseconds: 500 }
|
|
812
855
|
*/
|
|
813
|
-
function
|
|
814
|
-
//
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
856
|
+
function millisecond2Duration(milliseconds, options = { yearDays: 365, monthDays: 30 }) {
|
|
857
|
+
// 参数验证
|
|
858
|
+
if (typeof milliseconds !== "number" || isNaN(milliseconds)) {
|
|
859
|
+
throw new TypeError("milliseconds must be a valid number");
|
|
860
|
+
}
|
|
861
|
+
if (milliseconds < 0) {
|
|
862
|
+
throw new RangeError("milliseconds must be a non-negative number");
|
|
863
|
+
}
|
|
864
|
+
|
|
865
|
+
// 默认选项
|
|
866
|
+
const { yearDays = 365, monthDays = 30 } = options;
|
|
867
|
+
|
|
868
|
+
// 选项验证
|
|
869
|
+
if (!Number.isInteger(yearDays) || yearDays <= 0) {
|
|
870
|
+
throw new RangeError("yearDays must be a positive integer");
|
|
871
|
+
}
|
|
872
|
+
if (!Number.isInteger(monthDays) || monthDays <= 0) {
|
|
873
|
+
throw new RangeError("monthDays must be a positive integer");
|
|
874
|
+
}
|
|
875
|
+
|
|
876
|
+
const totalMilliseconds = Math.floor(milliseconds);
|
|
877
|
+
const ms = totalMilliseconds % 1000;
|
|
878
|
+
const diffSeconds = Math.floor(totalMilliseconds / 1000);
|
|
879
|
+
|
|
822
880
|
const seconds = diffSeconds % 60;
|
|
881
|
+
const minutes = Math.floor(diffSeconds / 60) % 60;
|
|
882
|
+
const hours = Math.floor(diffSeconds / 3600) % 24;
|
|
823
883
|
|
|
824
|
-
|
|
884
|
+
// 计算年、月、日
|
|
885
|
+
const totalDays = Math.floor(diffSeconds / 3600 / 24);
|
|
886
|
+
const years = Math.floor(totalDays / yearDays);
|
|
887
|
+
const remainingDays = totalDays % yearDays;
|
|
888
|
+
const months = Math.floor(remainingDays / monthDays);
|
|
889
|
+
const days = remainingDays % monthDays;
|
|
825
890
|
|
|
826
|
-
return {
|
|
827
|
-
days,
|
|
828
|
-
hours: padZero(hours),
|
|
829
|
-
minutes: padZero(minutes),
|
|
830
|
-
seconds: padZero(seconds)
|
|
831
|
-
};
|
|
891
|
+
return { years, months, days, hours, minutes, seconds, milliseconds: ms };
|
|
832
892
|
}
|
|
833
893
|
|
|
834
894
|
/**
|
|
835
|
-
*
|
|
836
|
-
*
|
|
837
|
-
*
|
|
838
|
-
* @
|
|
839
|
-
* @
|
|
840
|
-
* @
|
|
841
|
-
*
|
|
842
|
-
*
|
|
843
|
-
* @param {boolean} [options.showZero] - 是否强制显示 0 秒
|
|
844
|
-
* @returns {string} 拼接后的时长文本,如“1天 02小时 30分钟”
|
|
845
|
-
* @throws {TypeError} 当 totalSeconds 为非数字或负数时抛出
|
|
895
|
+
* 将毫秒转换为时间持续对象(最大单位为天)。
|
|
896
|
+
* @param {number} milliseconds - 毫秒数(非负整数)
|
|
897
|
+
* @returns {DurationMaxDayObject} 包含天、小时、分钟、秒、毫秒的时间持续对象
|
|
898
|
+
* @throws {TypeError} 当 milliseconds 不是有效数字时抛出
|
|
899
|
+
* @throws {RangeError} 当 milliseconds 为负数时抛出
|
|
900
|
+
* @example
|
|
901
|
+
* // 返回 { days: 486, hours: 22, minutes: 6, seconds: 40, milliseconds: 500 }
|
|
902
|
+
* millisecond2DurationMaxDay(42070000500);
|
|
846
903
|
*/
|
|
847
|
-
function
|
|
848
|
-
if (typeof
|
|
849
|
-
throw new TypeError("
|
|
904
|
+
function millisecond2DurationMaxDay(milliseconds) {
|
|
905
|
+
if (typeof milliseconds !== "number" || isNaN(milliseconds)) {
|
|
906
|
+
throw new TypeError("milliseconds must be a valid number");
|
|
907
|
+
}
|
|
908
|
+
if (milliseconds < 0) {
|
|
909
|
+
throw new RangeError("milliseconds must be a non-negative number");
|
|
850
910
|
}
|
|
851
911
|
|
|
852
|
-
|
|
853
|
-
const
|
|
854
|
-
|
|
855
|
-
month: "月",
|
|
856
|
-
day: "天",
|
|
857
|
-
hour: "小时",
|
|
858
|
-
minute: "分钟",
|
|
859
|
-
second: "秒"
|
|
860
|
-
};
|
|
912
|
+
const totalMilliseconds = Math.floor(milliseconds);
|
|
913
|
+
const ms = totalMilliseconds % 1000;
|
|
914
|
+
const diffSeconds = Math.floor(totalMilliseconds / 1000);
|
|
861
915
|
|
|
862
|
-
|
|
863
|
-
const
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
{ key: "second", seconds: 1 }
|
|
870
|
-
];
|
|
871
|
-
|
|
872
|
-
// 3. 合并用户自定义文本
|
|
873
|
-
const labels = Object.assign({}, DEFAULT_LABELS, options.labels);
|
|
874
|
-
|
|
875
|
-
// 4. 根据 maxUnit / minUnit 截取
|
|
876
|
-
let start = 0,
|
|
877
|
-
end = UNIT_TABLE.length;
|
|
878
|
-
if (options.maxUnit) {
|
|
879
|
-
const idx = UNIT_TABLE.findIndex((u) => u.key === options.maxUnit);
|
|
880
|
-
if (idx !== -1) start = idx;
|
|
881
|
-
}
|
|
882
|
-
if (options.minUnit) {
|
|
883
|
-
const idx = UNIT_TABLE.findIndex((u) => u.key === options.minUnit);
|
|
884
|
-
if (idx !== -1) end = idx + 1;
|
|
885
|
-
}
|
|
886
|
-
const units = UNIT_TABLE.slice(start, end);
|
|
887
|
-
if (!units.length) units.push(UNIT_TABLE[UNIT_TABLE.length - 1]); // 保底秒
|
|
888
|
-
|
|
889
|
-
// 5. 逐级计算
|
|
890
|
-
let rest = Math.floor(totalSeconds);
|
|
891
|
-
const parts = [];
|
|
892
|
-
|
|
893
|
-
for (const { key, seconds } of units) {
|
|
894
|
-
const val = Math.floor(rest / seconds);
|
|
895
|
-
rest %= seconds;
|
|
896
|
-
|
|
897
|
-
const shouldShow = val > 0 || (options.showZero && key === "second");
|
|
898
|
-
if (shouldShow || (parts.length === 0 && rest === 0)) {
|
|
899
|
-
parts.push(`${val}${labels[key]}`);
|
|
900
|
-
}
|
|
901
|
-
}
|
|
916
|
+
const seconds = diffSeconds % 60;
|
|
917
|
+
const minutes = Math.floor(diffSeconds / 60) % 60;
|
|
918
|
+
const hours = Math.floor(diffSeconds / 3600) % 24;
|
|
919
|
+
const days = Math.floor(diffSeconds / 3600 / 24);
|
|
920
|
+
|
|
921
|
+
return { days, hours, minutes, seconds, milliseconds: ms };
|
|
922
|
+
}
|
|
902
923
|
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
924
|
+
/**
|
|
925
|
+
* 将毫秒转换为时间持续对象(最大单位为小时)。
|
|
926
|
+
* @param {number} milliseconds - 毫秒数(非负整数)
|
|
927
|
+
* @returns {DurationMaxHourObject} 包含小时、分钟、秒、毫秒的时间持续对象
|
|
928
|
+
* @throws {TypeError} 当 milliseconds 不是有效数字时抛出
|
|
929
|
+
* @throws {RangeError} 当 milliseconds 为负数时抛出
|
|
930
|
+
* @example
|
|
931
|
+
* // 返回 { hours: 11686, minutes: 6, seconds: 40, milliseconds: 500 }
|
|
932
|
+
* millisecond2DurationMaxHour(42070000500);
|
|
933
|
+
*/
|
|
934
|
+
function millisecond2DurationMaxHour(milliseconds) {
|
|
935
|
+
if (typeof milliseconds !== "number" || isNaN(milliseconds)) {
|
|
936
|
+
throw new TypeError("milliseconds must be a valid number");
|
|
906
937
|
}
|
|
938
|
+
if (milliseconds < 0) {
|
|
939
|
+
throw new RangeError("milliseconds must be a non-negative number");
|
|
940
|
+
}
|
|
941
|
+
|
|
942
|
+
const totalMilliseconds = Math.floor(milliseconds);
|
|
943
|
+
const ms = totalMilliseconds % 1000;
|
|
944
|
+
const diffSeconds = Math.floor(totalMilliseconds / 1000);
|
|
907
945
|
|
|
908
|
-
|
|
946
|
+
const seconds = diffSeconds % 60;
|
|
947
|
+
const minutes = Math.floor(diffSeconds / 60) % 60;
|
|
948
|
+
const hours = Math.floor(diffSeconds / 3600);
|
|
949
|
+
|
|
950
|
+
return { hours, minutes, seconds, milliseconds: ms };
|
|
909
951
|
}
|
|
910
952
|
|
|
911
953
|
/**
|
|
912
|
-
*
|
|
954
|
+
* 将秒转换为时间持续对象。
|
|
913
955
|
*
|
|
914
|
-
* @param {number}
|
|
915
|
-
* @param {
|
|
916
|
-
* @
|
|
956
|
+
* @param {number} seconds - 秒数(非负整数)
|
|
957
|
+
* @param {Object} [options] - 选项对象。
|
|
958
|
+
* @param {number} [options.yearDays=365] - 一年的天数。
|
|
959
|
+
* @param {number} [options.monthDays=30] - 一月的天数。
|
|
960
|
+
* @returns {DurationObject} 时间持续对象
|
|
961
|
+
* @throws {TypeError} 当 seconds 不是有效数字
|
|
962
|
+
* @throws {RangeError} 当 seconds 为负数
|
|
963
|
+
* @throws {RangeError} 当 yearDays 或 monthDays 不是正整数
|
|
964
|
+
*
|
|
965
|
+
* @example
|
|
966
|
+
* // 基本用法
|
|
967
|
+
* second2Duration(42070000.5);
|
|
968
|
+
* // 返回: { years: 1, months: 4, days: 1, hours: 22, minutes: 6, seconds: 40, milliseconds: 500 }
|
|
917
969
|
*/
|
|
918
|
-
function
|
|
919
|
-
|
|
970
|
+
function second2Duration(seconds, options = { yearDays: 365, monthDays: 30 }) {
|
|
971
|
+
if (typeof seconds !== "number" || isNaN(seconds)) {
|
|
972
|
+
throw new TypeError("seconds must be a valid number");
|
|
973
|
+
}
|
|
974
|
+
if (seconds < 0) {
|
|
975
|
+
throw new RangeError("seconds must be a non-negative number");
|
|
976
|
+
}
|
|
977
|
+
return millisecond2Duration(seconds * 1000, options);
|
|
920
978
|
}
|
|
921
979
|
|
|
922
980
|
/**
|
|
923
|
-
*
|
|
924
|
-
*
|
|
925
|
-
* @
|
|
926
|
-
* @
|
|
927
|
-
* @
|
|
981
|
+
* 将秒转换为时间持续对象(最大单位为天)。
|
|
982
|
+
* @param {number} seconds - 秒数(非负整数)
|
|
983
|
+
* @returns {DurationMaxDayObject} 包含天、小时、分钟、秒、毫秒的时间持续对象
|
|
984
|
+
* @throws {TypeError} 当 seconds 不是有效数字时抛出
|
|
985
|
+
* @throws {RangeError} 当 seconds 为负数时抛出
|
|
986
|
+
* @example
|
|
987
|
+
* // 返回 { days: 486, hours: 22, minutes: 6, seconds: 40, milliseconds: 500 }
|
|
988
|
+
* second2DurationMaxDay(42070000.5);
|
|
989
|
+
*/
|
|
990
|
+
function second2DurationMaxDay(seconds) {
|
|
991
|
+
if (typeof seconds !== "number" || isNaN(seconds)) {
|
|
992
|
+
throw new TypeError("seconds must be a valid number");
|
|
993
|
+
}
|
|
994
|
+
if (seconds < 0) {
|
|
995
|
+
throw new RangeError("seconds must be a non-negative number");
|
|
996
|
+
}
|
|
997
|
+
return millisecond2DurationMaxDay(seconds * 1000);
|
|
998
|
+
}
|
|
999
|
+
|
|
1000
|
+
/**
|
|
1001
|
+
* 将秒转换为时间持续对象(最大单位为小时)。
|
|
1002
|
+
* @param {number} seconds - 秒数(非负整数)
|
|
1003
|
+
* @returns {DurationMaxHourObject} 包含小时、分钟、秒、毫秒的时间持续对象
|
|
1004
|
+
* @throws {TypeError} 当 seconds 不是有效数字时抛出
|
|
1005
|
+
* @throws {RangeError} 当 seconds 为负数时抛出
|
|
1006
|
+
* @example
|
|
1007
|
+
* // 返回 { hours: 11686, minutes: 6, seconds: 40, milliseconds: 500 }
|
|
1008
|
+
* second2DurationMaxHour(42070000.5);
|
|
928
1009
|
*/
|
|
929
|
-
function
|
|
930
|
-
|
|
1010
|
+
function second2DurationMaxHour(seconds) {
|
|
1011
|
+
if (typeof seconds !== "number" || isNaN(seconds)) {
|
|
1012
|
+
throw new TypeError("seconds must be a valid number");
|
|
1013
|
+
}
|
|
1014
|
+
if (seconds < 0) {
|
|
1015
|
+
throw new RangeError("seconds must be a non-negative number");
|
|
1016
|
+
}
|
|
1017
|
+
return millisecond2DurationMaxHour(seconds * 1000);
|
|
931
1018
|
}
|
|
932
1019
|
|
|
1020
|
+
//#endregion
|
|
1021
|
+
|
|
933
1022
|
/**
|
|
934
1023
|
* 根据小时数返回对应的时间段名称。
|
|
935
1024
|
*
|
|
@@ -1384,6 +1473,204 @@ registerProcessor('audio-stream-resampler-processor', AudioStreamResamplerProces
|
|
|
1384
1473
|
}
|
|
1385
1474
|
}
|
|
1386
1475
|
|
|
1476
|
+
/**
|
|
1477
|
+
* 处理数据库菜单项,生成 UI 菜单树和路由配置。
|
|
1478
|
+
*
|
|
1479
|
+
* @param {Array<Object>} menuItems - 原始菜单项数组,树形结构
|
|
1480
|
+
* @param {Object} [options] - 配置选项
|
|
1481
|
+
* @param {string} [options.idKey='id'] - 数据源 ID 键名
|
|
1482
|
+
* @param {string} [options.codeKey='code'] - 数据源编码键名, 用于拼接路由名称和路由路径
|
|
1483
|
+
* @param {string} [options.labelKey='text'] - 数据源标签键名
|
|
1484
|
+
* @param {string} [options.childrenKey='children'] - 数据源子节点键名
|
|
1485
|
+
* @param {string} [options.extendKey='extend'] - 数据源扩展对象键名
|
|
1486
|
+
* @param {string} [options.menuIdKey='key'] - 输出菜单 ID 键名
|
|
1487
|
+
* @param {string} [options.menuLabelKey='label'] - 输出菜单标签键名
|
|
1488
|
+
* @param {string} [options.menuChildrenKey='children'] - 输出菜单子节点键名
|
|
1489
|
+
* @param {string} [options.menuExtendKey='extend'] - 输出菜单扩展对象键名
|
|
1490
|
+
* @param {string} [options.routeNameKey='name'] - 路由名称键名
|
|
1491
|
+
* @param {string} [options.routePathKey='path'] - 路由路径键名
|
|
1492
|
+
* @param {string} [options.routeMetaKey='meta'] - 路由元数据键名
|
|
1493
|
+
* @param {Function} [options.handleNodeItem] - 节点处理钩子,参数:(nodeSimple) => void
|
|
1494
|
+
* @param {Function} [options.handleMenuItem] - 菜单项处理钩子,参数:(uiMenuItem, nodeSimple) => void
|
|
1495
|
+
* @param {Function} [options.handleRouteItem] - 路由项处理钩子,参数:(routeItem, nodeSimple) => void
|
|
1496
|
+
*
|
|
1497
|
+
* @returns {Object} 处理结果
|
|
1498
|
+
* @returns {Array<Object>} returns.uiMenuItems - UI 菜单树形结构数组
|
|
1499
|
+
* @returns {Array<Object>} returns.routeItems - 扁平化路由配置数组
|
|
1500
|
+
* @returns {Map<string, Object>} returns.nodeMap - 节点 ID 到节点数据的映射表
|
|
1501
|
+
*
|
|
1502
|
+
* @example
|
|
1503
|
+
* const menuItems = [
|
|
1504
|
+
* { id: '1', code: 'system', text: '系统管理', children: [
|
|
1505
|
+
* { id: '1-1', code: 'user', text: '用户管理' }
|
|
1506
|
+
* ]}
|
|
1507
|
+
* ];
|
|
1508
|
+
*
|
|
1509
|
+
* const result = handleDbMenuItems(menuItems, {
|
|
1510
|
+
* handleRouteItem: (route, node) => {
|
|
1511
|
+
* route.component = () => import(`./views/${node.code}.vue`);
|
|
1512
|
+
* }
|
|
1513
|
+
* });
|
|
1514
|
+
*
|
|
1515
|
+
* // result.uiMenuItems: [{ key: '1', label: '系统管理', children: [...] }]
|
|
1516
|
+
* // result.routeItems: [{ name: 'system.user', path: '/system/user', meta: { id: '1-1' } }]
|
|
1517
|
+
* // result.nodeMap: Map { '1' => {...}, '1-1' => {...} }
|
|
1518
|
+
*/
|
|
1519
|
+
function handleDbMenuItems(menuItems, options = {}) {
|
|
1520
|
+
// 1. 统一配置项,简化调用
|
|
1521
|
+
const {
|
|
1522
|
+
// 数据源键名
|
|
1523
|
+
idKey = "id",
|
|
1524
|
+
codeKey = "code",
|
|
1525
|
+
labelKey = "text",
|
|
1526
|
+
childrenKey = "children",
|
|
1527
|
+
extendKey = "extend",
|
|
1528
|
+
|
|
1529
|
+
// 输出目标键名
|
|
1530
|
+
menuIdKey = "key",
|
|
1531
|
+
menuLabelKey = "label",
|
|
1532
|
+
menuChildrenKey = "children",
|
|
1533
|
+
menuExtendKey = "extend",
|
|
1534
|
+
|
|
1535
|
+
routeNameKey = "name",
|
|
1536
|
+
routePathKey = "path",
|
|
1537
|
+
routeMetaKey = "meta",
|
|
1538
|
+
|
|
1539
|
+
// 钩子函数
|
|
1540
|
+
handleNodeItem,
|
|
1541
|
+
handleMenuItem,
|
|
1542
|
+
handleRouteItem
|
|
1543
|
+
} = options;
|
|
1544
|
+
|
|
1545
|
+
const uiMenuItems = [];
|
|
1546
|
+
const routeItems = [];
|
|
1547
|
+
const nodeMap = new Map(); // id -> node (纯净的节点数据)
|
|
1548
|
+
const nodePathMap = new Map(); // id -> [{ code, id }] (仅存储路径计算所需的轻量数据)
|
|
1549
|
+
|
|
1550
|
+
/* ---------- 1. 建立索引:扁平化节点 & 构建路径索引 ---------- */
|
|
1551
|
+
// 使用栈进行深度优先遍历,记录路径
|
|
1552
|
+
const stack = [];
|
|
1553
|
+
if (Array.isArray(menuItems)) {
|
|
1554
|
+
menuItems.forEach((n) => stack.push({ node: n, parentPath: [] }));
|
|
1555
|
+
}
|
|
1556
|
+
|
|
1557
|
+
while (stack.length) {
|
|
1558
|
+
const { node, parentPath } = stack.pop();
|
|
1559
|
+
const idValue = node[idKey];
|
|
1560
|
+
|
|
1561
|
+
// 构建当前节点的路径片段
|
|
1562
|
+
const currentPathSegment = { [idKey]: idValue };
|
|
1563
|
+
const fullPath = [...parentPath, currentPathSegment];
|
|
1564
|
+
|
|
1565
|
+
// 存储路径信息用于后续路由生成
|
|
1566
|
+
nodePathMap.set(idValue, fullPath);
|
|
1567
|
+
|
|
1568
|
+
// 创建轻量级的节点对象存入 nodeMap
|
|
1569
|
+
// 注意:这里先创建基础结构,路由信息在第二步补充
|
|
1570
|
+
const nodeSimple = {
|
|
1571
|
+
...node,
|
|
1572
|
+
[extendKey]: {}, // 初始化扩展对象
|
|
1573
|
+
[childrenKey]: null // 去除原始 children,避免引用污染
|
|
1574
|
+
};
|
|
1575
|
+
nodeMap.set(idValue, nodeSimple);
|
|
1576
|
+
|
|
1577
|
+
// 处理子节点
|
|
1578
|
+
const childrenNodes = node[childrenKey];
|
|
1579
|
+
if (Array.isArray(childrenNodes)) {
|
|
1580
|
+
// 逆序压栈,保持原序
|
|
1581
|
+
for (let i = childrenNodes.length - 1; i >= 0; i--) {
|
|
1582
|
+
stack.push({ node: childrenNodes[i], parentPath: fullPath });
|
|
1583
|
+
}
|
|
1584
|
+
}
|
|
1585
|
+
}
|
|
1586
|
+
|
|
1587
|
+
/* ---------- 2. 构建树形结构 & 生成路由 ---------- */
|
|
1588
|
+
const buildStack = [];
|
|
1589
|
+
if (Array.isArray(menuItems)) {
|
|
1590
|
+
for (let i = menuItems.length - 1; i >= 0; i--) {
|
|
1591
|
+
buildStack.push({ node: menuItems[i], parentUi: undefined });
|
|
1592
|
+
}
|
|
1593
|
+
}
|
|
1594
|
+
|
|
1595
|
+
while (buildStack.length) {
|
|
1596
|
+
const { node, parentUi } = buildStack.pop();
|
|
1597
|
+
const idValue = node[idKey];
|
|
1598
|
+
|
|
1599
|
+
// 从 Map 中取出之前创建好的节点对象
|
|
1600
|
+
const nodeSimple = nodeMap.get(idValue);
|
|
1601
|
+
|
|
1602
|
+
// 2.1 构建 UI 菜单项
|
|
1603
|
+
const uiMenuItem = {
|
|
1604
|
+
[menuIdKey]: idValue,
|
|
1605
|
+
[menuLabelKey]: node[labelKey],
|
|
1606
|
+
[menuExtendKey]: {} // UI 菜单专用扩展
|
|
1607
|
+
};
|
|
1608
|
+
|
|
1609
|
+
// 处理子节点
|
|
1610
|
+
const childrenNodes = node[childrenKey];
|
|
1611
|
+
const hasChildren = Array.isArray(childrenNodes) && childrenNodes.length > 0;
|
|
1612
|
+
|
|
1613
|
+
if (hasChildren) {
|
|
1614
|
+
// 有子节点 -> 继续压栈,传递当前 uiMenuItem 作为父级
|
|
1615
|
+
for (let i = childrenNodes.length - 1; i >= 0; i--) {
|
|
1616
|
+
buildStack.push({ node: childrenNodes[i], parentUi: uiMenuItem });
|
|
1617
|
+
}
|
|
1618
|
+
} else {
|
|
1619
|
+
// 2.2 无子节点 -> 生成路由配置
|
|
1620
|
+
|
|
1621
|
+
// 提取 code 和 id 数组
|
|
1622
|
+
const codePaths = [],
|
|
1623
|
+
keyPaths = [];
|
|
1624
|
+
nodePathMap.get(idValue).forEach((item) => {
|
|
1625
|
+
const idValue = item[idKey];
|
|
1626
|
+
const nodeSimple = nodeMap.get(idValue);
|
|
1627
|
+
keyPaths.push(idValue);
|
|
1628
|
+
codePaths.push(nodeSimple[codeKey]);
|
|
1629
|
+
});
|
|
1630
|
+
|
|
1631
|
+
const routeName = codePaths.join(".");
|
|
1632
|
+
const routePath = "/" + codePaths.join("/");
|
|
1633
|
+
|
|
1634
|
+
const routeItem = {
|
|
1635
|
+
[routeNameKey]: routeName,
|
|
1636
|
+
[routePathKey]: routePath,
|
|
1637
|
+
[routeMetaKey]: { [idKey]: idValue }
|
|
1638
|
+
};
|
|
1639
|
+
|
|
1640
|
+
// 执行路由钩子
|
|
1641
|
+
if (typeof handleRouteItem === "function") {
|
|
1642
|
+
handleRouteItem(routeItem, nodeSimple);
|
|
1643
|
+
}
|
|
1644
|
+
routeItems.push(routeItem);
|
|
1645
|
+
|
|
1646
|
+
// 补充扩展信息
|
|
1647
|
+
uiMenuItem[menuExtendKey].routeName = routeName;
|
|
1648
|
+
uiMenuItem[menuExtendKey].keyPaths = keyPaths;
|
|
1649
|
+
|
|
1650
|
+
nodeSimple[extendKey].routeName = routeName;
|
|
1651
|
+
nodeSimple[extendKey].keyPaths = keyPaths;
|
|
1652
|
+
}
|
|
1653
|
+
|
|
1654
|
+
// 2.3 挂载到父级 UI 或根数组
|
|
1655
|
+
if (parentUi) {
|
|
1656
|
+
// 逻辑赋值,确保 children 数组存在
|
|
1657
|
+
(parentUi[menuChildrenKey] || (parentUi[menuChildrenKey] = [])).push(uiMenuItem);
|
|
1658
|
+
} else {
|
|
1659
|
+
if (typeof handleMenuItem === "function") {
|
|
1660
|
+
handleMenuItem(uiMenuItem, nodeSimple);
|
|
1661
|
+
}
|
|
1662
|
+
uiMenuItems.push(uiMenuItem);
|
|
1663
|
+
}
|
|
1664
|
+
|
|
1665
|
+
// 执行节点钩子
|
|
1666
|
+
if (typeof handleNodeItem === "function") {
|
|
1667
|
+
handleNodeItem(nodeSimple);
|
|
1668
|
+
}
|
|
1669
|
+
}
|
|
1670
|
+
|
|
1671
|
+
return { uiMenuItems, routeItems, nodeMap };
|
|
1672
|
+
}
|
|
1673
|
+
|
|
1387
1674
|
/**
|
|
1388
1675
|
* 基于 `setTimeout` 的“间隔循环”定时器。
|
|
1389
1676
|
* 每次任务执行完成后才计算下一次间隔,避免任务堆积。
|
|
@@ -1504,24 +1791,23 @@ registerProcessor('audio-stream-resampler-processor', AudioStreamResamplerProces
|
|
|
1504
1791
|
}
|
|
1505
1792
|
|
|
1506
1793
|
/**
|
|
1507
|
-
* 在嵌套树中按 `id`
|
|
1794
|
+
* 在嵌套树中按 `id` 递归查找节点
|
|
1508
1795
|
*
|
|
1509
1796
|
* @template T extends Record<PropertyKey, any>
|
|
1510
1797
|
* @param {string | number} id - 要查找的 id
|
|
1511
1798
|
* @param {T[]} arr - 嵌套树森林
|
|
1512
|
-
* @param {string} [resultKey='name'] - 需要返回的字段
|
|
1513
1799
|
* @param {string} [idKey='id'] - 主键字段
|
|
1514
1800
|
* @param {string} [childrenKey='children'] - 子节点字段
|
|
1515
|
-
* @returns {any}
|
|
1801
|
+
* @returns {any} 找到的节点;未找到返回 `undefined`
|
|
1516
1802
|
*/
|
|
1517
|
-
const
|
|
1803
|
+
const findTreeNodeById = function findTreeNodeByIdFn(id, arr, idKey = "id", childrenKey = "children") {
|
|
1518
1804
|
if (Array.isArray(arr) && arr.length > 0) {
|
|
1519
1805
|
for (let i = 0; i < arr.length; i++) {
|
|
1520
1806
|
const item = arr[i];
|
|
1521
1807
|
if (item[idKey]?.toString() === id?.toString()) {
|
|
1522
|
-
return item
|
|
1808
|
+
return item;
|
|
1523
1809
|
} else if (Array.isArray(item[childrenKey]) && item[childrenKey].length > 0) {
|
|
1524
|
-
const result =
|
|
1810
|
+
const result = findTreeNodeByIdFn(id, item[childrenKey], idKey, childrenKey);
|
|
1525
1811
|
if (result) {
|
|
1526
1812
|
return result;
|
|
1527
1813
|
}
|
|
@@ -1530,6 +1816,21 @@ registerProcessor('audio-stream-resampler-processor', AudioStreamResamplerProces
|
|
|
1530
1816
|
}
|
|
1531
1817
|
};
|
|
1532
1818
|
|
|
1819
|
+
/**
|
|
1820
|
+
* 在嵌套树中按 `id` 递归查找节点,并返回其指定属性值。
|
|
1821
|
+
*
|
|
1822
|
+
* @template T extends Record<PropertyKey, any>
|
|
1823
|
+
* @param {string | number} id - 要查找的 id
|
|
1824
|
+
* @param {T[]} arr - 嵌套树森林
|
|
1825
|
+
* @param {string} [resultKey='name'] - 需要返回的字段
|
|
1826
|
+
* @param {string} [idKey='id'] - 主键字段
|
|
1827
|
+
* @param {string} [childrenKey='children'] - 子节点字段
|
|
1828
|
+
* @returns {any} 找到的值;未找到返回 `undefined`
|
|
1829
|
+
*/
|
|
1830
|
+
function findObjAttrValueById(id, arr, resultKey = "name", idKey = "id", childrenKey = "children") {
|
|
1831
|
+
return findTreeNodeById(id, arr, idKey, childrenKey)?.[resultKey];
|
|
1832
|
+
}
|
|
1833
|
+
|
|
1533
1834
|
/**
|
|
1534
1835
|
* 从服务端返回的已选 id 数组里,提取出
|
|
1535
1836
|
* 1. 叶子节点
|
|
@@ -2016,7 +2317,6 @@ registerProcessor('audio-stream-resampler-processor', AudioStreamResamplerProces
|
|
|
2016
2317
|
exports.MyId = MyId;
|
|
2017
2318
|
exports.WebSocketManager = WebSocketManager;
|
|
2018
2319
|
exports.assignExisting = assignExisting;
|
|
2019
|
-
exports.calcTimeDifference = calcTimeDifference;
|
|
2020
2320
|
exports.debounce = debounce;
|
|
2021
2321
|
exports.deepCloneByJSON = deepCloneByJSON;
|
|
2022
2322
|
exports.downloadByBlob = downloadByBlob;
|
|
@@ -2027,10 +2327,8 @@ registerProcessor('audio-stream-resampler-processor', AudioStreamResamplerProces
|
|
|
2027
2327
|
exports.extractFullyCheckedKeys = extractFullyCheckedKeys;
|
|
2028
2328
|
exports.fetchOrDownloadByUrl = fetchOrDownloadByUrl;
|
|
2029
2329
|
exports.findObjAttrValueById = findObjAttrValueById;
|
|
2330
|
+
exports.findTreeNodeById = findTreeNodeById;
|
|
2030
2331
|
exports.flatCompleteTree2NestedTree = flatCompleteTree2NestedTree;
|
|
2031
|
-
exports.formatDuration = formatDuration;
|
|
2032
|
-
exports.formatDurationMaxDay = formatDurationMaxDay;
|
|
2033
|
-
exports.formatDurationMaxHour = formatDurationMaxHour;
|
|
2034
2332
|
exports.formatTimeForLocale = formatTimeForLocale;
|
|
2035
2333
|
exports.getAllSearchParams = getAllSearchParams;
|
|
2036
2334
|
exports.getDataType = getDataType;
|
|
@@ -2039,12 +2337,16 @@ registerProcessor('audio-stream-resampler-processor', AudioStreamResamplerProces
|
|
|
2039
2337
|
exports.getSearchParam = getSearchParam;
|
|
2040
2338
|
exports.getTimePeriodName = getTimePeriodName;
|
|
2041
2339
|
exports.getViewportSize = getViewportSize;
|
|
2340
|
+
exports.handleDbMenuItems = handleDbMenuItems;
|
|
2042
2341
|
exports.isBlob = isBlob;
|
|
2043
2342
|
exports.isDate = isDate;
|
|
2044
2343
|
exports.isFunction = isFunction;
|
|
2045
2344
|
exports.isNonEmptyString = isNonEmptyString;
|
|
2046
2345
|
exports.isPlainObject = isPlainObject;
|
|
2047
2346
|
exports.isPromise = isPromise;
|
|
2347
|
+
exports.millisecond2Duration = millisecond2Duration;
|
|
2348
|
+
exports.millisecond2DurationMaxDay = millisecond2DurationMaxDay;
|
|
2349
|
+
exports.millisecond2DurationMaxHour = millisecond2DurationMaxHour;
|
|
2048
2350
|
exports.moveItem = moveItem;
|
|
2049
2351
|
exports.nestedTree2IdMap = nestedTree2IdMap;
|
|
2050
2352
|
exports.pcmToWavBlob = pcmToWavBlob;
|
|
@@ -2054,6 +2356,9 @@ registerProcessor('audio-stream-resampler-processor', AudioStreamResamplerProces
|
|
|
2054
2356
|
exports.randomHanOrEn = randomHanOrEn;
|
|
2055
2357
|
exports.randomIntInRange = randomIntInRange;
|
|
2056
2358
|
exports.readBlobAsText = readBlobAsText;
|
|
2359
|
+
exports.second2Duration = second2Duration;
|
|
2360
|
+
exports.second2DurationMaxDay = second2DurationMaxDay;
|
|
2361
|
+
exports.second2DurationMaxHour = second2DurationMaxHour;
|
|
2057
2362
|
exports.shuffle = shuffle;
|
|
2058
2363
|
exports.throttle = throttle;
|
|
2059
2364
|
exports.toDate = toDate;
|