a2bei4-utils 1.0.5 → 1.0.7
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 +7 -4
- 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 +7 -4
- 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 +7 -4
- 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/download.cjs +3 -0
- package/dist/download.cjs.map +1 -1
- package/dist/download.js +3 -0
- package/dist/download.js.map +1 -1
- package/dist/tree.cjs +4 -4
- package/dist/tree.cjs.map +1 -1
- package/dist/tree.js +4 -4
- package/dist/tree.js.map +1 -1
- package/package.json +1 -1
- package/types/index.d.ts +11 -1
- package/types/tree.d.ts +11 -1
package/dist/download.cjs
CHANGED
|
@@ -79,6 +79,9 @@ async function fetchOrDownloadByUrl(url, fileName) {
|
|
|
79
79
|
const urlPathname = new URL(url).pathname;
|
|
80
80
|
// 获取路径的最后一部分作为文件名,并移除可能的查询参数
|
|
81
81
|
fileName = urlPathname.substring(urlPathname.lastIndexOf("/") + 1).split("?")[0];
|
|
82
|
+
if (fileName) {
|
|
83
|
+
fileName = decodeURIComponent(fileName);
|
|
84
|
+
}
|
|
82
85
|
} catch (e) {}
|
|
83
86
|
// 如果提取后文件名为空(例如 URL 以 '/' 结尾),也使用时间戳
|
|
84
87
|
if (!fileName) {
|
package/dist/download.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"download.cjs","sources":["../src/source/download.js"],"sourcesContent":["/**\r\n * 通过动态创建 `<a>` 标签触发浏览器下载。\r\n * 注意:此方法可能无法强制下载浏览器原生支持的文件(如图片、PDF),浏览器可能会选择直接打开。\r\n *\r\n * @param {string} url - 任意可下载地址(同源或允许跨域)\r\n * @param {string} [fileName] - 保存到本地的文件名;不传时使用时间戳\r\n */\r\nexport function downloadByUrl(url, fileName) {\r\n const a = document.createElement(\"a\");\r\n a.style.display = \"none\";\r\n a.rel = \"noopener\";\r\n a.href = url;\r\n a.download = fileName || Date.now();\r\n document.body.appendChild(a);\r\n a.click();\r\n document.body.removeChild(a);\r\n}\r\n\r\n/**\r\n * 把 Blob 转成临时 URL 并触发下载,下载完成后立即释放内存。\r\n *\r\n * @param {Blob} blob - 待下载的 Blob(含 File)\r\n * @param {string} [fileName] - 保存到本地的文件名\r\n */\r\nexport function downloadByBlob(blob, fileName) {\r\n const url = URL.createObjectURL(blob);\r\n downloadByUrl(url, fileName);\r\n setTimeout(() => URL.revokeObjectURL(url), 0);\r\n}\r\n\r\n/**\r\n * 将任意数据包装成 Blob 并下载。\r\n *\r\n * @param {string | ArrayBufferView | ArrayBuffer | Blob} data - 要写入文件的数据\r\n * @param {string} [fileName] - 保存到本地的文件名\r\n * @param {string} [mimeType] - MIME 类型;默认 `application/octet-stream`\r\n */\r\nexport function downloadByData(data, fileName, mimeType = \"application/octet-stream\") {\r\n downloadByBlob(new Blob([data], { type: mimeType }), fileName);\r\n}\r\n\r\n/**\r\n * 快捷下载 Excel 文件(MIME 已固定)。\r\n *\r\n * @param {string | ArrayBufferView | ArrayBuffer | Blob} data - Excel 二进制或字符串内容\r\n * @param {string} [fileName] - 保存到本地的文件名\r\n */\r\nexport function downloadExcel(data, fileName) {\r\n downloadByData(data, fileName, \"application/vnd.ms-excel\");\r\n}\r\n\r\n/**\r\n * 快捷下载 JSON 文件(MIME 已固定)。\r\n * 若传入非字符串数据,会自行 `JSON.stringify`。\r\n *\r\n * @param {any} data - 要序列化的 JSON 数据\r\n * @param {string} [fileName] - 保存到本地的文件名\r\n */\r\nexport function downloadJSON(data, fileName) {\r\n // downloadByData(typeof data === \"string\" ? data : JSON.stringify(data, null, 4), fileName, \"application/json\");\r\n downloadByData(data, fileName, \"application/json\");\r\n}\r\n\r\n/**\r\n * 通过 `fetch` 获取资源并强制下载,避免浏览器直接打开文件。\r\n * 此方法适用于下载图片、PDF 等浏览器默认会打开的文件类型。\r\n * 如果 `fetch` 失败(如 CORS 策略阻止),会回退到 `downloadByUrl` 方法作为备选方案。\r\n *\r\n * @param {string} url - 文件的 URL 地址\r\n * @param {string} [fileName] - 保存到本地的文件名。如果不提供,会尝试从 URL 中自动提取\r\n * @returns {Promise<void>} 返回一个 Promise,在下载开始或失败后 resolve\r\n */\r\nexport async function fetchOrDownloadByUrl(url, fileName) {\r\n // 如果未提供文件名,尝试从 URL 路径中提取\r\n if (!fileName) {\r\n try {\r\n const urlPathname = new URL(url).pathname;\r\n // 获取路径的最后一部分作为文件名,并移除可能的查询参数\r\n fileName = urlPathname.substring(urlPathname.lastIndexOf(\"/\") + 1).split(\"?\")[0];\r\n } catch (e) {}\r\n // 如果提取后文件名为空(例如 URL 以 '/' 结尾),也使用时间戳\r\n if (!fileName) {\r\n fileName = Date.now().toString();\r\n }\r\n }\r\n\r\n try {\r\n const response = await fetch(url, {\r\n method: \"GET\",\r\n mode: \"cors\",\r\n cache: \"no-cache\"\r\n });\r\n\r\n if (!response.ok) {\r\n throw new Error(`HTTP error! ${response.status}: ${response.statusText}`);\r\n }\r\n\r\n const blob = await response.blob();\r\n downloadByBlob(blob, fileName);\r\n } catch (error) {\r\n downloadByUrl(url, fileName);\r\n }\r\n}\r\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"download.cjs","sources":["../src/source/download.js"],"sourcesContent":["/**\r\n * 通过动态创建 `<a>` 标签触发浏览器下载。\r\n * 注意:此方法可能无法强制下载浏览器原生支持的文件(如图片、PDF),浏览器可能会选择直接打开。\r\n *\r\n * @param {string} url - 任意可下载地址(同源或允许跨域)\r\n * @param {string} [fileName] - 保存到本地的文件名;不传时使用时间戳\r\n */\r\nexport function downloadByUrl(url, fileName) {\r\n const a = document.createElement(\"a\");\r\n a.style.display = \"none\";\r\n a.rel = \"noopener\";\r\n a.href = url;\r\n a.download = fileName || Date.now();\r\n document.body.appendChild(a);\r\n a.click();\r\n document.body.removeChild(a);\r\n}\r\n\r\n/**\r\n * 把 Blob 转成临时 URL 并触发下载,下载完成后立即释放内存。\r\n *\r\n * @param {Blob} blob - 待下载的 Blob(含 File)\r\n * @param {string} [fileName] - 保存到本地的文件名\r\n */\r\nexport function downloadByBlob(blob, fileName) {\r\n const url = URL.createObjectURL(blob);\r\n downloadByUrl(url, fileName);\r\n setTimeout(() => URL.revokeObjectURL(url), 0);\r\n}\r\n\r\n/**\r\n * 将任意数据包装成 Blob 并下载。\r\n *\r\n * @param {string | ArrayBufferView | ArrayBuffer | Blob} data - 要写入文件的数据\r\n * @param {string} [fileName] - 保存到本地的文件名\r\n * @param {string} [mimeType] - MIME 类型;默认 `application/octet-stream`\r\n */\r\nexport function downloadByData(data, fileName, mimeType = \"application/octet-stream\") {\r\n downloadByBlob(new Blob([data], { type: mimeType }), fileName);\r\n}\r\n\r\n/**\r\n * 快捷下载 Excel 文件(MIME 已固定)。\r\n *\r\n * @param {string | ArrayBufferView | ArrayBuffer | Blob} data - Excel 二进制或字符串内容\r\n * @param {string} [fileName] - 保存到本地的文件名\r\n */\r\nexport function downloadExcel(data, fileName) {\r\n downloadByData(data, fileName, \"application/vnd.ms-excel\");\r\n}\r\n\r\n/**\r\n * 快捷下载 JSON 文件(MIME 已固定)。\r\n * 若传入非字符串数据,会自行 `JSON.stringify`。\r\n *\r\n * @param {any} data - 要序列化的 JSON 数据\r\n * @param {string} [fileName] - 保存到本地的文件名\r\n */\r\nexport function downloadJSON(data, fileName) {\r\n // downloadByData(typeof data === \"string\" ? data : JSON.stringify(data, null, 4), fileName, \"application/json\");\r\n downloadByData(data, fileName, \"application/json\");\r\n}\r\n\r\n/**\r\n * 通过 `fetch` 获取资源并强制下载,避免浏览器直接打开文件。\r\n * 此方法适用于下载图片、PDF 等浏览器默认会打开的文件类型。\r\n * 如果 `fetch` 失败(如 CORS 策略阻止),会回退到 `downloadByUrl` 方法作为备选方案。\r\n *\r\n * @param {string} url - 文件的 URL 地址\r\n * @param {string} [fileName] - 保存到本地的文件名。如果不提供,会尝试从 URL 中自动提取\r\n * @returns {Promise<void>} 返回一个 Promise,在下载开始或失败后 resolve\r\n */\r\nexport async function fetchOrDownloadByUrl(url, fileName) {\r\n // 如果未提供文件名,尝试从 URL 路径中提取\r\n if (!fileName) {\r\n try {\r\n const urlPathname = new URL(url).pathname;\r\n // 获取路径的最后一部分作为文件名,并移除可能的查询参数\r\n fileName = urlPathname.substring(urlPathname.lastIndexOf(\"/\") + 1).split(\"?\")[0];\r\n if (fileName) {\r\n fileName = decodeURIComponent(fileName);\r\n }\r\n } catch (e) {}\r\n // 如果提取后文件名为空(例如 URL 以 '/' 结尾),也使用时间戳\r\n if (!fileName) {\r\n fileName = Date.now().toString();\r\n }\r\n }\r\n\r\n try {\r\n const response = await fetch(url, {\r\n method: \"GET\",\r\n mode: \"cors\",\r\n cache: \"no-cache\"\r\n });\r\n\r\n if (!response.ok) {\r\n throw new Error(`HTTP error! ${response.status}: ${response.statusText}`);\r\n }\r\n\r\n const blob = await response.blob();\r\n downloadByBlob(blob, fileName);\r\n } catch (error) {\r\n downloadByUrl(url, fileName);\r\n }\r\n}\r\n"],"names":[],"mappings":"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"}
|
package/dist/download.js
CHANGED
|
@@ -77,6 +77,9 @@ async function fetchOrDownloadByUrl(url, fileName) {
|
|
|
77
77
|
const urlPathname = new URL(url).pathname;
|
|
78
78
|
// 获取路径的最后一部分作为文件名,并移除可能的查询参数
|
|
79
79
|
fileName = urlPathname.substring(urlPathname.lastIndexOf("/") + 1).split("?")[0];
|
|
80
|
+
if (fileName) {
|
|
81
|
+
fileName = decodeURIComponent(fileName);
|
|
82
|
+
}
|
|
80
83
|
} catch (e) {}
|
|
81
84
|
// 如果提取后文件名为空(例如 URL 以 '/' 结尾),也使用时间戳
|
|
82
85
|
if (!fileName) {
|
package/dist/download.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"download.js","sources":["../src/source/download.js"],"sourcesContent":["/**\r\n * 通过动态创建 `<a>` 标签触发浏览器下载。\r\n * 注意:此方法可能无法强制下载浏览器原生支持的文件(如图片、PDF),浏览器可能会选择直接打开。\r\n *\r\n * @param {string} url - 任意可下载地址(同源或允许跨域)\r\n * @param {string} [fileName] - 保存到本地的文件名;不传时使用时间戳\r\n */\r\nexport function downloadByUrl(url, fileName) {\r\n const a = document.createElement(\"a\");\r\n a.style.display = \"none\";\r\n a.rel = \"noopener\";\r\n a.href = url;\r\n a.download = fileName || Date.now();\r\n document.body.appendChild(a);\r\n a.click();\r\n document.body.removeChild(a);\r\n}\r\n\r\n/**\r\n * 把 Blob 转成临时 URL 并触发下载,下载完成后立即释放内存。\r\n *\r\n * @param {Blob} blob - 待下载的 Blob(含 File)\r\n * @param {string} [fileName] - 保存到本地的文件名\r\n */\r\nexport function downloadByBlob(blob, fileName) {\r\n const url = URL.createObjectURL(blob);\r\n downloadByUrl(url, fileName);\r\n setTimeout(() => URL.revokeObjectURL(url), 0);\r\n}\r\n\r\n/**\r\n * 将任意数据包装成 Blob 并下载。\r\n *\r\n * @param {string | ArrayBufferView | ArrayBuffer | Blob} data - 要写入文件的数据\r\n * @param {string} [fileName] - 保存到本地的文件名\r\n * @param {string} [mimeType] - MIME 类型;默认 `application/octet-stream`\r\n */\r\nexport function downloadByData(data, fileName, mimeType = \"application/octet-stream\") {\r\n downloadByBlob(new Blob([data], { type: mimeType }), fileName);\r\n}\r\n\r\n/**\r\n * 快捷下载 Excel 文件(MIME 已固定)。\r\n *\r\n * @param {string | ArrayBufferView | ArrayBuffer | Blob} data - Excel 二进制或字符串内容\r\n * @param {string} [fileName] - 保存到本地的文件名\r\n */\r\nexport function downloadExcel(data, fileName) {\r\n downloadByData(data, fileName, \"application/vnd.ms-excel\");\r\n}\r\n\r\n/**\r\n * 快捷下载 JSON 文件(MIME 已固定)。\r\n * 若传入非字符串数据,会自行 `JSON.stringify`。\r\n *\r\n * @param {any} data - 要序列化的 JSON 数据\r\n * @param {string} [fileName] - 保存到本地的文件名\r\n */\r\nexport function downloadJSON(data, fileName) {\r\n // downloadByData(typeof data === \"string\" ? data : JSON.stringify(data, null, 4), fileName, \"application/json\");\r\n downloadByData(data, fileName, \"application/json\");\r\n}\r\n\r\n/**\r\n * 通过 `fetch` 获取资源并强制下载,避免浏览器直接打开文件。\r\n * 此方法适用于下载图片、PDF 等浏览器默认会打开的文件类型。\r\n * 如果 `fetch` 失败(如 CORS 策略阻止),会回退到 `downloadByUrl` 方法作为备选方案。\r\n *\r\n * @param {string} url - 文件的 URL 地址\r\n * @param {string} [fileName] - 保存到本地的文件名。如果不提供,会尝试从 URL 中自动提取\r\n * @returns {Promise<void>} 返回一个 Promise,在下载开始或失败后 resolve\r\n */\r\nexport async function fetchOrDownloadByUrl(url, fileName) {\r\n // 如果未提供文件名,尝试从 URL 路径中提取\r\n if (!fileName) {\r\n try {\r\n const urlPathname = new URL(url).pathname;\r\n // 获取路径的最后一部分作为文件名,并移除可能的查询参数\r\n fileName = urlPathname.substring(urlPathname.lastIndexOf(\"/\") + 1).split(\"?\")[0];\r\n } catch (e) {}\r\n // 如果提取后文件名为空(例如 URL 以 '/' 结尾),也使用时间戳\r\n if (!fileName) {\r\n fileName = Date.now().toString();\r\n }\r\n }\r\n\r\n try {\r\n const response = await fetch(url, {\r\n method: \"GET\",\r\n mode: \"cors\",\r\n cache: \"no-cache\"\r\n });\r\n\r\n if (!response.ok) {\r\n throw new Error(`HTTP error! ${response.status}: ${response.statusText}`);\r\n }\r\n\r\n const blob = await response.blob();\r\n downloadByBlob(blob, fileName);\r\n } catch (error) {\r\n downloadByUrl(url, fileName);\r\n }\r\n}\r\n"],"names":[],"mappings":"AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACO,SAAS,aAAa,CAAC,GAAG,EAAE,QAAQ,EAAE;AAC7C,IAAI,MAAM,CAAC,GAAG,QAAQ,CAAC,aAAa,CAAC,GAAG,CAAC,CAAC;AAC1C,IAAI,CAAC,CAAC,KAAK,CAAC,OAAO,GAAG,MAAM,CAAC;AAC7B,IAAI,CAAC,CAAC,GAAG,GAAG,UAAU,CAAC;AACvB,IAAI,CAAC,CAAC,IAAI,GAAG,GAAG,CAAC;AACjB,IAAI,CAAC,CAAC,QAAQ,GAAG,QAAQ,IAAI,IAAI,CAAC,GAAG,EAAE,CAAC;AACxC,IAAI,QAAQ,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC;AACjC,IAAI,CAAC,CAAC,KAAK,EAAE,CAAC;AACd,IAAI,QAAQ,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC;AACjC,CAAC;AACD;AACA;AACA;AACA;AACA;AACA;AACA;AACO,SAAS,cAAc,CAAC,IAAI,EAAE,QAAQ,EAAE;AAC/C,IAAI,MAAM,GAAG,GAAG,GAAG,CAAC,eAAe,CAAC,IAAI,CAAC,CAAC;AAC1C,IAAI,aAAa,CAAC,GAAG,EAAE,QAAQ,CAAC,CAAC;AACjC,IAAI,UAAU,CAAC,MAAM,GAAG,CAAC,eAAe,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC;AAClD,CAAC;AACD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO,SAAS,cAAc,CAAC,IAAI,EAAE,QAAQ,EAAE,QAAQ,GAAG,0BAA0B,EAAE;AACtF,IAAI,cAAc,CAAC,IAAI,IAAI,CAAC,CAAC,IAAI,CAAC,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC,EAAE,QAAQ,CAAC,CAAC;AACnE,CAAC;AACD;AACA;AACA;AACA;AACA;AACA;AACA;AACO,SAAS,aAAa,CAAC,IAAI,EAAE,QAAQ,EAAE;AAC9C,IAAI,cAAc,CAAC,IAAI,EAAE,QAAQ,EAAE,0BAA0B,CAAC,CAAC;AAC/D,CAAC;AACD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO,SAAS,YAAY,CAAC,IAAI,EAAE,QAAQ,EAAE;AAC7C;AACA,IAAI,cAAc,CAAC,IAAI,EAAE,QAAQ,EAAE,kBAAkB,CAAC,CAAC;AACvD,CAAC;AACD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO,eAAe,oBAAoB,CAAC,GAAG,EAAE,QAAQ,EAAE;AAC1D;AACA,IAAI,IAAI,CAAC,QAAQ,EAAE;AACnB,QAAQ,IAAI;AACZ,YAAY,MAAM,WAAW,GAAG,IAAI,GAAG,CAAC,GAAG,CAAC,CAAC,QAAQ,CAAC;AACtD;AACA,YAAY,QAAQ,GAAG,WAAW,CAAC,SAAS,CAAC,WAAW,CAAC,WAAW,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;AAC7F,QAAQ,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;AACtB;AACA,QAAQ,IAAI,CAAC,QAAQ,EAAE;AACvB,YAAY,QAAQ,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC,QAAQ,EAAE,CAAC;AAC7C,QAAQ,CAAC;AACT,IAAI,CAAC;AACL;AACA,IAAI,IAAI;AACR,QAAQ,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,GAAG,EAAE;AAC1C,YAAY,MAAM,EAAE,KAAK;AACzB,YAAY,IAAI,EAAE,MAAM;AACxB,YAAY,KAAK,EAAE,UAAU;AAC7B,SAAS,CAAC,CAAC;AACX;AACA,QAAQ,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE;AAC1B,YAAY,MAAM,IAAI,KAAK,CAAC,CAAC,YAAY,EAAE,QAAQ,CAAC,MAAM,CAAC,EAAE,EAAE,QAAQ,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC;AACtF,QAAQ,CAAC;AACT;AACA,QAAQ,MAAM,IAAI,GAAG,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAC;AAC3C,QAAQ,cAAc,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC;AACvC,IAAI,CAAC,CAAC,OAAO,KAAK,EAAE;AACpB,QAAQ,aAAa,CAAC,GAAG,EAAE,QAAQ,CAAC,CAAC;AACrC,IAAI,CAAC;AACL;;;;"}
|
|
1
|
+
{"version":3,"file":"download.js","sources":["../src/source/download.js"],"sourcesContent":["/**\r\n * 通过动态创建 `<a>` 标签触发浏览器下载。\r\n * 注意:此方法可能无法强制下载浏览器原生支持的文件(如图片、PDF),浏览器可能会选择直接打开。\r\n *\r\n * @param {string} url - 任意可下载地址(同源或允许跨域)\r\n * @param {string} [fileName] - 保存到本地的文件名;不传时使用时间戳\r\n */\r\nexport function downloadByUrl(url, fileName) {\r\n const a = document.createElement(\"a\");\r\n a.style.display = \"none\";\r\n a.rel = \"noopener\";\r\n a.href = url;\r\n a.download = fileName || Date.now();\r\n document.body.appendChild(a);\r\n a.click();\r\n document.body.removeChild(a);\r\n}\r\n\r\n/**\r\n * 把 Blob 转成临时 URL 并触发下载,下载完成后立即释放内存。\r\n *\r\n * @param {Blob} blob - 待下载的 Blob(含 File)\r\n * @param {string} [fileName] - 保存到本地的文件名\r\n */\r\nexport function downloadByBlob(blob, fileName) {\r\n const url = URL.createObjectURL(blob);\r\n downloadByUrl(url, fileName);\r\n setTimeout(() => URL.revokeObjectURL(url), 0);\r\n}\r\n\r\n/**\r\n * 将任意数据包装成 Blob 并下载。\r\n *\r\n * @param {string | ArrayBufferView | ArrayBuffer | Blob} data - 要写入文件的数据\r\n * @param {string} [fileName] - 保存到本地的文件名\r\n * @param {string} [mimeType] - MIME 类型;默认 `application/octet-stream`\r\n */\r\nexport function downloadByData(data, fileName, mimeType = \"application/octet-stream\") {\r\n downloadByBlob(new Blob([data], { type: mimeType }), fileName);\r\n}\r\n\r\n/**\r\n * 快捷下载 Excel 文件(MIME 已固定)。\r\n *\r\n * @param {string | ArrayBufferView | ArrayBuffer | Blob} data - Excel 二进制或字符串内容\r\n * @param {string} [fileName] - 保存到本地的文件名\r\n */\r\nexport function downloadExcel(data, fileName) {\r\n downloadByData(data, fileName, \"application/vnd.ms-excel\");\r\n}\r\n\r\n/**\r\n * 快捷下载 JSON 文件(MIME 已固定)。\r\n * 若传入非字符串数据,会自行 `JSON.stringify`。\r\n *\r\n * @param {any} data - 要序列化的 JSON 数据\r\n * @param {string} [fileName] - 保存到本地的文件名\r\n */\r\nexport function downloadJSON(data, fileName) {\r\n // downloadByData(typeof data === \"string\" ? data : JSON.stringify(data, null, 4), fileName, \"application/json\");\r\n downloadByData(data, fileName, \"application/json\");\r\n}\r\n\r\n/**\r\n * 通过 `fetch` 获取资源并强制下载,避免浏览器直接打开文件。\r\n * 此方法适用于下载图片、PDF 等浏览器默认会打开的文件类型。\r\n * 如果 `fetch` 失败(如 CORS 策略阻止),会回退到 `downloadByUrl` 方法作为备选方案。\r\n *\r\n * @param {string} url - 文件的 URL 地址\r\n * @param {string} [fileName] - 保存到本地的文件名。如果不提供,会尝试从 URL 中自动提取\r\n * @returns {Promise<void>} 返回一个 Promise,在下载开始或失败后 resolve\r\n */\r\nexport async function fetchOrDownloadByUrl(url, fileName) {\r\n // 如果未提供文件名,尝试从 URL 路径中提取\r\n if (!fileName) {\r\n try {\r\n const urlPathname = new URL(url).pathname;\r\n // 获取路径的最后一部分作为文件名,并移除可能的查询参数\r\n fileName = urlPathname.substring(urlPathname.lastIndexOf(\"/\") + 1).split(\"?\")[0];\r\n if (fileName) {\r\n fileName = decodeURIComponent(fileName);\r\n }\r\n } catch (e) {}\r\n // 如果提取后文件名为空(例如 URL 以 '/' 结尾),也使用时间戳\r\n if (!fileName) {\r\n fileName = Date.now().toString();\r\n }\r\n }\r\n\r\n try {\r\n const response = await fetch(url, {\r\n method: \"GET\",\r\n mode: \"cors\",\r\n cache: \"no-cache\"\r\n });\r\n\r\n if (!response.ok) {\r\n throw new Error(`HTTP error! ${response.status}: ${response.statusText}`);\r\n }\r\n\r\n const blob = await response.blob();\r\n downloadByBlob(blob, fileName);\r\n } catch (error) {\r\n downloadByUrl(url, fileName);\r\n }\r\n}\r\n"],"names":[],"mappings":"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"}
|
package/dist/tree.cjs
CHANGED
|
@@ -74,23 +74,23 @@ function flatCompleteTree2NestedTree(nodes, parentId = 0, { idKey = "id", parent
|
|
|
74
74
|
* @param {T[]} arr - 嵌套树森林
|
|
75
75
|
* @param {string} [idKey='id'] - 主键字段
|
|
76
76
|
* @param {string} [childrenKey='children'] - 子节点字段
|
|
77
|
-
* @returns {
|
|
77
|
+
* @returns {T | undefined} 找到的节点;未找到返回 `undefined`
|
|
78
78
|
*/
|
|
79
|
-
|
|
79
|
+
function findTreeNodeById(id, arr, idKey = "id", childrenKey = "children") {
|
|
80
80
|
if (Array.isArray(arr) && arr.length > 0) {
|
|
81
81
|
for (let i = 0; i < arr.length; i++) {
|
|
82
82
|
const item = arr[i];
|
|
83
83
|
if (item[idKey]?.toString() === id?.toString()) {
|
|
84
84
|
return item;
|
|
85
85
|
} else if (Array.isArray(item[childrenKey]) && item[childrenKey].length > 0) {
|
|
86
|
-
const result =
|
|
86
|
+
const result = findTreeNodeById(id, item[childrenKey], idKey, childrenKey);
|
|
87
87
|
if (result) {
|
|
88
88
|
return result;
|
|
89
89
|
}
|
|
90
90
|
}
|
|
91
91
|
}
|
|
92
92
|
}
|
|
93
|
-
}
|
|
93
|
+
}
|
|
94
94
|
|
|
95
95
|
/**
|
|
96
96
|
* 在嵌套树中按 `id` 递归查找节点,并返回其指定属性值。
|
package/dist/tree.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tree.cjs","sources":["../src/source/tree.js"],"sourcesContent":["/**\r\n * 把嵌套树拍平成 `{ [id]: node }` 映射,同时把原 `children` 置为 `null`。\r\n *\r\n * @template T extends Record<PropertyKey, any>\r\n * @param {T[]} data - 嵌套树森林\r\n * @param {string} [idKey='id'] - 主键字段\r\n * @param {string} [childrenKey='children'] - 子节点字段\r\n * @returns {Record<string, T & { [k in typeof childrenKey]: null }>} id→节点的映射表\r\n */\r\nexport function nestedTree2IdMap(data, idKey = \"id\", childrenKey = \"children\") {\r\n const retObj = {};\r\n function fn(nodes) {\r\n if (Array.isArray(nodes) && nodes.length > 0) {\r\n nodes.forEach((node) => {\r\n retObj[node[idKey]] = { ...node };\r\n retObj[node[idKey]][childrenKey] = null;\r\n\r\n fn(node[childrenKey]);\r\n });\r\n }\r\n }\r\n fn(data);\r\n return retObj;\r\n}\r\n\r\n/**\r\n * 把**已包含完整父子关系**的扁平节点列表还原成嵌套树(森林)。\r\n *\r\n * @template T extends Record<PropertyKey, any>\r\n * @param {T[]} nodes - 扁平节点列表(必须包含 id / parentId)\r\n * @param {number | string} [parentId=0] - 根节点标识值\r\n * @param {Object} [opts] - 字段映射配置\r\n * @param {string} [opts.idKey='id'] - 节点主键\r\n * @param {string} [opts.parentKey='parentId'] - 父节点外键\r\n * @param {string} [opts.childrenKey='children'] - 存放子节点的字段\r\n * @returns {(T & { [k in typeof childrenKey]: T[] })[]} 嵌套树森林\r\n */\r\nexport function flatCompleteTree2NestedTree(nodes, parentId = 0, { idKey = \"id\", parentKey = \"parentId\", childrenKey = \"children\" } = {}) {\r\n const map = new Map(); // id -> node\r\n const items = []; // 多根森林\r\n\r\n // 1. 初始化:保证每个节点都有 children,并存入 map\r\n for (const item of nodes) {\r\n const node = { ...item, [childrenKey]: [] };\r\n map.set(item[idKey], node);\r\n }\r\n\r\n // 2. 建立父子关系\r\n for (const item of nodes) {\r\n const node = map.get(item[idKey]);\r\n const parentIdVal = item[parentKey];\r\n\r\n if (parentIdVal === parentId) {\r\n // 根层\r\n items.push(node);\r\n } else {\r\n // 非根层:找到父节点,把自己挂上去\r\n const parent = map.get(parentIdVal);\r\n if (parent) parent[childrenKey].push(node);\r\n // 如果 parent 不存在,说明数据不完整,可自定义处理\r\n }\r\n }\r\n\r\n return items;\r\n}\r\n\r\n/**\r\n * 在嵌套树中按 `id` 递归查找节点\r\n *\r\n * @template T extends Record<PropertyKey, any>\r\n * @param {string | number} id - 要查找的 id\r\n * @param {T[]} arr - 嵌套树森林\r\n * @param {string} [idKey='id'] - 主键字段\r\n * @param {string} [childrenKey='children'] - 子节点字段\r\n * @returns {any} 找到的节点;未找到返回 `undefined`\r\n */\r\nexport const findTreeNodeById = function findTreeNodeByIdFn(id, arr, idKey = \"id\", childrenKey = \"children\") {\r\n if (Array.isArray(arr) && arr.length > 0) {\r\n for (let i = 0; i < arr.length; i++) {\r\n const item = arr[i];\r\n if (item[idKey]?.toString() === id?.toString()) {\r\n return item;\r\n } else if (Array.isArray(item[childrenKey]) && item[childrenKey].length > 0) {\r\n const result = findTreeNodeByIdFn(id, item[childrenKey], idKey, childrenKey);\r\n if (result) {\r\n return result;\r\n }\r\n }\r\n }\r\n }\r\n};\r\n\r\n/**\r\n * 在嵌套树中按 `id` 递归查找节点,并返回其指定属性值。\r\n *\r\n * @template T extends Record<PropertyKey, any>\r\n * @param {string | number} id - 要查找的 id\r\n * @param {T[]} arr - 嵌套树森林\r\n * @param {string} [resultKey='name'] - 需要返回的字段\r\n * @param {string} [idKey='id'] - 主键字段\r\n * @param {string} [childrenKey='children'] - 子节点字段\r\n * @returns {any} 找到的值;未找到返回 `undefined`\r\n */\r\nexport function findObjAttrValueById(id, arr, resultKey = \"name\", idKey = \"id\", childrenKey = \"children\") {\r\n return findTreeNodeById(id, arr, idKey, childrenKey)?.[resultKey];\r\n}\r\n\r\n/**\r\n * 从服务端返回的已选 id 数组里,提取出\r\n * 1. 叶子节点\r\n * 2. 所有子节点都被选中的父节点\r\n * 其余父节点一律丢弃(由前端 Tree 自动算半选)\r\n *\r\n * @param {Array} treeData 完整树\r\n * @param {Array} selectedKeys 后端给的选中 id 数组\r\n * @param {String} idKey 节点唯一字段\r\n * @param {String} childrenKey 子节点字段\r\n * @returns {{checked: string[], halfChecked: string[]}}\r\n */\r\nexport function extractFullyCheckedKeys(treeData, selectedKeys, idKey = \"id\", childrenKey = \"children\") {\r\n const selectedSet = new Set(selectedKeys);\r\n const checked = new Set();\r\n const halfChecked = new Set();\r\n\r\n /* 返回值含义\r\n 0 - 未选中\r\n 1 - 半选\r\n 2 - 全选\r\n */\r\n function dfs(node) {\r\n const nodeId = node[idKey];\r\n const children = node[childrenKey] || [];\r\n\r\n // 叶子\r\n if (!children.length) {\r\n if (selectedSet.has(nodeId)) {\r\n checked.add(nodeId);\r\n return 2;\r\n }\r\n return 0;\r\n }\r\n\r\n // 非叶子\r\n let allChecked = true;\r\n let someChecked = false;\r\n\r\n children.forEach((child) => {\r\n const childState = dfs(child);\r\n if (childState !== 2) allChecked = false;\r\n if (childState >= 1) someChecked = true;\r\n });\r\n\r\n // 当前节点本身在 selectedKeys 里,但子节点未全选 → 只能算半选\r\n if (selectedSet.has(nodeId)) {\r\n if (allChecked) {\r\n checked.add(nodeId);\r\n return 2;\r\n }\r\n halfChecked.add(nodeId);\r\n return 1;\r\n }\r\n\r\n // 当前节点不在 selectedKeys 里,看子节点\r\n if (allChecked) {\r\n checked.add(nodeId);\r\n return 2;\r\n }\r\n if (someChecked) {\r\n halfChecked.add(nodeId);\r\n return 1;\r\n }\r\n return 0;\r\n }\r\n\r\n treeData.forEach(dfs);\r\n return {\r\n checked: [...checked],\r\n halfChecked: [...halfChecked]\r\n };\r\n}\r\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"tree.cjs","sources":["../src/source/tree.js"],"sourcesContent":["/**\r\n * 把嵌套树拍平成 `{ [id]: node }` 映射,同时把原 `children` 置为 `null`。\r\n *\r\n * @template T extends Record<PropertyKey, any>\r\n * @param {T[]} data - 嵌套树森林\r\n * @param {string} [idKey='id'] - 主键字段\r\n * @param {string} [childrenKey='children'] - 子节点字段\r\n * @returns {Record<string, T & { [k in typeof childrenKey]: null }>} id→节点的映射表\r\n */\r\nexport function nestedTree2IdMap(data, idKey = \"id\", childrenKey = \"children\") {\r\n const retObj = {};\r\n function fn(nodes) {\r\n if (Array.isArray(nodes) && nodes.length > 0) {\r\n nodes.forEach((node) => {\r\n retObj[node[idKey]] = { ...node };\r\n retObj[node[idKey]][childrenKey] = null;\r\n\r\n fn(node[childrenKey]);\r\n });\r\n }\r\n }\r\n fn(data);\r\n return retObj;\r\n}\r\n\r\n/**\r\n * 把**已包含完整父子关系**的扁平节点列表还原成嵌套树(森林)。\r\n *\r\n * @template T extends Record<PropertyKey, any>\r\n * @param {T[]} nodes - 扁平节点列表(必须包含 id / parentId)\r\n * @param {number | string} [parentId=0] - 根节点标识值\r\n * @param {Object} [opts] - 字段映射配置\r\n * @param {string} [opts.idKey='id'] - 节点主键\r\n * @param {string} [opts.parentKey='parentId'] - 父节点外键\r\n * @param {string} [opts.childrenKey='children'] - 存放子节点的字段\r\n * @returns {(T & { [k in typeof childrenKey]: T[] })[]} 嵌套树森林\r\n */\r\nexport function flatCompleteTree2NestedTree(nodes, parentId = 0, { idKey = \"id\", parentKey = \"parentId\", childrenKey = \"children\" } = {}) {\r\n const map = new Map(); // id -> node\r\n const items = []; // 多根森林\r\n\r\n // 1. 初始化:保证每个节点都有 children,并存入 map\r\n for (const item of nodes) {\r\n const node = { ...item, [childrenKey]: [] };\r\n map.set(item[idKey], node);\r\n }\r\n\r\n // 2. 建立父子关系\r\n for (const item of nodes) {\r\n const node = map.get(item[idKey]);\r\n const parentIdVal = item[parentKey];\r\n\r\n if (parentIdVal === parentId) {\r\n // 根层\r\n items.push(node);\r\n } else {\r\n // 非根层:找到父节点,把自己挂上去\r\n const parent = map.get(parentIdVal);\r\n if (parent) parent[childrenKey].push(node);\r\n // 如果 parent 不存在,说明数据不完整,可自定义处理\r\n }\r\n }\r\n\r\n return items;\r\n}\r\n\r\n/**\r\n * 在嵌套树中按 `id` 递归查找节点\r\n *\r\n * @template T extends Record<PropertyKey, any>\r\n * @param {string | number} id - 要查找的 id\r\n * @param {T[]} arr - 嵌套树森林\r\n * @param {string} [idKey='id'] - 主键字段\r\n * @param {string} [childrenKey='children'] - 子节点字段\r\n * @returns {T | undefined} 找到的节点;未找到返回 `undefined`\r\n */\r\nexport function findTreeNodeById(id, arr, idKey = \"id\", childrenKey = \"children\") {\r\n if (Array.isArray(arr) && arr.length > 0) {\r\n for (let i = 0; i < arr.length; i++) {\r\n const item = arr[i];\r\n if (item[idKey]?.toString() === id?.toString()) {\r\n return item;\r\n } else if (Array.isArray(item[childrenKey]) && item[childrenKey].length > 0) {\r\n const result = findTreeNodeById(id, item[childrenKey], idKey, childrenKey);\r\n if (result) {\r\n return result;\r\n }\r\n }\r\n }\r\n }\r\n}\r\n\r\n/**\r\n * 在嵌套树中按 `id` 递归查找节点,并返回其指定属性值。\r\n *\r\n * @template T extends Record<PropertyKey, any>\r\n * @param {string | number} id - 要查找的 id\r\n * @param {T[]} arr - 嵌套树森林\r\n * @param {string} [resultKey='name'] - 需要返回的字段\r\n * @param {string} [idKey='id'] - 主键字段\r\n * @param {string} [childrenKey='children'] - 子节点字段\r\n * @returns {any} 找到的值;未找到返回 `undefined`\r\n */\r\nexport function findObjAttrValueById(id, arr, resultKey = \"name\", idKey = \"id\", childrenKey = \"children\") {\r\n return findTreeNodeById(id, arr, idKey, childrenKey)?.[resultKey];\r\n}\r\n\r\n/**\r\n * 从服务端返回的已选 id 数组里,提取出\r\n * 1. 叶子节点\r\n * 2. 所有子节点都被选中的父节点\r\n * 其余父节点一律丢弃(由前端 Tree 自动算半选)\r\n *\r\n * @param {Array} treeData 完整树\r\n * @param {Array} selectedKeys 后端给的选中 id 数组\r\n * @param {String} idKey 节点唯一字段\r\n * @param {String} childrenKey 子节点字段\r\n * @returns {{checked: string[], halfChecked: string[]}}\r\n */\r\nexport function extractFullyCheckedKeys(treeData, selectedKeys, idKey = \"id\", childrenKey = \"children\") {\r\n const selectedSet = new Set(selectedKeys);\r\n const checked = new Set();\r\n const halfChecked = new Set();\r\n\r\n /* 返回值含义\r\n 0 - 未选中\r\n 1 - 半选\r\n 2 - 全选\r\n */\r\n function dfs(node) {\r\n const nodeId = node[idKey];\r\n const children = node[childrenKey] || [];\r\n\r\n // 叶子\r\n if (!children.length) {\r\n if (selectedSet.has(nodeId)) {\r\n checked.add(nodeId);\r\n return 2;\r\n }\r\n return 0;\r\n }\r\n\r\n // 非叶子\r\n let allChecked = true;\r\n let someChecked = false;\r\n\r\n children.forEach((child) => {\r\n const childState = dfs(child);\r\n if (childState !== 2) allChecked = false;\r\n if (childState >= 1) someChecked = true;\r\n });\r\n\r\n // 当前节点本身在 selectedKeys 里,但子节点未全选 → 只能算半选\r\n if (selectedSet.has(nodeId)) {\r\n if (allChecked) {\r\n checked.add(nodeId);\r\n return 2;\r\n }\r\n halfChecked.add(nodeId);\r\n return 1;\r\n }\r\n\r\n // 当前节点不在 selectedKeys 里,看子节点\r\n if (allChecked) {\r\n checked.add(nodeId);\r\n return 2;\r\n }\r\n if (someChecked) {\r\n halfChecked.add(nodeId);\r\n return 1;\r\n }\r\n return 0;\r\n }\r\n\r\n treeData.forEach(dfs);\r\n return {\r\n checked: [...checked],\r\n halfChecked: [...halfChecked]\r\n };\r\n}\r\n"],"names":[],"mappings":"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"}
|
package/dist/tree.js
CHANGED
|
@@ -72,23 +72,23 @@ function flatCompleteTree2NestedTree(nodes, parentId = 0, { idKey = "id", parent
|
|
|
72
72
|
* @param {T[]} arr - 嵌套树森林
|
|
73
73
|
* @param {string} [idKey='id'] - 主键字段
|
|
74
74
|
* @param {string} [childrenKey='children'] - 子节点字段
|
|
75
|
-
* @returns {
|
|
75
|
+
* @returns {T | undefined} 找到的节点;未找到返回 `undefined`
|
|
76
76
|
*/
|
|
77
|
-
|
|
77
|
+
function findTreeNodeById(id, arr, idKey = "id", childrenKey = "children") {
|
|
78
78
|
if (Array.isArray(arr) && arr.length > 0) {
|
|
79
79
|
for (let i = 0; i < arr.length; i++) {
|
|
80
80
|
const item = arr[i];
|
|
81
81
|
if (item[idKey]?.toString() === id?.toString()) {
|
|
82
82
|
return item;
|
|
83
83
|
} else if (Array.isArray(item[childrenKey]) && item[childrenKey].length > 0) {
|
|
84
|
-
const result =
|
|
84
|
+
const result = findTreeNodeById(id, item[childrenKey], idKey, childrenKey);
|
|
85
85
|
if (result) {
|
|
86
86
|
return result;
|
|
87
87
|
}
|
|
88
88
|
}
|
|
89
89
|
}
|
|
90
90
|
}
|
|
91
|
-
}
|
|
91
|
+
}
|
|
92
92
|
|
|
93
93
|
/**
|
|
94
94
|
* 在嵌套树中按 `id` 递归查找节点,并返回其指定属性值。
|
package/dist/tree.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tree.js","sources":["../src/source/tree.js"],"sourcesContent":["/**\r\n * 把嵌套树拍平成 `{ [id]: node }` 映射,同时把原 `children` 置为 `null`。\r\n *\r\n * @template T extends Record<PropertyKey, any>\r\n * @param {T[]} data - 嵌套树森林\r\n * @param {string} [idKey='id'] - 主键字段\r\n * @param {string} [childrenKey='children'] - 子节点字段\r\n * @returns {Record<string, T & { [k in typeof childrenKey]: null }>} id→节点的映射表\r\n */\r\nexport function nestedTree2IdMap(data, idKey = \"id\", childrenKey = \"children\") {\r\n const retObj = {};\r\n function fn(nodes) {\r\n if (Array.isArray(nodes) && nodes.length > 0) {\r\n nodes.forEach((node) => {\r\n retObj[node[idKey]] = { ...node };\r\n retObj[node[idKey]][childrenKey] = null;\r\n\r\n fn(node[childrenKey]);\r\n });\r\n }\r\n }\r\n fn(data);\r\n return retObj;\r\n}\r\n\r\n/**\r\n * 把**已包含完整父子关系**的扁平节点列表还原成嵌套树(森林)。\r\n *\r\n * @template T extends Record<PropertyKey, any>\r\n * @param {T[]} nodes - 扁平节点列表(必须包含 id / parentId)\r\n * @param {number | string} [parentId=0] - 根节点标识值\r\n * @param {Object} [opts] - 字段映射配置\r\n * @param {string} [opts.idKey='id'] - 节点主键\r\n * @param {string} [opts.parentKey='parentId'] - 父节点外键\r\n * @param {string} [opts.childrenKey='children'] - 存放子节点的字段\r\n * @returns {(T & { [k in typeof childrenKey]: T[] })[]} 嵌套树森林\r\n */\r\nexport function flatCompleteTree2NestedTree(nodes, parentId = 0, { idKey = \"id\", parentKey = \"parentId\", childrenKey = \"children\" } = {}) {\r\n const map = new Map(); // id -> node\r\n const items = []; // 多根森林\r\n\r\n // 1. 初始化:保证每个节点都有 children,并存入 map\r\n for (const item of nodes) {\r\n const node = { ...item, [childrenKey]: [] };\r\n map.set(item[idKey], node);\r\n }\r\n\r\n // 2. 建立父子关系\r\n for (const item of nodes) {\r\n const node = map.get(item[idKey]);\r\n const parentIdVal = item[parentKey];\r\n\r\n if (parentIdVal === parentId) {\r\n // 根层\r\n items.push(node);\r\n } else {\r\n // 非根层:找到父节点,把自己挂上去\r\n const parent = map.get(parentIdVal);\r\n if (parent) parent[childrenKey].push(node);\r\n // 如果 parent 不存在,说明数据不完整,可自定义处理\r\n }\r\n }\r\n\r\n return items;\r\n}\r\n\r\n/**\r\n * 在嵌套树中按 `id` 递归查找节点\r\n *\r\n * @template T extends Record<PropertyKey, any>\r\n * @param {string | number} id - 要查找的 id\r\n * @param {T[]} arr - 嵌套树森林\r\n * @param {string} [idKey='id'] - 主键字段\r\n * @param {string} [childrenKey='children'] - 子节点字段\r\n * @returns {any} 找到的节点;未找到返回 `undefined`\r\n */\r\nexport const findTreeNodeById = function findTreeNodeByIdFn(id, arr, idKey = \"id\", childrenKey = \"children\") {\r\n if (Array.isArray(arr) && arr.length > 0) {\r\n for (let i = 0; i < arr.length; i++) {\r\n const item = arr[i];\r\n if (item[idKey]?.toString() === id?.toString()) {\r\n return item;\r\n } else if (Array.isArray(item[childrenKey]) && item[childrenKey].length > 0) {\r\n const result = findTreeNodeByIdFn(id, item[childrenKey], idKey, childrenKey);\r\n if (result) {\r\n return result;\r\n }\r\n }\r\n }\r\n }\r\n};\r\n\r\n/**\r\n * 在嵌套树中按 `id` 递归查找节点,并返回其指定属性值。\r\n *\r\n * @template T extends Record<PropertyKey, any>\r\n * @param {string | number} id - 要查找的 id\r\n * @param {T[]} arr - 嵌套树森林\r\n * @param {string} [resultKey='name'] - 需要返回的字段\r\n * @param {string} [idKey='id'] - 主键字段\r\n * @param {string} [childrenKey='children'] - 子节点字段\r\n * @returns {any} 找到的值;未找到返回 `undefined`\r\n */\r\nexport function findObjAttrValueById(id, arr, resultKey = \"name\", idKey = \"id\", childrenKey = \"children\") {\r\n return findTreeNodeById(id, arr, idKey, childrenKey)?.[resultKey];\r\n}\r\n\r\n/**\r\n * 从服务端返回的已选 id 数组里,提取出\r\n * 1. 叶子节点\r\n * 2. 所有子节点都被选中的父节点\r\n * 其余父节点一律丢弃(由前端 Tree 自动算半选)\r\n *\r\n * @param {Array} treeData 完整树\r\n * @param {Array} selectedKeys 后端给的选中 id 数组\r\n * @param {String} idKey 节点唯一字段\r\n * @param {String} childrenKey 子节点字段\r\n * @returns {{checked: string[], halfChecked: string[]}}\r\n */\r\nexport function extractFullyCheckedKeys(treeData, selectedKeys, idKey = \"id\", childrenKey = \"children\") {\r\n const selectedSet = new Set(selectedKeys);\r\n const checked = new Set();\r\n const halfChecked = new Set();\r\n\r\n /* 返回值含义\r\n 0 - 未选中\r\n 1 - 半选\r\n 2 - 全选\r\n */\r\n function dfs(node) {\r\n const nodeId = node[idKey];\r\n const children = node[childrenKey] || [];\r\n\r\n // 叶子\r\n if (!children.length) {\r\n if (selectedSet.has(nodeId)) {\r\n checked.add(nodeId);\r\n return 2;\r\n }\r\n return 0;\r\n }\r\n\r\n // 非叶子\r\n let allChecked = true;\r\n let someChecked = false;\r\n\r\n children.forEach((child) => {\r\n const childState = dfs(child);\r\n if (childState !== 2) allChecked = false;\r\n if (childState >= 1) someChecked = true;\r\n });\r\n\r\n // 当前节点本身在 selectedKeys 里,但子节点未全选 → 只能算半选\r\n if (selectedSet.has(nodeId)) {\r\n if (allChecked) {\r\n checked.add(nodeId);\r\n return 2;\r\n }\r\n halfChecked.add(nodeId);\r\n return 1;\r\n }\r\n\r\n // 当前节点不在 selectedKeys 里,看子节点\r\n if (allChecked) {\r\n checked.add(nodeId);\r\n return 2;\r\n }\r\n if (someChecked) {\r\n halfChecked.add(nodeId);\r\n return 1;\r\n }\r\n return 0;\r\n }\r\n\r\n treeData.forEach(dfs);\r\n return {\r\n checked: [...checked],\r\n halfChecked: [...halfChecked]\r\n };\r\n}\r\n"],"names":[],"mappings":"AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO,SAAS,gBAAgB,CAAC,IAAI,EAAE,KAAK,GAAG,IAAI,EAAE,WAAW,GAAG,UAAU,EAAE;AAC/E,IAAI,MAAM,MAAM,GAAG,EAAE,CAAC;AACtB,IAAI,SAAS,EAAE,CAAC,KAAK,EAAE;AACvB,QAAQ,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,IAAI,KAAK,CAAC,MAAM,GAAG,CAAC,EAAE;AACtD,YAAY,KAAK,CAAC,OAAO,CAAC,CAAC,IAAI,KAAK;AACpC,gBAAgB,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,GAAG,EAAE,GAAG,IAAI,EAAE,CAAC;AAClD,gBAAgB,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,WAAW,CAAC,GAAG,IAAI,CAAC;AACxD;AACA,gBAAgB,EAAE,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC,CAAC;AACtC,YAAY,CAAC,CAAC,CAAC;AACf,QAAQ,CAAC;AACT,IAAI,CAAC;AACL,IAAI,EAAE,CAAC,IAAI,CAAC,CAAC;AACb,IAAI,OAAO,MAAM,CAAC;AAClB,CAAC;AACD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO,SAAS,2BAA2B,CAAC,KAAK,EAAE,QAAQ,GAAG,CAAC,EAAE,EAAE,KAAK,GAAG,IAAI,EAAE,SAAS,GAAG,UAAU,EAAE,WAAW,GAAG,UAAU,EAAE,GAAG,EAAE,EAAE;AAC1I,IAAI,MAAM,GAAG,GAAG,IAAI,GAAG,EAAE,CAAC;AAC1B,IAAI,MAAM,KAAK,GAAG,EAAE,CAAC;AACrB;AACA;AACA,IAAI,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE;AAC9B,QAAQ,MAAM,IAAI,GAAG,EAAE,GAAG,IAAI,EAAE,CAAC,WAAW,GAAG,EAAE,EAAE,CAAC;AACpD,QAAQ,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,IAAI,CAAC,CAAC;AACnC,IAAI,CAAC;AACL;AACA;AACA,IAAI,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE;AAC9B,QAAQ,MAAM,IAAI,GAAG,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC;AAC1C,QAAQ,MAAM,WAAW,GAAG,IAAI,CAAC,SAAS,CAAC,CAAC;AAC5C;AACA,QAAQ,IAAI,WAAW,KAAK,QAAQ,EAAE;AACtC;AACA,YAAY,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAC7B,QAAQ,CAAC,MAAM;AACf;AACA,YAAY,MAAM,MAAM,GAAG,GAAG,CAAC,GAAG,CAAC,WAAW,CAAC,CAAC;AAChD,YAAY,IAAI,MAAM,EAAE,MAAM,CAAC,WAAW,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AACvD;AACA,QAAQ,CAAC;AACT,IAAI,CAAC;AACL;AACA,IAAI,OAAO,KAAK,CAAC;AACjB,CAAC;AACD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACY,MAAC,gBAAgB,GAAG,SAAS,kBAAkB,CAAC,EAAE,EAAE,GAAG,EAAE,KAAK,GAAG,IAAI,EAAE,WAAW,GAAG,UAAU,EAAE;AAC7G,IAAI,IAAI,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,GAAG,CAAC,MAAM,GAAG,CAAC,EAAE;AAC9C,QAAQ,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,GAAG,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE;AAC7C,YAAY,MAAM,IAAI,GAAG,GAAG,CAAC,CAAC,CAAC,CAAC;AAChC,YAAY,IAAI,IAAI,CAAC,KAAK,CAAC,EAAE,QAAQ,EAAE,KAAK,EAAE,EAAE,QAAQ,EAAE,EAAE;AAC5D,gBAAgB,OAAO,IAAI,CAAC;AAC5B,YAAY,CAAC,MAAM,IAAI,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC,IAAI,IAAI,CAAC,WAAW,CAAC,CAAC,MAAM,GAAG,CAAC,EAAE;AACzF,gBAAgB,MAAM,MAAM,GAAG,kBAAkB,CAAC,EAAE,EAAE,IAAI,CAAC,WAAW,CAAC,EAAE,KAAK,EAAE,WAAW,CAAC,CAAC;AAC7F,gBAAgB,IAAI,MAAM,EAAE;AAC5B,oBAAoB,OAAO,MAAM,CAAC;AAClC,gBAAgB,CAAC;AACjB,YAAY,CAAC;AACb,QAAQ,CAAC;AACT,IAAI,CAAC;AACL,EAAE;AACF;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO,SAAS,oBAAoB,CAAC,EAAE,EAAE,GAAG,EAAE,SAAS,GAAG,MAAM,EAAE,KAAK,GAAG,IAAI,EAAE,WAAW,GAAG,UAAU,EAAE;AAC1G,IAAI,OAAO,gBAAgB,CAAC,EAAE,EAAE,GAAG,EAAE,KAAK,EAAE,WAAW,CAAC,GAAG,SAAS,CAAC,CAAC;AACtE,CAAC;AACD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO,SAAS,uBAAuB,CAAC,QAAQ,EAAE,YAAY,EAAE,KAAK,GAAG,IAAI,EAAE,WAAW,GAAG,UAAU,EAAE;AACxG,IAAI,MAAM,WAAW,GAAG,IAAI,GAAG,CAAC,YAAY,CAAC,CAAC;AAC9C,IAAI,MAAM,OAAO,GAAG,IAAI,GAAG,EAAE,CAAC;AAC9B,IAAI,MAAM,WAAW,GAAG,IAAI,GAAG,EAAE,CAAC;AAClC;AACA;AACA;AACA;AACA;AACA;AACA,IAAI,SAAS,GAAG,CAAC,IAAI,EAAE;AACvB,QAAQ,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC;AACnC,QAAQ,MAAM,QAAQ,GAAG,IAAI,CAAC,WAAW,CAAC,IAAI,EAAE,CAAC;AACjD;AACA;AACA,QAAQ,IAAI,CAAC,QAAQ,CAAC,MAAM,EAAE;AAC9B,YAAY,IAAI,WAAW,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE;AACzC,gBAAgB,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;AACpC,gBAAgB,OAAO,CAAC,CAAC;AACzB,YAAY,CAAC;AACb,YAAY,OAAO,CAAC,CAAC;AACrB,QAAQ,CAAC;AACT;AACA;AACA,QAAQ,IAAI,UAAU,GAAG,IAAI,CAAC;AAC9B,QAAQ,IAAI,WAAW,GAAG,KAAK,CAAC;AAChC;AACA,QAAQ,QAAQ,CAAC,OAAO,CAAC,CAAC,KAAK,KAAK;AACpC,YAAY,MAAM,UAAU,GAAG,GAAG,CAAC,KAAK,CAAC,CAAC;AAC1C,YAAY,IAAI,UAAU,KAAK,CAAC,EAAE,UAAU,GAAG,KAAK,CAAC;AACrD,YAAY,IAAI,UAAU,IAAI,CAAC,EAAE,WAAW,GAAG,IAAI,CAAC;AACpD,QAAQ,CAAC,CAAC,CAAC;AACX;AACA;AACA,QAAQ,IAAI,WAAW,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE;AACrC,YAAY,IAAI,UAAU,EAAE;AAC5B,gBAAgB,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;AACpC,gBAAgB,OAAO,CAAC,CAAC;AACzB,YAAY,CAAC;AACb,YAAY,WAAW,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;AACpC,YAAY,OAAO,CAAC,CAAC;AACrB,QAAQ,CAAC;AACT;AACA;AACA,QAAQ,IAAI,UAAU,EAAE;AACxB,YAAY,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;AAChC,YAAY,OAAO,CAAC,CAAC;AACrB,QAAQ,CAAC;AACT,QAAQ,IAAI,WAAW,EAAE;AACzB,YAAY,WAAW,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;AACpC,YAAY,OAAO,CAAC,CAAC;AACrB,QAAQ,CAAC;AACT,QAAQ,OAAO,CAAC,CAAC;AACjB,IAAI,CAAC;AACL;AACA,IAAI,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;AAC1B,IAAI,OAAO;AACX,QAAQ,OAAO,EAAE,CAAC,GAAG,OAAO,CAAC;AAC7B,QAAQ,WAAW,EAAE,CAAC,GAAG,WAAW,CAAC;AACrC,KAAK,CAAC;AACN;;;;"}
|
|
1
|
+
{"version":3,"file":"tree.js","sources":["../src/source/tree.js"],"sourcesContent":["/**\r\n * 把嵌套树拍平成 `{ [id]: node }` 映射,同时把原 `children` 置为 `null`。\r\n *\r\n * @template T extends Record<PropertyKey, any>\r\n * @param {T[]} data - 嵌套树森林\r\n * @param {string} [idKey='id'] - 主键字段\r\n * @param {string} [childrenKey='children'] - 子节点字段\r\n * @returns {Record<string, T & { [k in typeof childrenKey]: null }>} id→节点的映射表\r\n */\r\nexport function nestedTree2IdMap(data, idKey = \"id\", childrenKey = \"children\") {\r\n const retObj = {};\r\n function fn(nodes) {\r\n if (Array.isArray(nodes) && nodes.length > 0) {\r\n nodes.forEach((node) => {\r\n retObj[node[idKey]] = { ...node };\r\n retObj[node[idKey]][childrenKey] = null;\r\n\r\n fn(node[childrenKey]);\r\n });\r\n }\r\n }\r\n fn(data);\r\n return retObj;\r\n}\r\n\r\n/**\r\n * 把**已包含完整父子关系**的扁平节点列表还原成嵌套树(森林)。\r\n *\r\n * @template T extends Record<PropertyKey, any>\r\n * @param {T[]} nodes - 扁平节点列表(必须包含 id / parentId)\r\n * @param {number | string} [parentId=0] - 根节点标识值\r\n * @param {Object} [opts] - 字段映射配置\r\n * @param {string} [opts.idKey='id'] - 节点主键\r\n * @param {string} [opts.parentKey='parentId'] - 父节点外键\r\n * @param {string} [opts.childrenKey='children'] - 存放子节点的字段\r\n * @returns {(T & { [k in typeof childrenKey]: T[] })[]} 嵌套树森林\r\n */\r\nexport function flatCompleteTree2NestedTree(nodes, parentId = 0, { idKey = \"id\", parentKey = \"parentId\", childrenKey = \"children\" } = {}) {\r\n const map = new Map(); // id -> node\r\n const items = []; // 多根森林\r\n\r\n // 1. 初始化:保证每个节点都有 children,并存入 map\r\n for (const item of nodes) {\r\n const node = { ...item, [childrenKey]: [] };\r\n map.set(item[idKey], node);\r\n }\r\n\r\n // 2. 建立父子关系\r\n for (const item of nodes) {\r\n const node = map.get(item[idKey]);\r\n const parentIdVal = item[parentKey];\r\n\r\n if (parentIdVal === parentId) {\r\n // 根层\r\n items.push(node);\r\n } else {\r\n // 非根层:找到父节点,把自己挂上去\r\n const parent = map.get(parentIdVal);\r\n if (parent) parent[childrenKey].push(node);\r\n // 如果 parent 不存在,说明数据不完整,可自定义处理\r\n }\r\n }\r\n\r\n return items;\r\n}\r\n\r\n/**\r\n * 在嵌套树中按 `id` 递归查找节点\r\n *\r\n * @template T extends Record<PropertyKey, any>\r\n * @param {string | number} id - 要查找的 id\r\n * @param {T[]} arr - 嵌套树森林\r\n * @param {string} [idKey='id'] - 主键字段\r\n * @param {string} [childrenKey='children'] - 子节点字段\r\n * @returns {T | undefined} 找到的节点;未找到返回 `undefined`\r\n */\r\nexport function findTreeNodeById(id, arr, idKey = \"id\", childrenKey = \"children\") {\r\n if (Array.isArray(arr) && arr.length > 0) {\r\n for (let i = 0; i < arr.length; i++) {\r\n const item = arr[i];\r\n if (item[idKey]?.toString() === id?.toString()) {\r\n return item;\r\n } else if (Array.isArray(item[childrenKey]) && item[childrenKey].length > 0) {\r\n const result = findTreeNodeById(id, item[childrenKey], idKey, childrenKey);\r\n if (result) {\r\n return result;\r\n }\r\n }\r\n }\r\n }\r\n}\r\n\r\n/**\r\n * 在嵌套树中按 `id` 递归查找节点,并返回其指定属性值。\r\n *\r\n * @template T extends Record<PropertyKey, any>\r\n * @param {string | number} id - 要查找的 id\r\n * @param {T[]} arr - 嵌套树森林\r\n * @param {string} [resultKey='name'] - 需要返回的字段\r\n * @param {string} [idKey='id'] - 主键字段\r\n * @param {string} [childrenKey='children'] - 子节点字段\r\n * @returns {any} 找到的值;未找到返回 `undefined`\r\n */\r\nexport function findObjAttrValueById(id, arr, resultKey = \"name\", idKey = \"id\", childrenKey = \"children\") {\r\n return findTreeNodeById(id, arr, idKey, childrenKey)?.[resultKey];\r\n}\r\n\r\n/**\r\n * 从服务端返回的已选 id 数组里,提取出\r\n * 1. 叶子节点\r\n * 2. 所有子节点都被选中的父节点\r\n * 其余父节点一律丢弃(由前端 Tree 自动算半选)\r\n *\r\n * @param {Array} treeData 完整树\r\n * @param {Array} selectedKeys 后端给的选中 id 数组\r\n * @param {String} idKey 节点唯一字段\r\n * @param {String} childrenKey 子节点字段\r\n * @returns {{checked: string[], halfChecked: string[]}}\r\n */\r\nexport function extractFullyCheckedKeys(treeData, selectedKeys, idKey = \"id\", childrenKey = \"children\") {\r\n const selectedSet = new Set(selectedKeys);\r\n const checked = new Set();\r\n const halfChecked = new Set();\r\n\r\n /* 返回值含义\r\n 0 - 未选中\r\n 1 - 半选\r\n 2 - 全选\r\n */\r\n function dfs(node) {\r\n const nodeId = node[idKey];\r\n const children = node[childrenKey] || [];\r\n\r\n // 叶子\r\n if (!children.length) {\r\n if (selectedSet.has(nodeId)) {\r\n checked.add(nodeId);\r\n return 2;\r\n }\r\n return 0;\r\n }\r\n\r\n // 非叶子\r\n let allChecked = true;\r\n let someChecked = false;\r\n\r\n children.forEach((child) => {\r\n const childState = dfs(child);\r\n if (childState !== 2) allChecked = false;\r\n if (childState >= 1) someChecked = true;\r\n });\r\n\r\n // 当前节点本身在 selectedKeys 里,但子节点未全选 → 只能算半选\r\n if (selectedSet.has(nodeId)) {\r\n if (allChecked) {\r\n checked.add(nodeId);\r\n return 2;\r\n }\r\n halfChecked.add(nodeId);\r\n return 1;\r\n }\r\n\r\n // 当前节点不在 selectedKeys 里,看子节点\r\n if (allChecked) {\r\n checked.add(nodeId);\r\n return 2;\r\n }\r\n if (someChecked) {\r\n halfChecked.add(nodeId);\r\n return 1;\r\n }\r\n return 0;\r\n }\r\n\r\n treeData.forEach(dfs);\r\n return {\r\n checked: [...checked],\r\n halfChecked: [...halfChecked]\r\n };\r\n}\r\n"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
package/types/index.d.ts
CHANGED
|
@@ -942,6 +942,17 @@ declare function flatCompleteTree2NestedTree<T>(nodes: T[], parentId?: number |
|
|
|
942
942
|
parentKey?: string | undefined;
|
|
943
943
|
childrenKey?: string | undefined;
|
|
944
944
|
}): (T & { [k in typeof childrenKey]: T[]; })[];
|
|
945
|
+
/**
|
|
946
|
+
* 在嵌套树中按 `id` 递归查找节点
|
|
947
|
+
*
|
|
948
|
+
* @template T extends Record<PropertyKey, any>
|
|
949
|
+
* @param {string | number} id - 要查找的 id
|
|
950
|
+
* @param {T[]} arr - 嵌套树森林
|
|
951
|
+
* @param {string} [idKey='id'] - 主键字段
|
|
952
|
+
* @param {string} [childrenKey='children'] - 子节点字段
|
|
953
|
+
* @returns {T | undefined} 找到的节点;未找到返回 `undefined`
|
|
954
|
+
*/
|
|
955
|
+
declare function findTreeNodeById<T>(id: string | number, arr: T[], idKey?: string, childrenKey?: string): T | undefined;
|
|
945
956
|
/**
|
|
946
957
|
* 在嵌套树中按 `id` 递归查找节点,并返回其指定属性值。
|
|
947
958
|
*
|
|
@@ -970,6 +981,5 @@ declare function extractFullyCheckedKeys(treeData: any[], selectedKeys: any[], i
|
|
|
970
981
|
checked: string[];
|
|
971
982
|
halfChecked: string[];
|
|
972
983
|
};
|
|
973
|
-
declare function findTreeNodeById<T>(id: string | number, arr: T[], idKey?: string, childrenKey?: string): any;
|
|
974
984
|
|
|
975
985
|
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 };
|
package/types/tree.d.ts
CHANGED
|
@@ -25,6 +25,17 @@ declare function flatCompleteTree2NestedTree<T>(nodes: T[], parentId?: number |
|
|
|
25
25
|
parentKey?: string | undefined;
|
|
26
26
|
childrenKey?: string | undefined;
|
|
27
27
|
}): (T & { [k in typeof childrenKey]: T[]; })[];
|
|
28
|
+
/**
|
|
29
|
+
* 在嵌套树中按 `id` 递归查找节点
|
|
30
|
+
*
|
|
31
|
+
* @template T extends Record<PropertyKey, any>
|
|
32
|
+
* @param {string | number} id - 要查找的 id
|
|
33
|
+
* @param {T[]} arr - 嵌套树森林
|
|
34
|
+
* @param {string} [idKey='id'] - 主键字段
|
|
35
|
+
* @param {string} [childrenKey='children'] - 子节点字段
|
|
36
|
+
* @returns {T | undefined} 找到的节点;未找到返回 `undefined`
|
|
37
|
+
*/
|
|
38
|
+
declare function findTreeNodeById<T>(id: string | number, arr: T[], idKey?: string, childrenKey?: string): T | undefined;
|
|
28
39
|
/**
|
|
29
40
|
* 在嵌套树中按 `id` 递归查找节点,并返回其指定属性值。
|
|
30
41
|
*
|
|
@@ -53,6 +64,5 @@ declare function extractFullyCheckedKeys(treeData: any[], selectedKeys: any[], i
|
|
|
53
64
|
checked: string[];
|
|
54
65
|
halfChecked: string[];
|
|
55
66
|
};
|
|
56
|
-
declare function findTreeNodeById<T>(id: string | number, arr: T[], idKey?: string, childrenKey?: string): any;
|
|
57
67
|
|
|
58
68
|
export { extractFullyCheckedKeys, findObjAttrValueById, findTreeNodeById, flatCompleteTree2NestedTree, nestedTree2IdMap };
|