mirror of
https://github.com/qaiu/netdisk-fast-download.git
synced 2026-10-11 01:04:26 +00:00
Align with VIP: default recursive batch depth is 5 (checked folder = depth 0). Still configurable via batchMaxDepth; warn when remaining folders hit the cap. Co-authored-by: qaiu <[email protected]>
153 lines
4.1 KiB
JavaScript
153 lines
4.1 KiB
JavaScript
/**
|
||
* 文件树批量勾选:按深度递归展开并收集可下载文件。
|
||
*
|
||
* Depth convention (checked folder = depth 0):
|
||
* - 用户勾选的文件夹为第 0 层。
|
||
* - 其直接子项为第 1 层。
|
||
* - 当 depth < maxDepth 时继续展开子文件夹;默认 maxDepth = 5,
|
||
* 即最多再向下展开 5 层(第 0/1/2/3/4 层会 load,第 5 层文件夹不再展开)。
|
||
* - 触及上限时若仍有未加载/剩余子文件夹,标记 depthExceeded,由 UI 提示跳过。
|
||
*/
|
||
|
||
const DEFAULT_BATCH_MAX_DEPTH = 5
|
||
|
||
function getTreeNodeId(item) {
|
||
if (!item) return ''
|
||
if (item.id) return String(item.id)
|
||
if (item.fileId) return `fid:${item.fileId}`
|
||
if (item.parserUrl) return `url:${item.parserUrl}`
|
||
return `name:${item.fileName || ''}:${item.fileType || ''}`
|
||
}
|
||
|
||
function normalizeTreeItem(item) {
|
||
if (!item || typeof item !== 'object') return item
|
||
return {
|
||
...item,
|
||
id: getTreeNodeId(item),
|
||
isLeaf: item.fileType !== 'folder'
|
||
}
|
||
}
|
||
|
||
function fileIdentity(file) {
|
||
return `${file?.parserUrl || ''}::${file?.fileName || ''}::${file?.fileId || file?.id || ''}`
|
||
}
|
||
|
||
function uniqueFiles(files) {
|
||
const seen = new Set()
|
||
const out = []
|
||
for (const file of files || []) {
|
||
const key = fileIdentity(file)
|
||
if (!key || seen.has(key)) continue
|
||
seen.add(key)
|
||
out.push(file)
|
||
}
|
||
return out
|
||
}
|
||
|
||
function isFolderNode(data) {
|
||
return !!(data && data.fileType === 'folder')
|
||
}
|
||
|
||
function collectAlreadyLoadedFiles(node, isDownloadable, ctx) {
|
||
for (const child of node.childNodes || []) {
|
||
if (isDownloadable(child.data)) {
|
||
ctx.files.push(child.data)
|
||
} else if (isFolderNode(child.data) && child.loaded) {
|
||
collectAlreadyLoadedFiles(child, isDownloadable, ctx)
|
||
}
|
||
}
|
||
}
|
||
|
||
function hasUnexploredFolders(node) {
|
||
if (!node) return false
|
||
if (isFolderNode(node.data) && !node.loaded) return true
|
||
return (node.childNodes || []).some((child) => {
|
||
if (!isFolderNode(child.data)) return false
|
||
if (!child.loaded) return true
|
||
return hasUnexploredFolders(child)
|
||
})
|
||
}
|
||
|
||
/**
|
||
* 递归展开文件夹并收集可下载文件(对接 el-tree node / loadNode)。
|
||
*
|
||
* @param {object} opts
|
||
* @param {object} opts.folderData 勾选的文件夹 data(depth 从 0 计)
|
||
* @param {number} [opts.depth=0]
|
||
* @param {number} opts.maxDepth
|
||
* @param {(data: object) => any} opts.getNode el-tree getNode
|
||
* @param {(node: any) => Promise<any>} opts.ensureLoaded 展开并触发懒加载
|
||
* @param {(data: object) => boolean} opts.isDownloadable
|
||
* @param {Set<string>} [opts.visitedKeys]
|
||
* @param {(data: object) => string} [opts.getKey]
|
||
* @param {{ files: object[], depthExceeded: boolean }} [opts.ctx]
|
||
*/
|
||
async function collectFolderFiles(opts) {
|
||
const {
|
||
folderData,
|
||
depth = 0,
|
||
maxDepth,
|
||
getNode,
|
||
ensureLoaded,
|
||
isDownloadable,
|
||
getKey = getTreeNodeId,
|
||
visitedKeys = new Set(),
|
||
ctx = { files: [], depthExceeded: false }
|
||
} = opts
|
||
|
||
if (!folderData || typeof maxDepth !== 'number') return ctx
|
||
|
||
const key = getKey(folderData)
|
||
if (key) visitedKeys.add(key)
|
||
|
||
const node = getNode(folderData)
|
||
if (!node) return ctx
|
||
|
||
if (depth >= maxDepth) {
|
||
// 未 load 的节点在真实 el-tree 中没有 childNodes,不能把预置子项算进去
|
||
if (node.loaded) {
|
||
collectAlreadyLoadedFiles(node, isDownloadable, ctx)
|
||
if (hasUnexploredFolders(node)) {
|
||
ctx.depthExceeded = true
|
||
}
|
||
} else if (isFolderNode(node.data)) {
|
||
ctx.depthExceeded = true
|
||
}
|
||
return ctx
|
||
}
|
||
|
||
try {
|
||
await ensureLoaded(node)
|
||
} catch {
|
||
// 单层加载失败:跳过该分支,不中断整次收集
|
||
return ctx
|
||
}
|
||
|
||
for (const child of node.childNodes || []) {
|
||
const data = child.data
|
||
if (!data) continue
|
||
if (isDownloadable(data)) {
|
||
ctx.files.push(data)
|
||
} else if (isFolderNode(data)) {
|
||
await collectFolderFiles({
|
||
...opts,
|
||
folderData: data,
|
||
depth: depth + 1,
|
||
visitedKeys,
|
||
ctx
|
||
})
|
||
}
|
||
}
|
||
return ctx
|
||
}
|
||
|
||
module.exports = {
|
||
DEFAULT_BATCH_MAX_DEPTH,
|
||
getTreeNodeId,
|
||
normalizeTreeItem,
|
||
fileIdentity,
|
||
uniqueFiles,
|
||
isFolderNode,
|
||
collectFolderFiles
|
||
}
|