From e56bd78096fc7e7233d6b1afa05dbc08ba5e3135 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=90=8C=E8=90=8C=E5=93=92=E8=B5=AB=E8=90=9D?= Date: Sat, 12 Aug 2023 01:00:40 -0700 Subject: [PATCH] :construction: WIP: add local path picbed for manage page --- package.json | 1 + src/main/apis/app/uploader/apis.ts | 39 +-- src/main/manage/apis/api.ts | 2 + src/main/manage/apis/local.ts | 319 +++++++++++++++++++++++ src/main/manage/apis/webdavplist.ts | 11 +- src/main/manage/manageApi.ts | 37 +-- src/main/utils/beforeOpen.ts | 13 +- src/renderer/apis/aliyun.ts | 2 +- src/renderer/manage/pages/bucketPage.vue | 67 +++-- src/renderer/manage/pages/logIn.vue | 2 +- src/renderer/manage/pages/manageMain.vue | 17 +- src/renderer/manage/utils/constants.ts | 4 +- src/renderer/utils/LS.ts | 15 +- yarn.lock | 30 ++- 14 files changed, 457 insertions(+), 102 deletions(-) diff --git a/package.json b/package.json index eb665ec..0ba6d6e 100644 --- a/package.json +++ b/package.json @@ -37,6 +37,7 @@ "@aws-sdk/s3-request-presigner": "^3.388.0", "@element-plus/icons-vue": "^2.1.0", "@highlightjs/vue-plugin": "^2.1.0", + "@nodelib/fs.walk": "^2.0.0", "@octokit/rest": "^19.0.7", "@picgo/i18n": "^1.0.0", "@picgo/store": "^2.0.4", diff --git a/src/main/apis/app/uploader/apis.ts b/src/main/apis/app/uploader/apis.ts index 2badb13..6b81d64 100644 --- a/src/main/apis/app/uploader/apis.ts +++ b/src/main/apis/app/uploader/apis.ts @@ -154,42 +154,21 @@ export const deleteChoosedFiles = async (list: ImgInfo[]): Promise => const file = await dbStore.removeById(item.id) if (await picgo.getConfig('settings.deleteCloudFile')) { if (item.type !== undefined && picBedsCanbeDeleted.includes(item.type)) { + const noteFunc = (value: boolean) => { + const notification = new Notification({ + title: T('MANAGE_BUCKET_BATCH_DELETE_ERROR_MSG_MSG2'), + body: T(value ? 'GALLERY_SYNC_DELETE_NOTICE_SUCCEED' : 'GALLERY_SYNC_DELETE_NOTICE_FAILED') + }) + notification.show() + } if (item.type === 'webdavplist') { const { fileName, config } = item setTimeout(() => { - deleteWebdavFile(getRawData(config), fileName || '').then((value: boolean) => { - if (value) { - const notification = new Notification({ - title: T('MANAGE_BUCKET_BATCH_DELETE_ERROR_MSG_MSG2'), - body: T('GALLERY_SYNC_DELETE_NOTICE_SUCCEED') - }) - notification.show() - } else { - const notification = new Notification({ - title: T('MANAGE_BUCKET_BATCH_DELETE_ERROR_MSG_MSG2'), - body: T('GALLERY_SYNC_DELETE_NOTICE_FAILED') - }) - notification.show() - } - }) + deleteWebdavFile(getRawData(config), fileName || '').then(noteFunc) }, 0) } else { setTimeout(() => { - ALLApi.delete(item).then((value: boolean) => { - if (value) { - const notification = new Notification({ - title: T('MANAGE_BUCKET_BATCH_DELETE_ERROR_MSG_MSG2'), - body: T('GALLERY_SYNC_DELETE_NOTICE_SUCCEED') - }) - notification.show() - } else { - const notification = new Notification({ - title: T('MANAGE_BUCKET_BATCH_DELETE_ERROR_MSG_MSG2'), - body: T('GALLERY_SYNC_DELETE_NOTICE_FAILED') - }) - notification.show() - } - }) + ALLApi.delete(item).then(noteFunc) }, 0) } } diff --git a/src/main/manage/apis/api.ts b/src/main/manage/apis/api.ts index e1a63b4..e240f9f 100644 --- a/src/main/manage/apis/api.ts +++ b/src/main/manage/apis/api.ts @@ -1,6 +1,7 @@ import AliyunApi from './aliyun' import GithubApi from './github' import ImgurApi from './imgur' +import LocalApi from './local' import QiniuApi from './qiniu' import S3plistApi from './s3plist' import SmmsApi from './smms' @@ -12,6 +13,7 @@ export default { AliyunApi, GithubApi, ImgurApi, + LocalApi, QiniuApi, S3plistApi, SmmsApi, diff --git a/src/main/manage/apis/local.ts b/src/main/manage/apis/local.ts index e69de29..6fe4de2 100644 --- a/src/main/manage/apis/local.ts +++ b/src/main/manage/apis/local.ts @@ -0,0 +1,319 @@ +// 日志记录器 +import ManageLogger from '../utils/logger' + +// 错误格式化函数、端点地址格式化函数、获取内部代理、新的下载器、并发异步任务池 +import { formatError } from '../utils/common' + +// HTTP 代理格式化函数、是否为图片的判断函数 +import { isImage } from '@/manage/utils/common' + +// 窗口管理器 +import windowManager from 'apis/app/window/windowManager' + +// 枚举类型声明 +import { IWindowList } from '#/types/enum' + +// Electron 相关 +import { ipcMain, IpcMainEvent } from 'electron' + +// 上传下载任务队列 +import UpDownTaskQueue, { uploadTaskSpecialStatus, commonTaskStatus, downloadTaskSpecialStatus } from '../datastore/upDownTaskQueue' + +// 文件系统库 +import fs from 'fs-extra' + +// 路径处理库 +import path from 'path' +import * as fsWalk from '@nodelib/fs.walk' + +// 取消下载任务的加载文件列表、刷新下载文件传输列表 +import { cancelDownloadLoadingFileList, refreshDownloadFileTransferList } from '@/manage/utils/static' + +class LocalApi { + logger: ManageLogger + isWindows: boolean + + constructor (logger: ManageLogger) { + this.logger = logger + this.isWindows = process.platform === 'win32' + } + + logParam = (error:any, method: string) => + this.logger.error(formatError(error, { class: 'LocalApi', method })) + + // windows 系统下将路径转换为 unix 风格 + transPathToUnix (filePath: string | undefined) { + if (!filePath) return '' + return this.isWindows ? filePath.split(path.sep).join(path.posix.sep) : filePath.replace(/^\/+/, '') + } + + transBack (filePath: string | undefined) { + if (!filePath) return '' + return this.isWindows + ? filePath.split(path.posix.sep).join(path.sep).replace(/^\\+/, '') + : `/${filePath.replace(/^\/+/, '')}` + } + + formatFolder (item: fs.Stats, urlPrefix: string, fileName: string, filePath: string) { + const key = this.transPathToUnix(filePath) + return { + ...item, + key, + fileName, + fileSize: 0, + Key: key, + formatedTime: '', + isDir: true, + checked: false, + isImage: false, + match: false, + url: urlPrefix + } + } + + formatFile (item: fs.Stats, urlPrefix: string, fileName: string, filePath: string) { + const key = this.transPathToUnix(filePath) + return { + ...item, + key, + fileName, + fileSize: item.size, + Key: key, + formatedTime: new Date(item.mtime).toLocaleString(), + isDir: false, + checked: false, + match: false, + isImage: isImage(fileName), + url: urlPrefix + } + } + + async getBucketListRecursively (configMap: IStringKeyMap): Promise { + const window = windowManager.get(IWindowList.SETTING_WINDOW)! + const { prefix, customUrl = '', cancelToken } = configMap + const urlPrefix = customUrl.replace(/\/+$/, '') + const cancelTask = [false] + ipcMain.on(cancelDownloadLoadingFileList, (_evt: IpcMainEvent, token: string) => { + if (token === cancelToken) { + cancelTask[0] = true + ipcMain.removeAllListeners(cancelDownloadLoadingFileList) + } + }) + let res = {} as any + const result = { + fullList: [], + success: false, + finished: false + } + try { + res = fsWalk.walkSync(prefix, { + followSymbolicLinks: true, + fs, + stats: true, + throwErrorOnBrokenSymbolicLink: false + }) + if (res.length) { + result.fullList.push( + ...res.data + .filter((item: fsWalk.Entry) => item.stats?.isFile()) + .map((item: any) => this.formatFile(item, urlPrefix, item.name, item.path)) + ) + result.success = true + } + } catch (error) { + this.logParam(error, 'getBucketListRecursively') + } + result.finished = true + window.webContents.send(refreshDownloadFileTransferList, result) + ipcMain.removeAllListeners(cancelDownloadLoadingFileList) + } + + async getBucketListBackstage (configMap: IStringKeyMap): Promise { + const window = windowManager.get(IWindowList.SETTING_WINDOW)! + const { customUrl = '', cancelToken, baseDir } = configMap + let prefix = configMap.prefix + prefix = this.transBack(prefix) + let urlPrefix = customUrl.replace(/\/+$/, '') + let webPath = configMap.webPath || '' + if (webPath && customUrl && webPath !== '/') { + webPath = webPath.replace(/^\/+|\/+$/, '') + } + + const cancelTask = [false] + ipcMain.on('cancelLoadingFileList', (_evt: IpcMainEvent, token: string) => { + if (token === cancelToken) { + cancelTask[0] = true + ipcMain.removeAllListeners('cancelLoadingFileList') + } + }) + const result = { + fullList: [], + success: false, + finished: false + } + try { + const res = await fs.readdir(prefix, { + withFileTypes: true + }) + if (res.length) { + res.forEach((item: fs.Dirent) => { + const pathOfFile = path.join(prefix, item.name) + const relativePath = path.relative(baseDir, pathOfFile) + const relative = webPath && urlPrefix + `/${path.join(webPath, relativePath)}`.replace(/\\/g, '/').replace(/\/+/g, '/') + if (webPath && customUrl) { + urlPrefix = relative + } else { + urlPrefix = pathOfFile + } + const stats = fs.statSync(pathOfFile) + if (item.isDirectory()) { + result.fullList.push(this.formatFolder(stats, urlPrefix, item.name, relativePath)) + } else { + result.fullList.push(this.formatFile(stats, urlPrefix, item.name, relativePath)) + } + }) + result.success = true + } + } catch (error) { + this.logParam(error, 'getBucketListBackstage') + } + result.finished = true + window.webContents.send('refreshFileTransferList', result) + ipcMain.removeAllListeners('cancelLoadingFileList') + } + + async renameBucketFile (configMap: IStringKeyMap): Promise { + const { oldKey, newKey } = configMap + let result = false + try { + await fs.rename(this.transBack(oldKey), this.transBack(newKey)) + result = true + } catch (error) { + this.logParam(error, 'renameBucketFile') + } + return result + } + + async deleteBucketFile (configMap: IStringKeyMap): Promise { + const { key } = configMap + let result = false + try { + await fs.remove(this.transBack(key)) + result = true + } catch (error) { + this.logParam(error, 'deleteBucketFile') + } + return result + } + + async deleteBucketFolder (configMap: IStringKeyMap): Promise { + const { key } = configMap + let result = false + try { + await fs.rmdir(this.transBack(key), { + recursive: true + }) + result = true + } catch (error) { + this.logParam(error, 'deleteBucketFolder') + } + return result + } + + async uploadBucketFile (configMap: IStringKeyMap): Promise { + const { fileArray } = configMap + const instance = UpDownTaskQueue.getInstance() + for (const item of fileArray) { + const { alias, bucketName, key, filePath, fileName } = item + const id = `${alias}-${bucketName}-${key}-${filePath}` + if (instance.getUploadTask(id)) { + continue + } + instance.addUploadTask({ + id, + progress: 0, + status: commonTaskStatus.queuing, + sourceFileName: fileName, + sourceFilePath: filePath, + targetFilePath: key, + targetFileBucket: bucketName, + targetFileRegion: '', + noProgress: true + }) + try { + fs.ensureFileSync(filePath) + await fs.copyFile(filePath, this.transBack(key)) + instance.updateUploadTask({ + id, + progress: 100, + status: uploadTaskSpecialStatus.uploaded, + finishTime: new Date().toLocaleString() + }) + } catch (error) { + this.logParam(error, 'uploadBucketFile') + instance.updateUploadTask({ + id, + progress: 0, + status: commonTaskStatus.failed, + finishTime: new Date().toLocaleString() + }) + } + } + return true + } + + async createBucketFolder (configMap: IStringKeyMap): Promise { + const { key } = configMap + let result = false + try { + await fs.mkdir(this.transBack(key), { + recursive: true + }) + result = true + } catch (error) { + this.logParam(error, 'createBucketFolder') + } + return result + } + + async downloadBucketFile (configMap: IStringKeyMap): Promise { + const { downloadPath, fileArray } = configMap + const instance = UpDownTaskQueue.getInstance() + for (const item of fileArray) { + const { alias, bucketName, key, fileName } = item + const savedFilePath = path.join(downloadPath, fileName) + const id = `${alias}-${bucketName}-local-${key}` + if (instance.getDownloadTask(id)) { + continue + } + instance.addDownloadTask({ + id, + progress: 0, + status: commonTaskStatus.queuing, + sourceFileName: fileName, + targetFilePath: savedFilePath + }) + try { + fs.ensureFileSync(savedFilePath) + await fs.copyFile(this.transBack(key), savedFilePath) + instance.updateDownloadTask({ + id, + progress: 100, + status: downloadTaskSpecialStatus.downloaded, + finishTime: new Date().toLocaleString() + }) + } catch (error) { + this.logParam(error, 'downloadBucketFile') + instance.updateDownloadTask({ + id, + progress: 0, + status: commonTaskStatus.failed, + finishTime: new Date().toLocaleString() + }) + } + } + return true + } +} + +export default LocalApi diff --git a/src/main/manage/apis/webdavplist.ts b/src/main/manage/apis/webdavplist.ts index 56aa356..528b708 100644 --- a/src/main/manage/apis/webdavplist.ts +++ b/src/main/manage/apis/webdavplist.ts @@ -137,20 +137,11 @@ class WebdavplistApi { } }) } - } else { - result.finished = true - window.webContents.send(refreshDownloadFileTransferList, result) - ipcMain.removeAllListeners(cancelDownloadLoadingFileList) - return + result.success = true } } catch (error) { this.logParam(error, 'getBucketListRecursively') - result.finished = true - window.webContents.send(refreshDownloadFileTransferList, result) - ipcMain.removeAllListeners(cancelDownloadLoadingFileList) - return } - result.success = true result.finished = true window.webContents.send(refreshDownloadFileTransferList, result) ipcMain.removeAllListeners(cancelDownloadLoadingFileList) diff --git a/src/main/manage/manageApi.ts b/src/main/manage/manageApi.ts index 3bf1a80..aec68ac 100644 --- a/src/main/manage/manageApi.ts +++ b/src/main/manage/manageApi.ts @@ -54,22 +54,24 @@ export class ManageApi extends EventEmitter implements ManageApiType { createClient () { const name = this.currentPicBedConfig.picBedName switch (name) { - case 'tcyun': - return new API.TcyunApi(this.currentPicBedConfig.secretId, this.currentPicBedConfig.secretKey, this.logger) case 'aliyun': return new API.AliyunApi(this.currentPicBedConfig.accessKeyId, this.currentPicBedConfig.accessKeySecret, this.logger) - case 'qiniu': - return new API.QiniuApi(this.currentPicBedConfig.accessKey, this.currentPicBedConfig.secretKey, this.logger) - case 'upyun': - return new API.UpyunApi(this.currentPicBedConfig.bucketName, this.currentPicBedConfig.operator, this.currentPicBedConfig.password, this.logger) - case 'smms': - return new API.SmmsApi(this.currentPicBedConfig.token, this.logger) case 'github': return new API.GithubApi(this.currentPicBedConfig.token, this.currentPicBedConfig.githubUsername, this.currentPicBedConfig.proxy, this.logger) case 'imgur': return new API.ImgurApi(this.currentPicBedConfig.imgurUserName, this.currentPicBedConfig.accessToken, this.currentPicBedConfig.proxy, this.logger) + case 'local': + return new API.LocalApi(this.logger) + case 'qiniu': + return new API.QiniuApi(this.currentPicBedConfig.accessKey, this.currentPicBedConfig.secretKey, this.logger) + case 'smms': + return new API.SmmsApi(this.currentPicBedConfig.token, this.logger) case 's3plist': return new API.S3plistApi(this.currentPicBedConfig.accessKeyId, this.currentPicBedConfig.secretAccessKey, this.currentPicBedConfig.endpoint, this.currentPicBedConfig.sslEnabled, this.currentPicBedConfig.s3ForcePathStyle, this.currentPicBedConfig.proxy, this.logger) + case 'tcyun': + return new API.TcyunApi(this.currentPicBedConfig.secretId, this.currentPicBedConfig.secretKey, this.logger) + case 'upyun': + return new API.UpyunApi(this.currentPicBedConfig.bucketName, this.currentPicBedConfig.operator, this.currentPicBedConfig.password, this.logger) case 'webdavplist': return new API.WebdavplistApi(this.currentPicBedConfig.endpoint, this.currentPicBedConfig.username, this.currentPicBedConfig.password, this.currentPicBedConfig.sslEnabled, this.currentPicBedConfig.proxy, this.logger) default: @@ -148,6 +150,7 @@ export class ManageApi extends EventEmitter implements ManageApiType { param?: IStringKeyMap | undefined ): Promise { let client + const name = this.currentPicBedConfig.picBedName.replace('plist', '') switch (this.currentPicBedConfig.picBedName) { case 'tcyun': case 'aliyun': @@ -169,15 +172,11 @@ export class ManageApi extends EventEmitter implements ManageApiType { CreationDate: new Date().toISOString() }] case 'smms': - return [{ - Name: 'smms', - Location: 'smms', - CreationDate: new Date().toISOString() - }] case 'webdavplist': + case 'local': return [{ - Name: 'webdav', - Location: 'webdav', + Name: name, + Location: name, CreationDate: new Date().toISOString() }] default: @@ -313,6 +312,7 @@ export class ManageApi extends EventEmitter implements ManageApiType { case 'imgur': case 's3plist': case 'webdavplist': + case 'local': try { client = this.createClient() as any return await client.getBucketListRecursively(param!) @@ -356,6 +356,7 @@ export class ManageApi extends EventEmitter implements ManageApiType { case 'imgur': case 's3plist': case 'webdavplist': + case 'local': try { client = this.createClient() as any return await client.getBucketListBackstage(param!) @@ -426,6 +427,7 @@ export class ManageApi extends EventEmitter implements ManageApiType { case 'imgur': case 's3plist': case 'webdavplist': + case 'local': try { client = this.createClient() as any const res = await client.deleteBucketFile(param!) @@ -451,6 +453,7 @@ export class ManageApi extends EventEmitter implements ManageApiType { case 'github': case 's3plist': case 'webdavplist': + case 'local': try { client = this.createClient() as any return await client.deleteBucketFolder(param!) @@ -474,6 +477,7 @@ export class ManageApi extends EventEmitter implements ManageApiType { case 'upyun': case 's3plist': case 'webdavplist': + case 'local': try { client = this.createClient() as any return await client.renameBucketFile(param!) @@ -500,6 +504,7 @@ export class ManageApi extends EventEmitter implements ManageApiType { case 'imgur': case 's3plist': case 'webdavplist': + case 'local': try { client = this.createClient() as any const res = await client.downloadBucketFile(param!) @@ -532,6 +537,7 @@ export class ManageApi extends EventEmitter implements ManageApiType { case 'github': case 's3plist': case 'webdavplist': + case 'local': try { client = this.createClient() as any return await client.createBucketFolder(param!) @@ -558,6 +564,7 @@ export class ManageApi extends EventEmitter implements ManageApiType { case 'imgur': case 's3plist': case 'webdavplist': + case 'local': try { client = this.createClient() as any return await client.uploadBucketFile(param!) diff --git a/src/main/utils/beforeOpen.ts b/src/main/utils/beforeOpen.ts index e124c4a..6570706 100644 --- a/src/main/utils/beforeOpen.ts +++ b/src/main/utils/beforeOpen.ts @@ -21,14 +21,11 @@ function beforeOpen () { */ function resolveMacWorkFlow () { const dest = `${os.homedir()}/Library/Services/Upload pictures with PicList.workflow` - if (fs.existsSync(dest)) { - return true - } else { - try { - fs.copySync(path.join(__static, 'Upload pictures with PicList.workflow'), dest) - } catch (e) { - console.log(e) - } + if (fs.existsSync(dest)) return true + try { + fs.copySync(path.join(__static, 'Upload pictures with PicList.workflow'), dest) + } catch (e) { + console.log(e) } } diff --git a/src/renderer/apis/aliyun.ts b/src/renderer/apis/aliyun.ts index e713634..3a210ff 100644 --- a/src/renderer/apis/aliyun.ts +++ b/src/renderer/apis/aliyun.ts @@ -33,7 +33,7 @@ export default class AliyunApi { try { const client = AliyunApi.createClient(config) const key = AliyunApi.getKey(fileName, config.path) - const result = await client.delete(key) as any + const result = await client.delete(key) return result.res.status === 204 } catch (error) { console.error(error) diff --git a/src/renderer/manage/pages/bucketPage.vue b/src/renderer/manage/pages/bucketPage.vue index 4bf9231..2c19a2b 100644 --- a/src/renderer/manage/pages/bucketPage.vue +++ b/src/renderer/manage/pages/bucketPage.vue @@ -1570,6 +1570,7 @@ const urlToUpload = ref('') // 图片预览相关 const previewedImage = ref('') const ImagePreviewList = computed(() => currentPageFilesInfo.filter(item => item.isImage).map(item => item.url)) +const getCurrentPreviewIndex = computed(() => ImagePreviewList.value.indexOf(previewedImage.value)) // 快捷键相关 const isShiftKeyPress = ref(false) const lastChoosed = ref(-1) @@ -1577,7 +1578,7 @@ const lastChoosed = ref(-1) const customDomainList = ref([] as any[]) const currentCustomDomain = ref('') const isShowCustomDomainSelectList = computed(() => ['tcyun', 'aliyun', 'qiniu', 'github'].includes(currentPicBedName.value)) -const isShowCustomDomainInput = computed(() => ['aliyun', 'qiniu', 'tcyun', 's3plist', 'webdavplist'].includes(currentPicBedName.value)) +const isShowCustomDomainInput = computed(() => ['aliyun', 'qiniu', 'tcyun', 's3plist', 'webdavplist', 'local'].includes(currentPicBedName.value)) const isAutoCustomDomain = computed(() => manageStore.config.picBed[configMap.alias].isAutoCustomUrl === undefined ? true : manageStore.config.picBed[configMap.alias].isAutoCustomUrl) // 文件预览相关 const isShowMarkDownDialog = ref(false) @@ -1588,17 +1589,27 @@ const isShowVideoFileDialog = ref(false) const videoFileUrl = ref('') const videoPlayerHeaders = ref({}) // 重命名相关 -const isShowRenameFileIcon = computed(() => ['tcyun', 'aliyun', 'qiniu', 'upyun', 's3plist', 'webdavplist'].includes(currentPicBedName.value)) +const isShowRenameFileIcon = computed(() => ['tcyun', 'aliyun', 'qiniu', 'upyun', 's3plist', 'webdavplist', 'local'].includes(currentPicBedName.value)) const isShowBatchRenameDialog = ref(false) const batchRenameMatch = ref('') const batchRenameReplace = ref('') const isRenameIncludeExt = ref(false) const isSingleRename = ref(false) const itemToBeRenamed = ref({} as any) -// 新建文件夹相关 -const isShowCreateNewFolder = computed(() => ['tcyun', 'aliyun', 'qiniu', 'upyun', 'github', 's3plist', 'webdavplist'].includes(currentPicBedName.value)) -const isShowPresignedUrl = computed(() => ['tcyun', 'aliyun', 'qiniu', 'github', 's3plist', 'webdavplist'].includes(currentPicBedName.value)) +// 当前页面信息相关 +const currentPicBedName = computed(() => manageStore.config.picBed[configMap.alias].picBedName) +const paging = computed(() => manageStore.config.picBed[configMap.alias].paging) +const itemsPerPage = computed(() => manageStore.config.picBed[configMap.alias].itemsPerPage) +const calculateAllFileSize = computed(() => formatFileSize(currentPageFilesInfo.reduce((total: any, item: { fileSize: any }) => total + item.fileSize, 0)) || '0') +const isShowThumbnail = computed(() => manageStore.config.settings.isShowThumbnail ?? false) +const isAutoRefresh = computed(() => manageStore.config.settings.isAutoRefresh ?? false) +const isIgnoreCase = computed(() => manageStore.config.settings.isIgnoreCase ?? false) + +// 新建文件夹相关 +const isShowCreateNewFolder = computed(() => ['aliyun', 'github', 'local', 'qiniu', 'tcyun', 's3plist', 'upyun', 'webdavplist'].includes(currentPicBedName.value)) + +const isShowPresignedUrl = computed(() => ['aliyun', 'github', 'qiniu', 's3plist', 'tcyun', 'webdavplist'].includes(currentPicBedName.value)) // 上传相关函数 @@ -1655,7 +1666,7 @@ function getBase64ofWebdav () { return headers } -const getCurrentPreviewIndex = computed(() => ImagePreviewList.value.indexOf(previewedImage.value)) +// 上传文件选择相关 function openFileSelectDialog () { ipcRenderer.invoke('openFileSelectDialog').then((res: any) => { @@ -1866,11 +1877,6 @@ function handleCopyUploadingTaskInfo () { ElMessage.success($T('MANAGE_BUCKET_COPY_SUCCESS')) } -function handleCopyDownloadingTaskInfo () { - clipboard.writeText(JSON.stringify(downloadTaskList.value, null, 2)) - ElMessage.success($T('MANAGE_BUCKET_COPY_SUCCESS')) -} - function handleDeleteUploadedTask () { ipcRenderer.send('deleteUploadedTask') ElMessage.success($T('MANAGE_BUCKET_DELETE_SUCCESS')) @@ -1881,6 +1887,13 @@ function handleDeleteAllUploadedTask () { ElMessage.success($T('MANAGE_BUCKET_DELETE_SUCCESS')) } +// 下载任务相关 + +function handleCopyDownloadingTaskInfo () { + clipboard.writeText(JSON.stringify(downloadTaskList.value, null, 2)) + ElMessage.success($T('MANAGE_BUCKET_COPY_SUCCESS')) +} + function handleDeleteDownloadedTask () { ipcRenderer.send('deleteDownloadedTask') ElMessage.success($T('MANAGE_BUCKET_DELETE_SUCCESS')) @@ -1891,7 +1904,11 @@ function handleDeleteAllDownloadedTask () { ElMessage.success($T('MANAGE_BUCKET_DELETE_SUCCESS')) } -const handleOpenDownloadedFolder = () => ipcRenderer.send('OpenDownloadedFolder', manageStore.config.settings.downloadDir) +function handleOpenDownloadedFolder () { + ipcRenderer.send('OpenDownloadedFolder', manageStore.config.settings.downloadDir) +} + +// 文件列表相关 function handleShowFileInfo (item: any) { isShowFileInfo.value = true @@ -1967,17 +1984,7 @@ async function handleClickFile (item: any) { } } -const currentPicBedName = computed(() => manageStore.config.picBed[configMap.alias].picBedName) - -const paging = computed(() => manageStore.config.picBed[configMap.alias].paging) - -const itemsPerPage = computed(() => manageStore.config.picBed[configMap.alias].itemsPerPage) - -const calculateAllFileSize = computed(() => formatFileSize(currentPageFilesInfo.reduce((total: any, item: { fileSize: any }) => total + item.fileSize, 0)) || '0') - -const isShowThumbnail = computed(() => manageStore.config.settings.isShowThumbnail ?? false) -const isAutoRefresh = computed(() => manageStore.config.settings.isAutoRefresh ?? false) -const isIgnoreCase = computed(() => manageStore.config.settings.isIgnoreCase ?? false) +// 自定义域名相关 async function handleChangeCustomUrl () { if (currentPicBedName.value === 'github') { @@ -2101,6 +2108,8 @@ async function initCustomDomainList () { } } +// 重置 + async function resetParam (force: boolean = false) { if (isLoadingData.value) { isLoadingData.value = false @@ -2596,7 +2605,7 @@ function handleCreateFolder () { ElMessageBox.prompt($T('MANAGE_BUCKET_CREATE_FOLDER_BOX_TITLE'), $T('MANAGE_BUCKET_CREATE_FOLDER_BOX_TIP'), { confirmButtonText: $T('MANAGE_BUCKET_CREATE_FOLDER_BOX_CONFIRM'), cancelButtonText: $T('MANAGE_BUCKET_CREATE_FOLDER_BOX_CANCEL'), - inputPattern: /^[\u4e00-\u9fa5_a-zA-Z0-9/]+$/, + inputPattern: /^[\u4e00-\u9fff_a-zA-Z0-9/]+$/, inputErrorMessage: $T('MANAGE_BUCKET_CREATE_FOLDER_ERROR_MSG') }).then(async ({ value }) => { let formatedPath = value @@ -2617,7 +2626,7 @@ function handleCreateFolder () { }).catch(() => {}) } -const showUrlDialog = () => { +function showUrlDialog () { dialogVisible.value = true } @@ -2857,10 +2866,12 @@ async function getBucketFileListBackStage () { isLoadingData.value = true const fileTransferStore = useFileTransferStore() fileTransferStore.resetFileTransferList() - if (currentPicBedName.value === 'webdavplist') { + if (currentPicBedName.value === 'webdavplist' || + currentPicBedName.value === 'local') { param.baseDir = configMap.baseDir param.webPath = configMap.webPath } + console.log(param) ipcRenderer.send('getBucketListBackstage', configMap.alias, param) ipcRenderer.on('refreshFileTransferList', (evt: IpcRendererEvent, data) => { fileTransferStore.refreshFileTransferList(data) @@ -3433,7 +3444,9 @@ const upLoadTaskColumns: Column[] = [ } ] -const rowClass = ({ rowData }: Parameters>[0]) => rowData.checked ? 'file-list-row-checked' : '' +function rowClass ({ rowData }: Parameters>[0]) { + return rowData.checked ? 'file-list-row-checked' : '' +} const columns: Column[] = [ { diff --git a/src/renderer/manage/pages/logIn.vue b/src/renderer/manage/pages/logIn.vue index 82ca32c..c5e1efa 100644 --- a/src/renderer/manage/pages/logIn.vue +++ b/src/renderer/manage/pages/logIn.vue @@ -555,7 +555,7 @@ function handleConfigImport (alias: string) { const selectedConfig = existingConfiguration[alias] if (!selectedConfig) return - supportedPicBedList[selectedConfig.picBedName].forEach((option: any) => { + supportedPicBedList[selectedConfig.picBedName].options.forEach((option: any) => { if (selectedConfig[option] !== undefined) { configResult[selectedConfig.picBedName + '.' + option] = selectedConfig[option] } diff --git a/src/renderer/manage/pages/manageMain.vue b/src/renderer/manage/pages/manageMain.vue index 2926464..aa3be58 100644 --- a/src/renderer/manage/pages/manageMain.vue +++ b/src/renderer/manage/pages/manageMain.vue @@ -302,6 +302,7 @@ import { useManageStore } from '../store/manageStore' // 国际化函数 import { T as $T } from '@/i18n' +import path from 'path' const manageStore = useManageStore() as any const route = useRoute() @@ -443,13 +444,22 @@ async function getBucketList () { } } +function transPathToUnix (filePath: string | undefined) { + if (!filePath) return '' + return process.platform === 'win32' ? filePath.split(path.sep).join(path.posix.sep).replace(/^\/+|\/+$/g, '') : filePath.replace(/^\/+|\/+$/g, '') +} + function handleSelectMenu (bucketName: string) { const currentPicBedConfig = manageStore.config.picBed[currentAlias.value] const transformedConfig = JSON.parse(currentPicBedConfig.transformedConfig ?? '{}') let prefix = transformedConfig[bucketName]?.baseDir || '/' - prefix = prefix.startsWith('/') ? prefix : `/${prefix}` - prefix = prefix.endsWith('/') ? prefix : `${prefix}/` + if (currentPicBedConfig.picBedName ?? currentPicBedName.value === 'local') { + prefix = `/${transPathToUnix(prefix)}/` + } else { + prefix = prefix.startsWith('/') ? prefix : `/${prefix}` + prefix = prefix.endsWith('/') ? prefix : `${prefix}/` + } const configMap = { prefix, @@ -463,7 +473,7 @@ function handleSelectMenu (bucketName: string) { webPath: currentPicBedConfig.webPath || '' } currentSelectedBucket.value = bucketName - + console.log(configMap) router.push({ path: '/main-page/manage-main-page/manage-bucket-page', query: { @@ -477,6 +487,7 @@ function switchPicBed (picBedAlias:string) { router.push({ path: '/main-page/manage-login-page' }) + return } if (route.fullPath.startsWith('/main-page/manage-main-page/manage-bucket-page') || route.fullPath.startsWith('/main-page/manage-main-page/manage-setting-page') ) { diff --git a/src/renderer/manage/utils/constants.ts b/src/renderer/manage/utils/constants.ts index a25215f..fa06599 100644 --- a/src/renderer/manage/utils/constants.ts +++ b/src/renderer/manage/utils/constants.ts @@ -41,7 +41,7 @@ const aliasRule = [ }, { validator: (rule: any, value: any, callback: any) => { - const reg = /^[\u4e00-\u9fa5_a-zA-Z0-9-]+$/ + const reg = /^[\u4e00-\u9fff_a-zA-Z0-9-]+$/ if (!reg.test(value)) { callback(new Error($T('MANAGE_CONSTANT_ALIAS_RULE_MESSAGE_B'))) } else { @@ -838,7 +838,7 @@ export const supportedPicBedList: IStringKeyMap = { } }, explain: $T('MANAGE_CONSTANT_LOCAL_EXPLAIN'), - options: ['alias', 'baseDir', 'customUrl', 'webPath'], + options: ['alias', 'baseDir', 'customUrl', 'bucketName', 'webPath'], refLink: 'https://piclist.cn', referenceText: $T('MANAGE_CONSTANT_LOCAL_REFER_TEXT') } diff --git a/src/renderer/utils/LS.ts b/src/renderer/utils/LS.ts index 709771e..765f261 100644 --- a/src/renderer/utils/LS.ts +++ b/src/renderer/utils/LS.ts @@ -1,11 +1,18 @@ class LS { get (name: string) { - const item = localStorage.getItem(name) as string - return item ? JSON.parse(item) : {} + const item = localStorage.getItem(name) + if (item) { + try { + return JSON.parse(item) + } catch (e) { + console.error('Failed to parse JSON:', e) + } + } + return {} } - set (name: string, value: any) { - return localStorage.setItem(name, JSON.stringify(value)) + set (name: string, value: any): void { + localStorage.setItem(name, JSON.stringify(value)) } } diff --git a/yarn.lock b/yarn.lock index d989c0e..34975ee 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2159,11 +2159,24 @@ "@nodelib/fs.stat" "2.0.5" run-parallel "^1.1.9" +"@nodelib/fs.scandir@3.0.0": + version "3.0.0" + resolved "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-3.0.0.tgz#91c0a33e1aeaedcd4bab2bf31be5d1962a55d2a7" + integrity sha512-ktI9+PxfHYtKjF3cLTUAh2N+b8MijCRPNwKJNqTVdL0gB0QxLU2rIRaZ1t71oEa3YBDE6bukH1sR0+CDnpp/Mg== + dependencies: + "@nodelib/fs.stat" "3.0.0" + run-parallel "^1.2.0" + "@nodelib/fs.stat@2.0.5", "@nodelib/fs.stat@^2.0.2": version "2.0.5" resolved "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz#5bd262af94e9d25bd1e71b05deed44876a222e8b" integrity sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A== +"@nodelib/fs.stat@3.0.0": + version "3.0.0" + resolved "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-3.0.0.tgz#ef6c829f2b05f42595d88854ebd777d4335ff0a9" + integrity sha512-2tQOI38s19P9i7X/Drt0v8iMA+KMsgdhB/dyPER+e+2Y8L1Z7QvnuRdW/uLuf5YRFUYmnj4bMA6qCuZHFI1GDQ== + "@nodelib/fs.walk@^1.2.3", "@nodelib/fs.walk@^1.2.8": version "1.2.8" resolved "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz#e95737e8bb6746ddedf69c556953494f196fe69a" @@ -2172,6 +2185,14 @@ "@nodelib/fs.scandir" "2.1.5" fastq "^1.6.0" +"@nodelib/fs.walk@^2.0.0": + version "2.0.0" + resolved "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-2.0.0.tgz#10499ac2210f6399770b465ba728adafc7d44bb1" + integrity sha512-54voNDBobGdMl3BUXSu7UaDh1P85PGHWlJ5e0XhPugo1JulOyCtp2I+5ri4wplGDJ8QGwPEQW7/x3yTLU7yF1A== + dependencies: + "@nodelib/fs.scandir" "3.0.0" + fastq "^1.15.0" + "@octokit/auth-token@^3.0.0": version "3.0.3" resolved "https://registry.npmmirror.com/@octokit/auth-token/-/auth-token-3.0.3.tgz#ce7e48a3166731f26068d7a7a7996b5da58cbe0c" @@ -7418,6 +7439,13 @@ fast-xml-parser@^4.2.4: dependencies: strnum "^1.0.5" +fastq@^1.15.0: + version "1.15.0" + resolved "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz#d04d07c6a2a68fe4599fea8d2e103a937fae6b3a" + integrity sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw== + dependencies: + reusify "^1.0.4" + fastq@^1.6.0: version "1.13.0" resolved "https://registry.npmjs.org/fastq/-/fastq-1.13.0.tgz#616760f88a7526bdfc596b7cab8c18938c36b98c" @@ -12102,7 +12130,7 @@ run-node@^1.0.0: resolved "https://registry.npmjs.org/run-node/-/run-node-1.0.0.tgz#46b50b946a2aa2d4947ae1d886e9856fd9cabe5e" integrity sha512-kc120TBlQ3mih1LSzdAJXo4xn/GWS2ec0l3S+syHDXP9uRr0JAT8Qd3mdMuyjqCzeZktgP3try92cEgf9Nks8A== -run-parallel@^1.1.9: +run-parallel@^1.1.9, run-parallel@^1.2.0: version "1.2.0" resolved "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz#66d1368da7bdf921eb9d95bd1a9229e7f21a43ee" integrity sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==