@infly/libs 2.0.36 → 2.0.38

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.
@@ -9,12 +9,10 @@ import TokenService from "./TokenService";
9
9
  * @param {Object} config.router 路由实例
10
10
  * @param {Object} config.store Vuex store 实例
11
11
  * @param {Object} config.NProgress NProgress 实例
12
- * @param {Function} config.getToken 获取 token 的函数
13
12
  * @param {Function} config.getUserInfo 获取用户信息的函数
14
13
  * @param {Function} config.resetToken 重置 token 的函数
15
14
  * @param {Function} config.showMessage 显示消息的函数
16
- * @param {Function} config.getTitle 获取页面标题的函数
17
- * * @returns {void}
15
+ * @returns {void}
18
16
  */
19
17
  export function initPermission({
20
18
  whiteList = ["/login", "/404", "/403", "/error/401", "/error/404"],
@@ -32,14 +30,14 @@ export function initPermission({
32
30
  resetToken,
33
31
  showMessage
34
32
  }) {
35
- const { title: projectTitle, projectConfig } = settings || {};
36
- const { initRoutePath = "/" } = projectConfig || {};
33
+ const { title: projectTitle } = settings || {};
37
34
  const { dispatch } = store || {};
35
+ // 转为 Set,将白名单查找从 O(n) 优化为 O(1)
36
+ const whiteSet = new Set(whiteList);
38
37
 
38
+ // afterEach 已统一调用 NProgress.done(),此处无需重复
39
39
  const redirectToLogin = (to, next) => {
40
40
  next(`/login?redirect=${to.path}`);
41
- NProgress.done();
42
- return;
43
41
  };
44
42
 
45
43
  const handleGetUserInfo = () => {
@@ -58,24 +56,20 @@ export function initPermission({
58
56
  }
59
57
  };
60
58
 
59
+ // 降级链:优先调用自定义 showMessage,其次 Message,最后 console
61
60
  const handleShowMessage = (message, type = "error") => {
62
61
  if (Uts.isFunction(showMessage)) {
63
62
  showMessage(message);
64
- }
65
-
66
- if (Uts.isFunction(Message)) {
63
+ } else if (Uts.isFunction(Message)) {
67
64
  Message[type](message);
65
+ } else {
66
+ console.log(message);
68
67
  }
69
-
70
- console.log(message);
71
68
  };
72
69
 
73
70
  const handlePageTitle = (path) => {
74
71
  const title = projectTitle || "后台管理系统";
75
- if (path) {
76
- return `${path} - ${title}`;
77
- }
78
- return `${title}`;
72
+ return path ? `${path} - ${title}` : title;
79
73
  };
80
74
 
81
75
  NProgress.configure({ showSpinner: false });
@@ -84,8 +78,6 @@ export function initPermission({
84
78
  const handleUrlTokenParam = async (to, next, accessTokenKey, accessToken) => {
85
79
  if (!to.query[accessTokenKey]) return false;
86
80
 
87
- const newQuery = { ...to.query };
88
-
89
81
  // 优先从地址栏获取token, 更新存储token值
90
82
  if (accessToken && Uts.isFunction(dispatch)) {
91
83
  try {
@@ -95,7 +87,8 @@ export function initPermission({
95
87
  }
96
88
  }
97
89
 
98
- delete newQuery[accessTokenKey];
90
+ // 用解构 rest 移除 token 参数,避免修改原对象
91
+ const { [accessTokenKey]: _, ...newQuery } = to.query;
99
92
  next({ path: to.path, query: newQuery, replace: true });
100
93
  return true;
101
94
  };
@@ -115,23 +108,38 @@ export function initPermission({
115
108
 
116
109
  // 辅助函数:处理已认证用户的路由
117
110
  const handleAuthenticatedRoute = async (to, next, permission) => {
118
- // 已登录用户访问登录页,重定向到首页
111
+ // 已登录用户访问登录页
119
112
  if (to.path === "/login") {
120
- return next({ path: "/" });
113
+ const { PMRedirectPath } = (await handleGetUserInfo()) || {};
114
+ const redirectPath = to.query.redirect;
115
+
116
+ if (redirectPath) {
117
+ // 查找 redirect 路由对应的权限
118
+ const matched = router.resolve(redirectPath)?.route;
119
+ const redirectPermission = matched?.meta?.permission;
120
+ // 无需权限 或 有权限,则走 redirect
121
+ if (!redirectPermission || checkPermission(redirectPermission)) {
122
+ return next({ path: redirectPath });
123
+ }
124
+ }
125
+
126
+ // 否则跳到第一个有权限的页面
127
+ return next({ path: PMRedirectPath || "/" });
121
128
  }
122
129
 
123
130
  try {
124
131
  // 获取用户信息
125
- const { PMRedirectPath } = await handleGetUserInfo();
126
-
127
- // 如果是进入初始化配置的path:""重定向路由且没权限(PMRedirectPath !== initRoutePath),则重定向到指定有权限的第一条菜单路由
128
- if (initRoutePath && to.path === initRoutePath && PMRedirectPath !== initRoutePath) {
129
- return next({ path: PMRedirectPath || "/" });
130
- }
131
-
132
- // 检查页面权限
133
- if (permission && !checkPermission(permission) && !whiteList.includes(to.path)) {
134
- return next({ path: "/error/403" });
132
+ const { PMRedirectPath } = (await handleGetUserInfo()) || {};
133
+
134
+ // 合并权限检查,避免重复调用 checkPermission
135
+ if (permission && !checkPermission(permission)) {
136
+ // 有兜底路径时优先跳转,否则跳 403
137
+ if (PMRedirectPath) {
138
+ return next({ path: PMRedirectPath });
139
+ }
140
+ if (!whiteSet.has(to.path)) {
141
+ return next({ path: "/error/403" });
142
+ }
135
143
  }
136
144
 
137
145
  next();
@@ -154,14 +162,13 @@ export function initPermission({
154
162
  const Token = TokenService.getToken();
155
163
  const accessTokenKey = TokenService.getUrlTokenKey();
156
164
  const accessToken = TokenService.getUrlToken();
157
- const { meta = {} } = to || {};
158
- const { permission } = meta || {};
165
+ const { permission } = to.meta || {};
159
166
 
160
167
  // 设置页面标题
161
- document.title = handlePageTitle(to.meta.title);
168
+ document.title = handlePageTitle(to.meta?.title);
162
169
 
163
170
  // 缓存页面路径(非白名单页面)
164
- if (!whiteList.includes(to.path) && pagePathCacheKey) {
171
+ if (pagePathCacheKey && !whiteSet.has(to.path)) {
165
172
  localStorage.setItem(pagePathCacheKey, window.location.href);
166
173
  }
167
174
 
@@ -171,7 +178,7 @@ export function initPermission({
171
178
  }
172
179
 
173
180
  // 2. 检查白名单路径
174
- if (whiteList.includes(to.path)) {
181
+ if (whiteSet.has(to.path)) {
175
182
  return next();
176
183
  }
177
184
 
package/module/REST.js CHANGED
@@ -1,18 +1,71 @@
1
1
  import axios from "axios";
2
2
  import Uts from "./Uts.js";
3
3
  import TokenService from "./TokenService.js";
4
+ import { resolveInternalServiceURL } from "./cjs/request-url-rules.cjs";
4
5
 
5
6
  const baseURL = process.env.VUE_APP_BASE_API;
6
7
  const { env } = Uts.getEnv() || {};
7
8
  const isTestEnv = Uts.contain(["localhost", "infly-test"], env);
8
- const isProdAPI = Uts.contain(["https://api.sutpay.com"], baseURL);
9
+
10
+ // 若接口地址是 localhost/127.0.0.1,则自动替换为当前页面访问主机。
11
+ // 协议、端口、路径保持不变,便于局域网联调时免改环境变量。
12
+ function remapLocalhostToCurrentHost(url) {
13
+ const normalized = Uts.normalizeEnvUrl(url);
14
+ if (!normalized) {
15
+ return normalized;
16
+ }
17
+
18
+ if (typeof window === "undefined" || !window.location || !window.location.hostname) {
19
+ return normalized;
20
+ }
21
+
22
+ try {
23
+ const parsed = new URL(normalized);
24
+ const isLocalHost = ["localhost", "127.0.0.1"].includes(parsed.hostname);
25
+ if (!isLocalHost) {
26
+ return normalized;
27
+ }
28
+
29
+ parsed.hostname = window.location.hostname;
30
+ return parsed.toString();
31
+ } catch (e) {
32
+ return normalized;
33
+ }
34
+ }
35
+
36
+ // 解析 axios 最终使用的 baseURL。
37
+ // 优先级:config.baseURL > process.env.VUE_APP_BASE_API,再执行 localhost 主机替换。
38
+ function resolveServiceBaseURL(customBaseURL) {
39
+ const targetBaseURL = Uts.normalizeEnvUrl(customBaseURL || baseURL);
40
+ return remapLocalhostToCurrentHost(targetBaseURL);
41
+ }
42
+
43
+ /**
44
+ * 对任意通知函数加去重包装
45
+ * @param {Function} fn - 原始通知函数,接受字符串或 { message, type, ... }
46
+ * @param {Number} ttl - 去重时间窗(ms),默认 500
47
+ * @returns {Function} 去重后的通知函数
48
+ */
49
+ export function createNotifyDedup(fn, ttl = 500) {
50
+ const recentMsgs = new Set();
51
+ return function dedupedNotify(msgOrOptions) {
52
+ const message = typeof msgOrOptions === "string" ? msgOrOptions : msgOrOptions?.message;
53
+ if (message && recentMsgs.has(message)) return;
54
+ if (message) {
55
+ recentMsgs.add(message);
56
+ setTimeout(() => recentMsgs.delete(message), ttl);
57
+ }
58
+ return fn(msgOrOptions);
59
+ };
60
+ }
9
61
 
10
62
  /**
11
63
  * 创建封装的 axios 实例
12
64
  * @param {Object} config 配置项
13
- * @param {Function|String} config.getToken 获取 token 的函数或值
65
+ * @param {String} config.getTokenFunc 获取 token 的 TokenService 方法名,默认 "getToken"
14
66
  * @param {Function} config.onLogout 登出回调
15
- * @param {Function} config.onNotify 通知回调
67
+ * @param {Object} config.notify Message-like 对象(如 ElementUI Message),传入后自动应用去重并构建 onNotify
68
+ * @param {Function} config.onNotify 自定义通知回调;若传入 Message-like 对象也可自动识别
16
69
  * @returns {AxiosInstance}
17
70
  */
18
71
  export function createService(config) {
@@ -22,10 +75,32 @@ export function createService(config) {
22
75
  serviceField = defaultField,
23
76
  logoutAction = "user/logout", // 退出登录的action方法
24
77
  onLogout,
25
- onNotify,
26
- getTokenFunc = "getToken"
78
+ notify,
79
+ onNotify: rawOnNotify,
80
+ getTokenFunc = "getToken",
81
+ internalServiceURLRules = []
27
82
  } = config || {};
28
- const service = axios.create({ baseURL: customBaseURL || baseURL });
83
+ // 统一在 REST 内部处理 baseURL,避免业务项目重复实现主机替换逻辑。
84
+ const resolvedBaseURL = resolveServiceBaseURL(customBaseURL);
85
+ const isProdAPI = Uts.contain(["https://api.sutpay.com"], resolvedBaseURL);
86
+ const service = axios.create({ baseURL: resolvedBaseURL });
87
+
88
+ // 统一处理 notify 与 onNotify:
89
+ // - 优先使用显式传入的 notify
90
+ // - 若 onNotify 是 Message-like 对象(带 .error/.success 等类型方法),也当作 notify 处理
91
+ // 两种写法都自动应用去重,拦截器与调用方 .catch() 共用同一个去重包装
92
+ const notifyTarget = notify || (rawOnNotify && typeof rawOnNotify["error"] === "function" ? rawOnNotify : null);
93
+ let onNotify = notifyTarget ? null : rawOnNotify;
94
+
95
+ if (notifyTarget) {
96
+ ["success", "warning", "info", "error"].forEach((type) => {
97
+ if (typeof notifyTarget[type] === "function") {
98
+ notifyTarget[type] = createNotifyDedup(notifyTarget[type].bind(notifyTarget));
99
+ }
100
+ });
101
+ onNotify = ({ message, type = "error", duration }) =>
102
+ notifyTarget[type]?.(message) ?? notifyTarget({ message, type, duration });
103
+ }
29
104
 
30
105
  /**
31
106
  * 发送通知
@@ -61,9 +136,10 @@ export function createService(config) {
61
136
  });
62
137
  }
63
138
 
64
- // 鼓励金接口特殊处理
65
- if (customBaseURL) {
66
- config.url = config.url.replace("https://auth.sutpay.cn/", customBaseURL);
139
+ const internalServiceURL = resolveInternalServiceURL(config.url, internalServiceURLRules);
140
+ if (internalServiceURL) {
141
+ config.url = internalServiceURL.url;
142
+ config.baseURL = resolveServiceBaseURL(internalServiceURL.baseURL);
67
143
  }
68
144
 
69
145
  if (serviceField !== defaultField) {
@@ -83,54 +159,41 @@ export function createService(config) {
83
159
 
84
160
  service.interceptors.response.use(
85
161
  (response) => {
86
- return response.data || {};
162
+ const { data } = response || {};
163
+ return Uts.transArrayBufferToObj(data);
87
164
  },
88
165
  (error) => {
89
166
  const { response, request } = error || {};
90
167
  const { status, data } = response || {};
168
+ const { error: bufferError } = Uts.transArrayBufferToObj(data) ?? {};
91
169
  const { error: dataError, detail, message } = data || {};
170
+ const errorMsg = bufferError || dataError || detail || message || `请求失败(${status})`;
92
171
 
93
172
  // 添加标识,表示错误已被拦截器处理
94
173
  error.handledByInterceptor = true;
95
174
 
96
- // HTTP 状态码错误处理映射
97
- const STATUS_HANDLERS = {
98
- 401: { message: "登录信息已过期,请重新登录", action: () => handleLogout() },
99
- 403: { message: "您没有权限访问,请联系管理员" },
100
- 500: { message: "服务器内部错误,请联系开发人员" },
101
- 502: { message: "网关错误,请稍后重试或联系开发人员" },
102
- 503: { message: "服务暂时不可用,请稍后重试" },
103
- 504: { message: "网关超时,请稍后重试或联系开发人员" }
175
+ // HTTP 状态码 → 固定提示语映射(400 直接用后端返回的 errorMsg)
176
+ const STATUS_MESSAGES = {
177
+ 400: errorMsg,
178
+ 401: "登录信息已过期,请重新登录",
179
+ 403: "您没有权限访问,请联系管理员",
180
+ 500: errorMsg || "服务器内部错误,请联系开发人员",
181
+ 502: "网关错误,请稍后重试或联系开发人员",
182
+ 503: "服务暂时不可用,请稍后重试",
183
+ 504: "网关超时,请稍后重试或联系开发人员"
104
184
  };
105
185
 
106
- // 处理特定状态码
107
- const handler = STATUS_HANDLERS[status];
108
- if (handler) {
109
- handleNotify({
110
- message: handler.message,
111
- type: "error"
112
- });
113
- handler.action?.();
114
- } else if (response) {
115
- // 其他 HTTP 错误响应,优先使用后端返回的错误信息
116
- const errorMsg = dataError || detail || message || `请求失败(${status})`;
117
- handleNotify({
118
- message: errorMsg,
119
- type: "error"
120
- });
121
- } else if (request) {
122
- // 请求已发送但无响应(网络问题)
123
- handleNotify({
124
- message: "网络请求失败,请检查网络连接",
125
- type: "error"
126
- });
127
- } else {
128
- // 请求配置错误
129
- handleNotify({
130
- message: error.message || "请求配置错误",
131
- type: "error"
132
- });
133
- }
186
+ // 按优先级确定最终提示语,合并为一次 handleNotify 调用
187
+ const notifyMsg =
188
+ STATUS_MESSAGES[status] ??
189
+ (response ? errorMsg : null) ??
190
+ (request ? "网络请求失败,请检查网络连接" : null) ??
191
+ (error.message || "请求配置错误");
192
+
193
+ handleNotify({ message: notifyMsg, type: "error" });
194
+
195
+ // 401 副作用:自动登出(不受弹窗逻辑影响)
196
+ if (status === 401) handleLogout();
134
197
 
135
198
  return Promise.reject(error);
136
199
  }
package/module/Uts.js CHANGED
@@ -7,6 +7,12 @@
7
7
  // const _history = createHashHistory();
8
8
  // console.log(_history);
9
9
 
10
+ const { deepMerge: deepMergeImpl } = require("./cjs/deep-merge.cjs");
11
+ const {
12
+ mergePageConfig: mergePageConfigImpl,
13
+ resolvePlatformPageConfig: resolvePlatformPageConfigImpl
14
+ } = require("./cjs/page-config.cjs");
15
+
10
16
  let _getItem = Storage.prototype.getItem;
11
17
  // import Uts from "./Uts";
12
18
  let $state = {
@@ -81,6 +87,15 @@ let Uts = {
81
87
  ce(text) {
82
88
  global.console && global.console.error && global.console.error(text);
83
89
  },
90
+ /**
91
+ * 规范化环境变量中的 URL 字符串
92
+ * 例如去除首尾空格和包裹引号:"'http://xx/'" -> "http://xx/"
93
+ */
94
+ normalizeEnvUrl(url = "") {
95
+ return String(url)
96
+ .trim()
97
+ .replace(/^['"]|['"]$/g, "");
98
+ },
84
99
  /**
85
100
  * set class 动态设置类名
86
101
  * @module condition
@@ -534,7 +549,7 @@ Filter.formatDate(1655740800000,'Y-m-d')
534
549
  Uts.join()
535
550
  ''
536
551
  */
537
- join(arr = [], symbol = ".") {
552
+ join(arr = [], symbol = ",") {
538
553
  if (Uts.notEmptyArray(arr)) {
539
554
  return arr.join(symbol);
540
555
  } else {
@@ -1295,11 +1310,15 @@ Uts.getEnv()
1295
1310
  getEnv(string) {
1296
1311
  let config = {};
1297
1312
  let host = string || location.host;
1298
- const projectConfig =
1299
- {
1300
- [process.env.VUE_APP_PROJECT_NAME]: { reqModule: "admin" },
1301
- ["postal-benefits-platform-level"]: { reqModule: "management" }
1302
- }[process.env.VUE_APP_PROJECT_NAME] || {};
1313
+ const projectName = process.env.VUE_APP_PROJECT_NAME;
1314
+ const projectConfigs = {
1315
+ "postal-benefits-platform-level": { reqModule: "management" }
1316
+ // 可继续添加其它项目配置
1317
+ };
1318
+ const projectConfig = {
1319
+ projectName,
1320
+ ...(projectConfigs[projectName] || { reqModule: "admin" })
1321
+ };
1303
1322
 
1304
1323
  config.rootDomain = (host.match(Uts.FINDDOMAINEXP) && host.match(Uts.FINDDOMAINEXP)[0]) || null;
1305
1324
  config.env = "";
@@ -1683,35 +1702,13 @@ console.log(o1)
1683
1702
  * @returns {Object} 合并后的目标对象
1684
1703
  */
1685
1704
  deepMerge(target, source, overwrite = false) {
1686
- if (!source || typeof source !== "object") return target;
1687
- if (!target || typeof target !== "object") return source;
1688
-
1689
- Object.keys(source).forEach((key) => {
1690
- const sourceValue = source[key];
1691
- const targetValue = target[key];
1692
-
1693
- if (Array.isArray(sourceValue)) {
1694
- // 处理数组:如果不覆盖且目标已存在,保持原数组
1695
- if (!overwrite && targetValue !== undefined) {
1696
- return;
1697
- }
1698
- target[key] = [...sourceValue];
1699
- } else if (sourceValue && typeof sourceValue === "object") {
1700
- // 处理对象:递归合并
1701
- if (!target[key] || typeof target[key] !== "object") {
1702
- target[key] = {};
1703
- }
1704
- Uts.deepMerge(target[key], sourceValue, overwrite);
1705
- } else {
1706
- // 处理基本类型:如果不覆盖且目标已存在,跳过
1707
- if (!overwrite && targetValue !== undefined) {
1708
- return;
1709
- }
1710
- target[key] = sourceValue;
1711
- }
1712
- });
1713
-
1714
- return target;
1705
+ return deepMergeImpl(target, source, overwrite);
1706
+ },
1707
+ mergePageConfig(...sources) {
1708
+ return mergePageConfigImpl(...sources);
1709
+ },
1710
+ resolvePlatformPageConfig(pageConfig, platform, options) {
1711
+ return resolvePlatformPageConfigImpl(pageConfig, platform, options);
1715
1712
  },
1716
1713
  /**
1717
1714
  * 转换字符串为小写
@@ -3917,6 +3914,21 @@ false
3917
3914
  isNull(value) {
3918
3915
  return value === null;
3919
3916
  },
3917
+ /**
3918
+ * 判断接口字段是否有实际返回值;0 和 false 是有效值,仅排除 undefined、null、空字符串。
3919
+ * @module checkType
3920
+ * @todo Uts.isPresentValue
3921
+ * @param {*} value 要检查的值
3922
+ * @return {Boolean}
3923
+ * @example
3924
+ * Uts.isPresentValue(0)
3925
+ true
3926
+ Uts.isPresentValue('')
3927
+ false
3928
+ */
3929
+ isPresentValue(value) {
3930
+ return value !== undefined && value !== null && value !== "";
3931
+ },
3920
3932
  /**
3921
3933
  * 检查数值是否为NaN
3922
3934
  * @module checkType
@@ -3925,9 +3937,9 @@ false
3925
3937
  * @return {Boolean}
3926
3938
  * @example
3927
3939
  * Uts.isNaN({})
3928
- false
3929
- Uts.isNaN(NaN)
3930
- true
3940
+ false
3941
+ Uts.isNaN(NaN)
3942
+ true
3931
3943
  */
3932
3944
  isNaN(value) {
3933
3945
  return value !== value;
@@ -3940,9 +3952,9 @@ true
3940
3952
  * @return {Boolean} true or false
3941
3953
  * @example
3942
3954
  * Uts.isString('w')
3943
- true
3944
- Uts.isString(1)
3945
- false
3955
+ true
3956
+ Uts.isString(1)
3957
+ false
3946
3958
  */
3947
3959
  isString(value) {
3948
3960
  return typeof value === "string";
@@ -3955,9 +3967,9 @@ false
3955
3967
  * @return {Boolean} true or false
3956
3968
  * @example
3957
3969
  * Uts.isDate({})
3958
- false
3959
- Uts.isDate(new Date())
3960
- true
3970
+ false
3971
+ Uts.isDate(new Date())
3972
+ true
3961
3973
  */
3962
3974
  isDate(value) {
3963
3975
  return value && value.constructor === Date;
@@ -3971,9 +3983,9 @@ true
3971
3983
  * @return {Boolean} true or false
3972
3984
  * @example
3973
3985
  * Uts.isWaitingObject({})
3974
- false
3975
- Uts.isWaitingObject(new Waiting())
3976
- true
3986
+ false
3987
+ Uts.isWaitingObject(new Waiting())
3988
+ true
3977
3989
  */
3978
3990
  isWaitingObject(obj) {
3979
3991
  return obj && (obj instanceof Waiting || obj.endpoint || Uts.isFunction(obj.then));
@@ -3986,9 +3998,9 @@ true
3986
3998
  * @return {Boolean}
3987
3999
  * @example
3988
4000
  * Uts.isError({})
3989
- false
3990
- Uts.isError(new Error())
3991
- true
4001
+ false
4002
+ Uts.isError(new Error())
4003
+ true
3992
4004
  */
3993
4005
  isError: function (obj) {
3994
4006
  return obj && obj instanceof Error;
@@ -6352,10 +6364,10 @@ Uts.getParentByPath(item,treeData,{ pathFieldName: "__path", treeChildProp: "chi
6352
6364
  * @returns
6353
6365
  */
6354
6366
  getPMBySettings: function (code, settings) {
6355
- const { permissionsCodeKey } = settings || {};
6367
+ const { permissionsCodeKey, notVerifyPMCodes } = settings || {};
6356
6368
  const permissionCodes = Uts.storage.get(permissionsCodeKey) || [];
6357
6369
 
6358
- return Uts.contain(permissionCodes, code);
6370
+ return Uts.contain(permissionCodes, code) || Uts.contain(notVerifyPMCodes, code);
6359
6371
  },
6360
6372
  /**
6361
6373
  * 过滤路由权限
@@ -7227,6 +7239,32 @@ Uts.getSecondDomain('http://www.baidu.com')
7227
7239
  return path.split(".").reduce(function (acc, part) {
7228
7240
  return acc ? acc[part] : undefined;
7229
7241
  }, obj);
7242
+ },
7243
+ transArrayBufferToObj(data) {
7244
+ if (!(data instanceof ArrayBuffer)) {
7245
+ return data;
7246
+ }
7247
+
7248
+ try {
7249
+ const jsonStr = new TextDecoder().decode(data);
7250
+ return JSON.parse(jsonStr);
7251
+ } catch {
7252
+ return data;
7253
+ }
7254
+ },
7255
+ /**
7256
+ * 将字符串按分隔符转换为数组
7257
+ * @param {String|Array} value - 输入值
7258
+ * @returns {Array} - 转换后的数组
7259
+ */
7260
+ convertToArray(value) {
7261
+ if (Array.isArray(value)) {
7262
+ return value;
7263
+ }
7264
+ return String(value)
7265
+ .split(/[,\s\uFF0C]+/)
7266
+ .map((s) => s.trim())
7267
+ .filter(Boolean);
7230
7268
  }
7231
7269
  };
7232
7270
 
@@ -0,0 +1,38 @@
1
+ /**
2
+ * 深度合并对象
3
+ * @param {Object} target - 目标对象
4
+ * @param {Object} source - 源对象
5
+ * @param {boolean} overwrite - 是否覆盖已存在的属性,默认 false
6
+ * @returns {Object} 合并后的目标对象
7
+ */
8
+ function deepMerge(target, source, overwrite = false) {
9
+ if (!source || typeof source !== "object") return target;
10
+ if (!target || typeof target !== "object") return source;
11
+
12
+ Object.keys(source).forEach((key) => {
13
+ const sourceValue = source[key];
14
+ const targetValue = target[key];
15
+
16
+ if (Array.isArray(sourceValue)) {
17
+ if (!overwrite && targetValue !== undefined) {
18
+ return;
19
+ }
20
+ target[key] = [...sourceValue];
21
+ } else if (sourceValue && typeof sourceValue === "object") {
22
+ if (!target[key] || typeof target[key] !== "object") {
23
+ target[key] = {};
24
+ }
25
+ deepMerge(target[key], sourceValue, overwrite);
26
+ } else if (!overwrite && targetValue !== undefined) {
27
+ return;
28
+ } else {
29
+ target[key] = sourceValue;
30
+ }
31
+ });
32
+
33
+ return target;
34
+ }
35
+
36
+ module.exports = {
37
+ deepMerge
38
+ };