1、移动文件到指定目录
// 文件/文件夹移动脚本 - 带合并提示的增强版
// 使用说明:修改下方的 targetDirectory 为你想要移动文件的目标目录路径
// 注意:目标目录必须已存在,否则脚本将提示错误
const fs = require('fs')
const path = require('path')
const { promisify } = require('util')
const crypto = require('crypto')
// 将fs方法转换为Promise版本
const renameAsync = promisify(fs.rename)
const statAsync = promisify(fs.stat)
const mkdirAsync = promisify(fs.mkdir)
const readdirAsync = promisify(fs.readdir)
const readFileAsync = promisify(fs.readFile)
// ⚠️ 请在这里修改为你想要移动文件的目标目录路径 ⚠️
// ⚠️ 注意:此目录必须已存在,脚本不会自动创建 ⚠️
const targetDirectory = 'C:\\Your\\Target\\Directory\\'
// 检查目录是否存在
async function checkDirectoryExists(dirPath) {
try {
const stats = await statAsync(dirPath)
return stats.isDirectory()
} catch (error) {
if (error.code === 'ENOENT') return false
throw error
}
}
// 确保子目录存在,不存在则创建(仅用于合并文件夹时)
async function ensureSubDirectoryExists(dirPath) {
try {
await mkdirAsync(dirPath, { recursive: true })
} catch (error) {
if (error.code !== 'EEXIST') throw error
}
}
// 检查目标位置是否存在同名项
async function checkNameConflict(source, target) {
try {
await statAsync(target)
return true // 存在冲突
} catch (error) {
if (error.code === 'ENOENT') return false // 不存在冲突
throw error // 其他错误
}
}
// 计算文件的MD5哈希值
async function getFileHash(filePath) {
try {
const data = await readFileAsync(filePath)
return crypto.createHash('md5').update(data).digest('hex')
} catch (error) {
console.error(`计算文件哈希值失败: ${filePath}`, error)
return null
}
}
// 比较两个文件是否相同
async function areFilesIdentical(file1, file2) {
try {
const hash1 = await getFileHash(file1)
const hash2 = await getFileHash(file2)
return hash1 === hash2
} catch (error) {
console.error(`比较文件失败: ${file1} 和 ${file2}`, error)
return false
}
}
// 合并文件夹内容(只移动不同的文件)
async function mergeFolderContents(source, target) {
const conflicts = []
const identical = []
const moved = []
let mergeNoticeShown = false
try {
// 确保目标子目录存在
await ensureSubDirectoryExists(target)
const items = await readdirAsync(source)
for (const item of items) {
const sourcePath = path.join(source, item)
const targetPath = path.join(target, item)
const stats = await statAsync(sourcePath)
if (stats.isDirectory()) {
if (await checkNameConflict(sourcePath, targetPath)) {
// 显示同级文件夹合并提示(只显示一次)
if (!mergeNoticeShown) {
console.log(`正在合并文件夹: ${path.basename(source)} -> ${path.basename(target)}`)
mergeNoticeShown = true
}
// 递归处理子文件夹
const subResult = await mergeFolderContents(sourcePath, targetPath)
if (subResult.conflicts) conflicts.push(...subResult.conflicts)
if (subResult.identical) identical.push(...subResult.identical)
if (subResult.moved) moved.push(...subResult.moved)
} else {
// 直接移动子文件夹
await renameAsync(sourcePath, targetPath)
moved.push(path.relative(source, sourcePath))
}
} else {
if (await checkNameConflict(sourcePath, targetPath)) {
// 检查文件是否相同
const filesAreIdentical = await areFilesIdentical(sourcePath, targetPath)
if (filesAreIdentical) {
console.log(`跳过相同文件: ${path.relative(source, sourcePath)}`)
identical.push(path.relative(source, sourcePath))
} else {
console.log(`跳过不同内容的同名文件: ${path.relative(source, sourcePath)}`)
conflicts.push(path.relative(source, sourcePath))
}
continue // 跳过当前文件
}
await renameAsync(sourcePath, targetPath)
moved.push(path.relative(source, sourcePath))
}
}
const relativePath = path.relative(path.dirname(source), source)
return {
success: true,
conflicts,
identical,
moved,
message: moved.length > 0
? `合并完成: ${relativePath} (移动了 ${moved.length} 个文件/文件夹)`
: `没有需要移动的文件: ${relativePath}`
}
} catch (error) {
console.error(`合并文件夹出错:`, error)
return {
success: false,
message: `合并 ${path.basename(source)} 时出错: ${error.message}`
}
}
}
// 移动单个文件(遇到同名时检查内容)
async function moveFile(source, target) {
try {
if (await checkNameConflict(source, target)) {
// 检查文件是否相同
const filesAreIdentical = await areFilesIdentical(source, target)
if (filesAreIdentical) {
console.log(`跳过相同文件: ${path.basename(source)}`)
return {
success: false,
identical: true,
message: `已跳过: ${path.basename(source)} (文件内容相同)`
}
} else {
console.log(`跳过不同内容的同名文件: ${path.basename(source)}`)
return {
success: false,
skipped: true,
message: `已跳过: ${path.basename(source)} (同名文件内容不同)`
}
}
}
await renameAsync(source, target)
return { success: true, message: `已移动: ${path.basename(source)}` }
} catch (error) {
console.error(`移动文件出错:`, error)
return { success: false, message: `移动 ${path.basename(source)} 出错: ${error.message}` }
}
}
// 主处理函数
async function moveItemsToTarget() {
// 检查目标目录是否存在
const targetExists = await checkDirectoryExists(targetDirectory)
if (!targetExists) {
const errorMessage = `目标目录不存在: ${targetDirectory}\n请修改脚本中的 targetDirectory 变量为正确的目录路径。`
console.error(errorMessage)
utools.showNotification(errorMessage)
return []
}
console.log('开始移动操作...')
console.log(`目标目录: ${targetDirectory}`)
const results = await Promise.all(ENTER.payload.map(async (item) => {
const sourcePath = item.path
const itemName = path.basename(sourcePath)
const targetPath = path.join(targetDirectory, itemName)
try {
const stats = await statAsync(sourcePath)
if (stats.isDirectory()) {
if (await checkNameConflict(sourcePath, targetPath)) {
console.log(`发现同名文件夹: ${itemName}, 开始合并...`)
return await mergeFolderContents(sourcePath, targetPath)
} else {
console.log(`移动文件夹: ${itemName}`)
await renameAsync(sourcePath, targetPath)
return { success: true, message: `已移动文件夹: ${itemName}` }
}
} else if (stats.isFile()) {
console.log(`处理文件: ${itemName}`)
return await moveFile(sourcePath, targetPath)
}
return { success: false, message: `不支持的类型: ${itemName}` }
} catch (error) {
console.error(`处理项目出错:`, error)
return { success: false, message: `处理 ${itemName} 出错: ${error.message}` }
}
}))
// 统计结果
const successCount = results.filter(r => r.success).length
const skippedCount = results.filter(r => r.skipped).length
const identicalCount = results.filter(r => r.identical).length
const failedCount = results.filter(r => !r.success && !r.skipped && !r.identical).length
// 统计移动的文件
const movedFiles = results
.filter(result => result.moved && result.moved.length > 0)
.flatMap(result => result.moved)
// 统计冲突情况
const conflicts = results
.filter(result => result.conflicts && result.conflicts.length > 0)
.flatMap(result => result.conflicts)
// 显示结果通知
let notificationMessage = ''
if (successCount > 0 || movedFiles.length > 0) {
const totalMoved = successCount + movedFiles.length
notificationMessage += `成功移动: ${totalMoved} 项\n`
}
if (skippedCount > 0 || conflicts.length > 0) {
const totalSkipped = skippedCount + conflicts.length
notificationMessage += `跳过不同内容: ${totalSkipped} 项\n`
}
if (identicalCount > 0) {
notificationMessage += `跳过相同内容: ${identicalCount} 项\n`
}
if (failedCount > 0) {
notificationMessage += `失败: ${failedCount} 项\n`
}
// 确保通知始终显示
if (notificationMessage === '') {
notificationMessage = '操作完成,但没有处理任何项目'
} else {
notificationMessage = `移动操作完成\n${notificationMessage}`
}
utools.showNotification(notificationMessage.trim())
console.log('操作最终结果:', results)
return results
}
// 主处理逻辑
if (ENTER.type === 'files') {
console.log('开始处理选中的文件/文件夹...')
moveItemsToTarget().catch(error => {
console.error('操作出错:', error)
utools.showNotification(`操作失败: ${error.message}`)
})
} else if (ENTER.type === 'window') {
utools.showNotification('请在文件管理器中选择文件或文件夹后再执行此操作')
} else {
utools.showNotification('不支持的操作类型')
}