285 lines
6.6 KiB
JavaScript
285 lines
6.6 KiB
JavaScript
/**
|
||
* 下载管理工具
|
||
* 文件保存到公共下载目录,用户可通过文件管理器访问
|
||
*/
|
||
|
||
const STORAGE_KEY = 'localDownloads'
|
||
const SUB_DIR = 'AppDownloads' // 公共下载目录下的子文件夹
|
||
|
||
/**
|
||
* 获取公共下载目录路径
|
||
* @returns {Promise<string>} 下载目录绝对路径
|
||
*/
|
||
function getPublicDownloadDir() {
|
||
return new Promise((resolve, reject) => {
|
||
// #ifdef APP-PLUS
|
||
plus.io.requestFileSystem(
|
||
plus.io.PUBLIC_DOWNLOADS,
|
||
(fs) => {
|
||
// PUBLIC_DOWNLOADS 对应 /storage/emulated/0/Download/
|
||
fs.root.getDirectory(
|
||
SUB_DIR,
|
||
{ create: true },
|
||
(dirEntry) => {
|
||
resolve(dirEntry.fullPath)
|
||
},
|
||
(err) => {
|
||
console.error('getDirectory failed:', err)
|
||
reject(err)
|
||
}
|
||
)
|
||
},
|
||
(err) => {
|
||
console.error('requestFileSystem failed:', err)
|
||
reject(err)
|
||
}
|
||
)
|
||
// #endif
|
||
// #ifndef APP-PLUS
|
||
reject(new Error('仅APP端支持'))
|
||
// #endif
|
||
})
|
||
}
|
||
|
||
/**
|
||
* 从完整路径中提取可读的显示路径
|
||
* 如 /storage/emulated/0/Download/AppDownloads/xxx.pdf → 下载/AppDownloads/xxx.pdf
|
||
*/
|
||
function getDisplayPath(fullPath) {
|
||
const idx = fullPath.indexOf('/Download/')
|
||
if (idx !== -1) {
|
||
return '下载/' + fullPath.slice(idx + 10)
|
||
}
|
||
return fullPath
|
||
}
|
||
|
||
/**
|
||
* 获取所有下载记录
|
||
* @returns {Array} 下载记录列表
|
||
*/
|
||
export function getDownloadList() {
|
||
try {
|
||
return uni.getStorageSync(STORAGE_KEY) || []
|
||
} catch (e) {
|
||
return []
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 保存下载记录(复制到公共下载目录)
|
||
* @param {string} tempFilePath 临时文件路径(uni.downloadFile 返回的)
|
||
* @param {string} fileName 文件名
|
||
* @param {number} fileSize 文件大小(字节,可选)
|
||
*/
|
||
export function saveDownload(tempFilePath, fileName, fileSize) {
|
||
return new Promise((resolve, reject) => {
|
||
// #ifdef APP-PLUS
|
||
getPublicDownloadDir().then((dirPath) => {
|
||
const destPathForUrl = plus.io.convertLocalFileSystemURL(dirPath + '/' + fileName)
|
||
|
||
plus.io.resolveLocalFileSystemURL(
|
||
tempFilePath,
|
||
(fileEntry) => {
|
||
plus.io.resolveLocalFileSystemURL(
|
||
dirPath,
|
||
(dirEntry) => {
|
||
fileEntry.copyTo(
|
||
dirEntry,
|
||
fileName,
|
||
() => {
|
||
const record = {
|
||
id: Date.now().toString(),
|
||
name: fileName,
|
||
path: destPathForUrl,
|
||
displayPath: getDisplayPath(destPathForUrl),
|
||
size: fileSize || 0,
|
||
time: Date.now()
|
||
}
|
||
const list = getDownloadList()
|
||
const exists = list.some(item => item.name === fileName)
|
||
if (exists) {
|
||
const idx = list.findIndex(item => item.name === fileName)
|
||
if (idx !== -1) list.splice(idx, 1)
|
||
}
|
||
list.unshift(record)
|
||
try {
|
||
uni.setStorageSync(STORAGE_KEY, list)
|
||
} catch (e) {
|
||
list.pop()
|
||
uni.setStorageSync(STORAGE_KEY, list)
|
||
}
|
||
resolve(record)
|
||
},
|
||
(err) => {
|
||
console.error('copyTo failed:', err)
|
||
reject(err)
|
||
}
|
||
)
|
||
},
|
||
reject
|
||
)
|
||
},
|
||
reject
|
||
)
|
||
}).catch(reject)
|
||
// #endif
|
||
// #ifndef APP-PLUS
|
||
// 非 APP 端降级使用 uni.saveFile
|
||
uni.saveFile({
|
||
tempFilePath: tempFilePath,
|
||
success: (saveRes) => {
|
||
const record = {
|
||
id: Date.now().toString(),
|
||
name: fileName,
|
||
path: saveRes.savedFilePath,
|
||
size: fileSize || 0,
|
||
time: Date.now()
|
||
}
|
||
const list = getDownloadList()
|
||
list.unshift(record)
|
||
uni.setStorageSync(STORAGE_KEY, list)
|
||
resolve(record)
|
||
},
|
||
fail: reject
|
||
})
|
||
// #endif
|
||
})
|
||
}
|
||
|
||
/**
|
||
* 删除单条下载记录及其文件
|
||
* @param {string} id 记录 ID
|
||
*/
|
||
export function deleteDownload(id) {
|
||
return new Promise((resolve, reject) => {
|
||
const list = getDownloadList()
|
||
const record = list.find(item => item.id === id)
|
||
if (!record) {
|
||
reject(new Error('记录不存在'))
|
||
return
|
||
}
|
||
|
||
// #ifdef APP-PLUS
|
||
// 删除公共目录下的文件
|
||
plus.io.resolveLocalFileSystemURL(
|
||
record.path,
|
||
(fileEntry) => {
|
||
fileEntry.remove(
|
||
() => {
|
||
const newList = list.filter(item => item.id !== id)
|
||
uni.setStorageSync(STORAGE_KEY, newList)
|
||
resolve()
|
||
},
|
||
() => {
|
||
// 文件不存在也清除记录
|
||
const newList = list.filter(item => item.id !== id)
|
||
uni.setStorageSync(STORAGE_KEY, newList)
|
||
resolve()
|
||
}
|
||
)
|
||
},
|
||
() => {
|
||
// 无法解析路径,直接清除记录
|
||
const newList = list.filter(item => item.id !== id)
|
||
uni.setStorageSync(STORAGE_KEY, newList)
|
||
resolve()
|
||
}
|
||
)
|
||
// #endif
|
||
// #ifndef APP-PLUS
|
||
uni.removeSavedFile({
|
||
filePath: record.path,
|
||
success: () => {
|
||
const newList = list.filter(item => item.id !== id)
|
||
uni.setStorageSync(STORAGE_KEY, newList)
|
||
resolve()
|
||
},
|
||
fail: () => {
|
||
const newList = list.filter(item => item.id !== id)
|
||
uni.setStorageSync(STORAGE_KEY, newList)
|
||
resolve()
|
||
}
|
||
})
|
||
// #endif
|
||
})
|
||
}
|
||
|
||
/**
|
||
* 清空所有下载记录和文件
|
||
*/
|
||
export function clearAllDownloads() {
|
||
return new Promise((resolve) => {
|
||
const list = getDownloadList()
|
||
if (list.length === 0) {
|
||
resolve()
|
||
return
|
||
}
|
||
|
||
// #ifdef APP-PLUS
|
||
let count = 0
|
||
const done = () => {
|
||
count++
|
||
if (count >= list.length) {
|
||
uni.setStorageSync(STORAGE_KEY, [])
|
||
resolve()
|
||
}
|
||
}
|
||
|
||
list.forEach(record => {
|
||
plus.io.resolveLocalFileSystemURL(
|
||
record.path,
|
||
(fileEntry) => {
|
||
fileEntry.remove(done, done)
|
||
},
|
||
done
|
||
)
|
||
})
|
||
|
||
setTimeout(() => {
|
||
uni.setStorageSync(STORAGE_KEY, [])
|
||
resolve()
|
||
}, 5000)
|
||
// #endif
|
||
// #ifndef APP-PLUS
|
||
let count = 0
|
||
list.forEach(record => {
|
||
uni.removeSavedFile({
|
||
filePath: record.path,
|
||
success: () => { count++; if (count >= list.length) { uni.setStorageSync(STORAGE_KEY, []); resolve() } },
|
||
fail: () => { count++; if (count >= list.length) { uni.setStorageSync(STORAGE_KEY, []); resolve() } }
|
||
})
|
||
})
|
||
if (list.length === 0) resolve()
|
||
setTimeout(() => { uni.setStorageSync(STORAGE_KEY, []); resolve() }, 3000)
|
||
// #endif
|
||
})
|
||
}
|
||
|
||
/**
|
||
* 格式化文件大小为可读字符串
|
||
* @param {number} bytes 字节数
|
||
* @returns {string}
|
||
*/
|
||
export function formatFileSize(bytes) {
|
||
if (!bytes || bytes <= 0) return '未知大小'
|
||
const units = ['B', 'KB', 'MB', 'GB']
|
||
let i = 0
|
||
let size = bytes
|
||
while (size >= 1024 && i < units.length - 1) {
|
||
size /= 1024
|
||
i++
|
||
}
|
||
return size.toFixed(i === 0 ? 0 : 1) + ' ' + units[i]
|
||
}
|
||
|
||
/**
|
||
* 格式化时间戳为可读日期
|
||
* @param {number} timestamp
|
||
* @returns {string}
|
||
*/
|
||
export function formatTime(timestamp) {
|
||
const d = new Date(timestamp)
|
||
const pad = n => String(n).padStart(2, '0')
|
||
return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())} ${pad(d.getHours())}:${pad(d.getMinutes())}`
|
||
}
|