// WebDAV Driver for VueFinder import request from '../../utils/request' import { getApiBaseUrl } from '../../utils/config' // 解析 WebDAV PROPFIND XML 响应 export function parseWebDAVXML(xmlString) { try { // 确保输入是字符串 if (typeof xmlString !== 'string') { console.error('parseWebDAVXML: 输入不是字符串类型', typeof xmlString, xmlString) throw new Error('XML 响应必须是字符串类型') } // 清理和提取有效的 XML 内容 let cleanedXml = xmlString.trim() // 如果响应包含多个 XML 文档或末尾有额外内容,尝试提取第一个完整的 XML 文档 // 查找第一个 /i const endMatch = cleanedXml.substring(startIndex).match(endTagPattern) if (endMatch) { // 提取完整的 XML 文档 const endIndex = startIndex + endMatch.index + endMatch[0].length cleanedXml = cleanedXml.substring(startIndex, endIndex) // console.log('提取了有效的 XML 片段,原始长度:', xmlString.length, '清理后长度:', cleanedXml.length) } else { console.warn('未找到 XML 结束标签,使用原始响应') } } // 如果仍然没有找到有效的 XML,尝试查找第一个 if (!cleanedXml.includes('multistatus') && !cleanedXml.includes(' { if (!parent || !parent.getElementsByTagName) return null const allElements = parent.getElementsByTagName('*') for (let i = 0; i < allElements.length; i++) { const elem = allElements[i] const elemLocalName = elem.localName || elem.tagName?.split(':').pop() || elem.tagName if (elemLocalName === localName || elem.tagName === localName || elem.tagName?.endsWith(':' + localName)) { return elem } } return null } const getElementTextByLocalName = (parent, localName) => { const element = getElementByLocalName(parent, localName) return element?.textContent || '' } // 获取所有 response 节点 const allElements = xmlDoc.getElementsByTagName('*') const responses = [] for (let i = 0; i < allElements.length; i++) { const elem = allElements[i] const elemLocalName = elem.localName || elem.tagName?.split(':').pop() || elem.tagName if (elemLocalName === 'response' || elem.tagName === 'response' || elem.tagName?.endsWith(':response')) { responses.push(elem) } } const fileList = [] responses.forEach((response) => { try { const href = getElementTextByLocalName(response, 'href') // 跳过根路径 if (href === '/' || !href) { return } const propstat = getElementByLocalName(response, 'propstat') if (!propstat) return const prop = getElementByLocalName(propstat, 'prop') if (!prop) return // 检查是否为集合(文件夹) const resourcetype = getElementByLocalName(prop, 'resourcetype') const collectionNode = resourcetype ? getElementByLocalName(resourcetype, 'collection') : null const isCollection = collectionNode !== null // 获取文件大小 const getcontentlengthText = getElementTextByLocalName(prop, 'getcontentlength') const size = getcontentlengthText ? parseInt(getcontentlengthText, 10) : 0 // 获取修改时间 const lastModified = getElementTextByLocalName(prop, 'getlastmodified') // 获取内容类型 const getcontenttypeText = getElementTextByLocalName(prop, 'getcontenttype') const contentType = getcontenttypeText || (isCollection ? 'directory' : 'application/octet-stream') // 从路径中提取文件名和相对路径 // href 格式可能是: /folda/ 或 /filea,可能是 URL 编码的 // 先对 href 进行 URL 解码 let decodedHref = href try { decodedHref = decodeURIComponent(href) } catch (e) { // 如果解码失败,使用原始 href decodedHref = href } const cleanHref = decodedHref.replace(/\/$/, '') // 移除末尾斜杠 const pathParts = cleanHref.split('/').filter(p => p) const name = pathParts[pathParts.length - 1] || cleanHref || 'unknown' // 构建相对路径(相对于 webdav 根目录) // 例如: /folda/ -> folda, /filea -> filea // 注意:相对路径也需要解码后的值 const relativePath = pathParts.join('/') fileList.push({ href: href, name: name, isDirectory: isCollection, size: size, lastModified: lastModified, contentType: contentType, path: relativePath, // 相对路径,例如: "folda" 或 "filea" }) } catch (err) { console.warn('解析单个 response 失败:', err) } }) return fileList } catch (error) { console.error('解析 WebDAV XML 失败:', error) throw error } } // 将 WebDAV 文件列表转换为 VueFinder 格式 function convertToVueFinderFormat(webdavFiles, basePath = '') { const items = [] webdavFiles.forEach(file => { // VueFinder 期望的格式:path应该以/开头 let itemPath if (basePath && basePath !== '' && basePath !== '/') { const normalizedBasePath = basePath.startsWith('/') ? basePath : '/' + basePath itemPath = normalizedBasePath + '/' + file.name } else { // 根目录下的文件,path应该是 /folda 或 /filea itemPath = '/' + file.name } const item = { name: file.name, path: itemPath, // 绝对路径,例如: /folda 或 /filea type: file.isDirectory ? 'dir' : 'file', size: file.size || 0, modified: file.lastModified ? new Date(file.lastModified).getTime() : Date.now(), isDirectory: file.isDirectory, } items.push(item) }) // 目录排在前面 items.sort((a, b) => { if (a.type === 'dir' && b.type !== 'dir') return -1 if (a.type !== 'dir' && b.type === 'dir') return 1 return a.name.localeCompare(b.name) }) // 返回的path表示当前目录路径 // 对于根目录,path应该是空字符串;对于子目录,path应该是相对路径(不带开头的/) let normalizedPath = basePath || '' if (normalizedPath === '/') { normalizedPath = '' } return { items: items, path: normalizedPath, // 当前目录路径,根目录为空字符串 } } // 创建 WebDAV Driver export function createWebDAVDriver() { const baseURL = getApiBaseUrl() || '' const basePath = `${baseURL}/webdav` return { async list(params = {}) { try { // 构建路径,确保 URL 格式为 webdav/ 或 webdav/path/ let path = params.path || '' // 移除开头的 /,因为我们要构建相对路径用于API请求 if (path.startsWith('/')) { path = path.substring(1) } // 如果 path 为空,请求 webdav/,否则请求 webdav/path/ const url = path ? `/webdav/${path}${path.endsWith('/') ? '' : '/'}` : '/webdav/' // console.log('WebDAV list 请求:', { url, path, params }) // 发送 PROPFIND 请求,使用 Depth: 1 请求头 const response = await request({ url: url, method: 'PROPFIND', headers: { 'Depth': '1' } }) // console.log('WebDAV 原始响应:', response) // 解析 XML 响应 const fileList = parseWebDAVXML(response) // console.log('解析后的文件列表:', fileList) // 转换为 VueFinder 格式 // normalizedPath用于构建item的path,应该保持原样(不带开头的/) const normalizedPath = path || '' const vueFinderData = convertToVueFinderFormat(fileList, normalizedPath) // console.log('VueFinder 格式数据:', vueFinderData) // console.log('VueFinder items 数量:', vueFinderData.items.length) // console.log('VueFinder items 详情:', vueFinderData.items) // 确保返回的数据格式正确 if (!vueFinderData.items || !Array.isArray(vueFinderData.items)) { console.error('VueFinder 数据格式错误:', vueFinderData) return { items: [], path: normalizedPath } } // 验证每个 item 的格式,确保所有必需字段都存在 vueFinderData.items.forEach((item, index) => { if (!item.name || !item.path || !item.type) { console.warn(`Item ${index} 格式不完整:`, item) } // 确保path是字符串格式 if (typeof item.path !== 'string') { item.path = String(item.path) } // 确保size是数字 if (typeof item.size !== 'number') { item.size = Number(item.size) || 0 } }) console.log('返回给 VueFinder 的数据:', JSON.stringify(vueFinderData, null, 2)) console.log('最终返回数据:', vueFinderData) return vueFinderData } catch (error) { console.error('WebDAV list 失败:', error) throw error } }, async delete(params) { return request({ url: `/webdav/${params.path}`, method: 'DELETE' }) }, async rename(params) { // WebDAV MOVE 方法用于重命名 // 对新路径进行 URL 编码(对每个路径段分别编码,保留斜杠) const encodedNewPath = params.newPath .split('/') .map(segment => encodeURIComponent(segment)) .join('/') return request({ url: `/webdav/${params.path}`, method: 'MOVE', headers: { 'Destination': `/webdav/${encodedNewPath}` } }) }, getDownloadUrl(params) { const token = localStorage.getItem('token') const uid = localStorage.getItem('uid') const path = params.path?.startsWith('/') ? params.path.substring(1) : params.path return `${basePath}/${path}?token=${token}&uid=${uid}` }, getPreviewUrl(params) { return this.getDownloadUrl(params) }, async search(params) { // 搜索功能可能需要遍历所有目录 // 这里先实现简单的列表 const result = await this.list({ path: params.path || '' }) return result.items.filter(item => item.name.toLowerCase().includes(params.query.toLowerCase()) ) }, async save(params) { // 上传文件使用 PUT 方法 return request({ url: `/webdav/${params.path}`, method: 'PUT', data: params.content, headers: { 'Content-Type': params.contentType || 'application/octet-stream' } }) } } }