This commit is contained in:
2025-10-29 22:20:21 +08:00
commit 32b3b7b29a
111 changed files with 344425 additions and 0 deletions

View File

@@ -0,0 +1,49 @@
# coding: utf-8
from PyQt6.QtCore import Qt
from PyQt6.QtWidgets import QVBoxLayout
from qfluentwidgets import CardWidget, ImageLabel, SubtitleLabel
class EmptyCard(CardWidget):
def __init__(self, parent=None, text=None):
super().__init__(parent=parent)
self.setMinimumWidth(200)
self.setBorderRadius(10)
self.vBoxLayout = QVBoxLayout(self)
self.vBoxLayout.setAlignment(Qt.AlignmentFlag.AlignCenter)
self.iconLabel = ImageLabel(self)
self.iconLabel.setImage(":app/images/empty.png")
self.iconLabel.scaledToHeight(130)
self.iconLabel.scaledToWidth(130)
self.titleLabel = SubtitleLabel(self)
self.titleLabel.setText(text)
self.titleLabel.setAlignment(Qt.AlignmentFlag.AlignCenter)
self.vBoxLayout.addWidget(self.iconLabel, 0, Qt.AlignmentFlag.AlignHCenter)
self.vBoxLayout.addWidget(self.titleLabel)
def setText(self, text):
self.titleLabel.setText(text)
self.update()
def load(self):
self.iconLabel.setImage(":app/images/load.png")
self.iconLabel.scaledToHeight(130)
self.iconLabel.scaledToWidth(130)
self.titleLabel.setText("加载中...")
def error(self):
self.iconLabel.setImage(":app/images/error.png")
self.iconLabel.scaledToHeight(130)
self.iconLabel.scaledToWidth(130)
self.titleLabel.setText("加载失败,请重试")
def empty(self):
self.iconLabel.setImage(":app/images/empty.png")
self.iconLabel.scaledToHeight(130)
self.iconLabel.scaledToWidth(130)
self.titleLabel.setText("这里空空如也")

View File

@@ -0,0 +1,362 @@
# coding: utf-8
from loguru import logger
from PyQt6.QtCore import Qt, pyqtSignal
from PyQt6.QtGui import QPixmap
from PyQt6.QtWidgets import QHBoxLayout
from qfluentwidgets import Action, BodyLabel, CardWidget, ImageLabel, InfoBar, InfoBarPosition, MenuAnimationType, \
MessageBox, PushButton, RoundMenu, StrongBodyLabel
from qfluentwidgets import FluentIcon as FIF
from app.core import (DeleteFileThread, formatDate, formatSize, getFileIcon, lang, signalBus)
from app.view.widgets.share_file_messageBox import ShareFileMessageBox
class FileCard(CardWidget):
def __init__(self, _id, fileName, fileType, path, date, size, parent=None):
super().__init__(parent)
self._id = _id
self.fileName = fileName
self.fileSize = size
self.changeTime = date
self.fileType = fileType
self.filePath = path
self.setFixedHeight(50)
self.iconLabel = ImageLabel(self)
self.fileNameLabel = StrongBodyLabel(self.fileName, self)
self.fileSizeLabel = BodyLabel(formatSize(self.fileSize), self)
self.changeTimeLabel = BodyLabel(formatDate(self.changeTime), self)
self.hBoxLayout = QHBoxLayout(self)
self.hBoxLayout.addWidget(self.iconLabel)
self.hBoxLayout.addWidget(self.fileNameLabel)
self.hBoxLayout.addWidget(self.fileSizeLabel)
self.hBoxLayout.addWidget(self.changeTimeLabel)
self.hBoxLayout.setStretch(0, 1)
self.hBoxLayout.setStretch(1, 2)
self.hBoxLayout.setStretch(2, 1)
self.hBoxLayout.setContentsMargins(10, 10, 10, 10)
self.hBoxLayout.setSpacing(10)
self.loadIcon()
if self.fileType == "dir":
self.clicked.connect(self.dirClicked)
self.suffix = fileName.split(".")[-1].lower()
def mousePressEvent(self, event):
"""重写鼠标点击事件,区分左右键"""
if event.button() == Qt.MouseButton.RightButton:
# 使用globalPosition()获取全局位置并转换为适合菜单显示的坐标
global_pos = event.globalPosition().toPoint()
if self.fileType == "file":
self.showFileContextMenu(global_pos)
else:
self.showFolderContextMenu(global_pos)
else:
super().mousePressEvent(event)
def loadIcon(self):
icon_name = getFileIcon(self.fileType, self.fileName)
self.iconLabel.setImage(QPixmap(f":app/icons/{icon_name}"))
self.iconLabel.scaledToHeight(25)
self.iconLabel.scaledToWidth(25)
def dirClicked(self):
if self.filePath == "/":
paths = "/" + self.fileName
else:
paths = f"{self.filePath}/{self.fileName}"
signalBus.dirOpenSignal.emit(paths)
def selfPreview(self):
if self.fileType == "file" and self.suffix in [
"jpg",
"png",
"jpeg",
"bmp",
"gif",
]:
signalBus.imagePreviewSignal.emit(self._id)
if self.fileType == "file" and self.suffix in ["txt", "py", "md"]:
signalBus.txtPreviewSignal.emit(self._id)
def downloadFile(self):
if self.fileType == "file":
# 构建Cloudreve V4 API所需的正确路径格式
# 确保不会出现重复的前缀和文件名
if self.filePath == "/":
# 根目录情况
full_path = f"cloudreve://my/{self.fileName}"
else:
# 子目录情况,确保正确拼接路径
# 清理路径,避免重复的斜杠
clean_path = self.filePath.lstrip("/")
full_path = f"cloudreve://my/{clean_path}/{self.fileName}"
# 确保路径格式正确,没有重复的部分
full_path = full_path.replace("cloudreve://my/cloudreve://my", "cloudreve://my")
# 确保没有重复的文件名
if f"/{self.fileName}/{self.fileName}" in full_path:
full_path = full_path.replace(f"/{self.fileName}/{self.fileName}", f"/{self.fileName}")
signalBus.addDownloadFileTask.emit(
f"own.{self.suffix}", self.fileName, full_path
)
def showFileContextMenu(self, pos):
"""显示上下文菜单"""
menu = RoundMenu(parent=self)
menu.addActions(
[
Action(
FIF.DOWNLOAD, lang("下载"), triggered=lambda: self.downloadFile()
),
Action(
FIF.PROJECTOR, lang("预览"), triggered=lambda: self.selfPreview()
),
]
)
menu.addSeparator()
menu.addActions(
[
Action(FIF.DELETE, lang("删除"), triggered=lambda: self.deleteSelf()),
]
)
menu.exec(pos, aniType=MenuAnimationType.DROP_DOWN)
def showFolderContextMenu(self, pos):
"""显示上下文菜单"""
menu = RoundMenu(parent=self)
menu.addActions(
[
Action(FIF.DOWNLOAD, lang("进入"), triggered=lambda: self.dirClicked()),
]
)
menu.addSeparator()
menu.addActions(
[
Action(FIF.DELETE, lang("删除"), triggered=lambda: self.deleteSelf()),
]
)
menu.exec(pos, aniType=MenuAnimationType.DROP_DOWN)
def deleteSelf(self):
w = MessageBox(
"确认删除",
f"你确定要删除{self.fileName}吗?\n删除后不可恢复噢!",
parent=self.window(),
)
if w.exec():
self.deleteThread = DeleteFileThread(self._id, self.fileType)
self.deleteThread.successDelete.connect(self.deleteSuccess)
self.deleteThread.errorDelete.connect(self.deleteError)
self.deleteThread.start()
else:
InfoBar.info(
"提示",
"删除已取消",
Qt.Orientation.Horizontal,
True,
1000,
InfoBarPosition.TOP_RIGHT,
self.window(),
)
def deleteSuccess(self):
InfoBar.success(
"成功",
"成功删除",
Qt.Orientation.Horizontal,
True,
1000,
InfoBarPosition.TOP_RIGHT,
self.window(),
)
signalBus.refreshFolderListSignal.emit()
def deleteError(self, error_msg):
InfoBar.error(
"失败",
"删除失败",
Qt.Orientation.Horizontal,
True,
1000,
InfoBarPosition.TOP_RIGHT,
self.window(),
)
logger.error(f"删除文件失败:{error_msg}")
def contextMenuEvent(self, e):
"""重写上下文菜单事件,确保只有右键点击才会触发"""
pass
class ShareFileCard(CardWidget):
def __init__(self, data, parent=None):
super().__init__(parent)
self._id = data["key"]
self.fileName = data["source"]["name"]
self.fileSize = data["source"]["size"]
self.changeTime = data["create_date"]
self.fileType = "dir" if data["is_dir"] else "file"
self.preview = data["preview"]
self.passWord = data["password"]
self.remainDownloads = data["remain_downloads"]
self.downloads = data["downloads"]
self.score = data["score"]
self.views = data["views"]
self.expireTime = data["expire"]
self.setFixedHeight(50)
self.iconLabel = ImageLabel(self)
self.fileNameLabel = StrongBodyLabel(self.fileName, self)
self.changeTimeLabel = BodyLabel(formatDate(self.changeTime), self)
self.viewButton = PushButton("查看", self)
self.viewButton.clicked.connect(self.viewFile)
self.hBoxLayout = QHBoxLayout(self)
self.hBoxLayout.addWidget(self.iconLabel)
self.hBoxLayout.addWidget(self.fileNameLabel)
self.hBoxLayout.addWidget(self.changeTimeLabel)
self.hBoxLayout.addWidget(self.viewButton)
self.hBoxLayout.setStretch(0, 1)
self.hBoxLayout.setStretch(1, 2)
self.hBoxLayout.setStretch(2, 1)
self.hBoxLayout.setContentsMargins(10, 10, 10, 10)
self.hBoxLayout.setSpacing(10)
self.loadIcon()
self.suffix = self.fileName.split(".")[-1].lower()
def loadIcon(self):
icon_name = getFileIcon(self.fileType, self.fileName)
self.iconLabel.setImage(QPixmap(f":app/icons/{icon_name}"))
self.iconLabel.scaledToHeight(25)
self.iconLabel.scaledToWidth(25)
def viewFile(self):
if self.fileType == "file":
w = ShareFileMessageBox(
self._id, self.iconLabel.pixmap(), self.suffix, self.window()
)
if w.exec():
...
else:
signalBus.shareFolderViewSignal.emit(self._id)
class SharedFolderFileCard(CardWidget):
shareFileDownloadSignal = pyqtSignal() # 共享文件下载信号
def __init__(self, key, _id, fileName, fileType, path, date, size, parent=None):
super().__init__(parent)
self._id = _id
self.key = key
self.fileName = fileName
self.fileSize = size
self.changeTime = date
self.fileType = fileType
self.filePath = path
self.setFixedHeight(50)
self.iconLabel = ImageLabel(self)
self.fileNameLabel = StrongBodyLabel(self.fileName, self)
self.fileSizeLabel = BodyLabel(formatSize(self.fileSize), self)
self.changeTimeLabel = BodyLabel(formatDate(self.changeTime), self)
self.hBoxLayout = QHBoxLayout(self)
self.hBoxLayout.addWidget(self.iconLabel)
self.hBoxLayout.addWidget(self.fileNameLabel)
self.hBoxLayout.addWidget(self.fileSizeLabel)
self.hBoxLayout.addWidget(self.changeTimeLabel)
self.hBoxLayout.setStretch(0, 1)
self.hBoxLayout.setStretch(1, 2)
self.hBoxLayout.setStretch(2, 1)
self.hBoxLayout.setContentsMargins(10, 10, 10, 10)
self.hBoxLayout.setSpacing(10)
self.loadIcon()
self.suffix = self.fileName.split(".")[-1].lower()
if self.fileType == "dir":
# 连接左键点击信号
self.clicked.connect(self.dirClicked)
def mousePressEvent(self, event):
"""重写鼠标点击事件,区分左右键"""
if event.button() == Qt.MouseButton.RightButton:
# 右键点击,显示上下文菜单
if self.fileType == "file":
self.showFileContextMenu(event.globalPos())
else:
self.showFolderContextMenu(event.globalPos())
else:
# 左键或其他按钮点击,调用父类处理
super().mousePressEvent(event)
def loadIcon(self):
icon_name = getFileIcon(self.fileType, self.fileName)
self.iconLabel.setImage(QPixmap(f":app/icons/{icon_name}"))
self.iconLabel.scaledToHeight(25)
self.iconLabel.scaledToWidth(25)
def dirClicked(self):
if self.filePath == "/":
paths = "/" + self.fileName
else:
paths = f"{self.filePath}/{self.fileName}"
signalBus.shareDirOpenSignal.emit(paths)
def downloadFile(self):
if self.fileType == "file":
signalBus.addDownloadFileTask.emit(
f"share.{self.suffix}",
self.fileName,
f"{self.filePath}/{self.fileName}.{self.key}",
)
signalBus.shareFileDownloadSignal.emit()
def showFileContextMenu(self, pos):
"""显示上下文菜单"""
menu = RoundMenu(parent=self)
menu.addActions(
[
Action(
FIF.DOWNLOAD, lang("下载"), triggered=lambda: self.downloadFile()
),
]
)
menu.exec(pos, aniType=MenuAnimationType.DROP_DOWN)
def showFolderContextMenu(self, pos):
"""显示上下文菜单"""
menu = RoundMenu(parent=self)
menu.addActions(
[
Action(FIF.DOWNLOAD, lang("进入"), triggered=lambda: self.dirClicked()),
]
)
menu.exec(pos, aniType=MenuAnimationType.DROP_DOWN)

View File

@@ -0,0 +1,263 @@
# coding: utf-8
import os
from PyQt6.QtCore import Qt, QTimer
from PyQt6.QtGui import QPixmap
from PyQt6.QtWidgets import QHBoxLayout, QVBoxLayout
from qfluentwidgets import (BodyLabel, CardWidget, FluentIcon, ImageLabel, InfoBar, InfoBarPosition, PrimaryToolButton,
ProgressBar, SubtitleLabel)
from app.core import (DownloadShareThread, DownloadThread, formatSize, getFileIcon, signalBus, UploadThread)
class UploadCard(CardWidget):
def __init__(self, fileType, filePath, parent=None):
super().__init__(parent=parent)
self.fileType = fileType
self.filePath = filePath
self.fileName = os.path.basename(filePath)
self.setFixedHeight(75)
self.hBoxLayout = QHBoxLayout(self)
self.infomationLayout = QVBoxLayout()
self.iconLabel = ImageLabel(self)
self.fileNameLabel = SubtitleLabel(self.fileName, self)
self.currentStatusLabel = BodyLabel("等待中...", self)
self.progressBar = ProgressBar(self)
self.progressBar.setValue(0)
self.cancelButton = PrimaryToolButton(FluentIcon.DELETE, self)
self.retryButton = PrimaryToolButton(FluentIcon.RETURN, self)
self.retryButton.clicked.connect(self.retryUpload)
self.cancelButton.clicked.connect(self.cancelUpload)
self.hBoxLayout.addWidget(
self.iconLabel, 0, Qt.AlignmentFlag.AlignCenter | Qt.AlignmentFlag.AlignLeft
)
self.hBoxLayout.addSpacing(5)
self.infomationLayout.addWidget(
self.fileNameLabel,
0,
Qt.AlignmentFlag.AlignTop | Qt.AlignmentFlag.AlignLeft,
)
self.infomationLayout.addWidget(
self.currentStatusLabel,
0,
Qt.AlignmentFlag.AlignTop | Qt.AlignmentFlag.AlignLeft,
)
self.infomationLayout.addWidget(self.progressBar)
self.hBoxLayout.addLayout(self.infomationLayout)
self.hBoxLayout.addSpacing(10)
self.hBoxLayout.addWidget(
self.retryButton,
0,
Qt.AlignmentFlag.AlignRight | Qt.AlignmentFlag.AlignVCenter,
)
self.hBoxLayout.addWidget(
self.cancelButton,
0,
Qt.AlignmentFlag.AlignRight | Qt.AlignmentFlag.AlignVCenter,
)
self.setIcon()
self.uploadThread = None
self.startUpload()
def startUpload(self):
self.retryButton.setEnabled(False)
self.uploadThread = UploadThread(self.filePath)
self.uploadThread.uploadApplicationApprovedSignal.connect(
self.uploadApplication
)
self.uploadThread.uploadFinished.connect(self.uploadFinished)
self.uploadThread.uploadFailed.connect(self.uploadFailed)
self.uploadThread.uploadProgress.connect(self.uploadProgress)
self.uploadThread.start()
def retryUpload(self):
self.currentStatusLabel.setText("重试...")
self.startUpload()
def cancelUpload(self):
if self.uploadThread:
self.progressBar.setValue(0)
self.selfDelete()
else:
self.selfDelete()
def selfDelete(self):
self.currentStatusLabel.setText("取消中...")
self.cancelButton.setEnabled(False)
self.retryButton.setEnabled(False)
if self.uploadThread:
self.uploadThread.cancelUpload()
self.uploadThread.terminate()
self.uploadThread = None
QTimer.singleShot(1000, self.deleteLater)
def uploadApplication(self):
self.currentStatusLabel.setText("已向服务器提交任务,读取文件中...")
def uploadFinished(self):
self.currentStatusLabel.setText("上传成功")
InfoBar.success(
"成功",
f"{self.fileName}上传成功,请注意查看~",
Qt.Orientation.Horizontal,
True,
5000,
InfoBarPosition.BOTTOM_RIGHT,
InfoBar.desktopView(),
)
signalBus.refreshFolderListSignal.emit()
self.retryButton.setEnabled(False)
self.progressBar.setValue(100)
def uploadProgress(self, progress, uploaded_size, total_size):
self.progressBar.setValue(int(progress))
self.currentStatusLabel.setText(
f"上传中...({formatSize(uploaded_size)}/{formatSize(total_size)})"
)
def uploadFailed(self, error_message):
self.currentStatusLabel.setText(f"上传失败:{error_message}")
self.progressBar.setValue(0)
self.retryButton.setEnabled(True)
self.uploadThread.terminate()
self.uploadThread = None
def setIcon(self):
icon_name = getFileIcon("file", self.fileName)
self.iconLabel.setImage(QPixmap(f":app/icons/{icon_name}"))
self.iconLabel.scaledToHeight(50)
self.iconLabel.scaledToWidth(50)
class DownloadCard(CardWidget):
def __init__(self, suffix, fileName, _id, parent=None):
super().__init__(parent=parent)
self._id = _id
self.suffix = suffix
self.setFixedHeight(75)
self.hBoxLayout = QHBoxLayout(self)
self.infomationLayout = QVBoxLayout()
self.iconLabel = ImageLabel(self)
self.fileNameLabel = SubtitleLabel(fileName, self)
self.currentStatusLabel = BodyLabel("请求中...", self)
self.progressBar = ProgressBar(self)
self.progressBar.setValue(0)
self.cancelButton = PrimaryToolButton(FluentIcon.DELETE, self)
self.cancelButton.clicked.connect(self.cancelDownload)
self.hBoxLayout.addWidget(
self.iconLabel, 0, Qt.AlignmentFlag.AlignCenter | Qt.AlignmentFlag.AlignLeft
)
self.hBoxLayout.addSpacing(5)
self.infomationLayout.addWidget(
self.fileNameLabel,
0,
Qt.AlignmentFlag.AlignTop | Qt.AlignmentFlag.AlignLeft,
)
self.infomationLayout.addWidget(
self.currentStatusLabel,
0,
Qt.AlignmentFlag.AlignTop | Qt.AlignmentFlag.AlignLeft,
)
self.infomationLayout.addWidget(self.progressBar)
self.hBoxLayout.addLayout(self.infomationLayout)
self.hBoxLayout.addSpacing(10)
self.hBoxLayout.addWidget(
self.cancelButton,
0,
Qt.AlignmentFlag.AlignRight | Qt.AlignmentFlag.AlignVCenter,
)
suffix = self.suffix.split(".")[1].lower()
self._type = self.suffix.split(".")[0].lower()
self.setIcon(suffix)
self.downloadThread = None
self.startUpload()
def startUpload(self):
if self._type == "own":
# 同时传递file_id和file_path确保file_path不为空
# 对于own类型使用self._id作为file_path因为它已经包含了完整的cloudreve://格式路径
self.downloadThread = DownloadThread(self._id, self._id)
elif self._type == "share":
self.downloadThread = DownloadShareThread(self._id)
self.downloadThread.downloadUrlAcquired.connect(self.downloadUrlAcquired)
self.downloadThread.downloadFinished.connect(self.downloadFinished)
self.downloadThread.downloadFailed.connect(self.downloadFailed)
self.downloadThread.downloadProgress.connect(self.downloadProgress)
self.downloadThread.start()
def cancelDownload(self):
if self.downloadThread:
self.progressBar.setValue(0)
self.selfDelete()
else:
self.selfDelete()
def selfDelete(self):
self.currentStatusLabel.setText("取消中...")
self.cancelButton.setEnabled(False)
if self.downloadThread:
self.downloadThread.cancelDownload()
self.downloadThread.terminate()
self.downloadThread = None
QTimer.singleShot(1000, self.deleteLater)
def downloadUrlAcquired(self):
self.currentStatusLabel.setText("成功获取下载链接,准备下载...")
def downloadFinished(self):
self.currentStatusLabel.setText("下载成功")
InfoBar.success(
"成功",
f"{self._id}下载成功,请注意查看~",
Qt.Orientation.Horizontal,
True,
5000,
InfoBarPosition.BOTTOM_RIGHT,
InfoBar.desktopView(),
)
self.progressBar.setValue(100)
def downloadProgress(self, progress, uploaded_size, total_size):
self.progressBar.setValue(int(progress))
self.currentStatusLabel.setText(
f"下载中...({formatSize(uploaded_size)}/{formatSize(total_size)})"
)
def downloadFailed(self, error_message):
self.currentStatusLabel.setText(f"下载失败:{error_message}")
self.progressBar.setValue(0)
self.downloadThread.terminate()
self.downloadThread = None
def setIcon(self, fileName):
icon_name = getFileIcon("file", fileName)
self.iconLabel.setImage(QPixmap(f":app/icons/{icon_name}"))
self.iconLabel.scaledToHeight(50)
self.iconLabel.scaledToWidth(50)

View File

@@ -0,0 +1,37 @@
# coding: utf-8
from PyQt6.QtCore import Qt
from PyQt6.QtWidgets import QVBoxLayout
from qfluentwidgets import BodyLabel, ElevatedCardWidget, SubtitleLabel
class GbInformationCard(ElevatedCardWidget):
def __init__(self, amount,station,parent=None):
super().__init__(parent=parent)
self.currentAmountLabel = SubtitleLabel(self)
self.currentAmountLabel.setText(self.formatSize(amount))
self.currentAmountLabel.setAlignment(Qt.AlignmentFlag.AlignCenter)
self.stationLabel = BodyLabel(station,self)
self.stationLabel.setAlignment(Qt.AlignmentFlag.AlignCenter)
self.vBoxLayout = QVBoxLayout(self)
self.vBoxLayout.setContentsMargins(2,5,2,5)
self.vBoxLayout.addWidget(self.currentAmountLabel,0,Qt.AlignmentFlag.AlignTop)
self.vBoxLayout.addSpacing(0)
self.vBoxLayout.addWidget(self.stationLabel,0,Qt.AlignmentFlag.AlignTop)
@staticmethod
def formatSize(size):
"""格式化文件大小"""
for unit in ["B", "KB", "MB", "GB", "TB"]:
if size < 1024:
return f"{size:.2f} {unit}"
size /= 1024
return f"{size:.2f} PB"
def updateValue(self,value):
self.currentAmountLabel.setText(self.formatSize(value))

View File

@@ -0,0 +1,362 @@
# coding: utf-8
from loguru import logger
from PyQt6.QtCore import Qt, pyqtSignal
from PyQt6.QtWidgets import QFileDialog, QVBoxLayout, QWidget
from qfluentwidgets import (
Action,
InfoBar,
InfoBarPosition,
MenuAnimationType,
RoundMenu,
ScrollArea,
)
from qfluentwidgets import FluentIcon as FIF
from app.core import (lang, ListFileThread, ListSearchThread, ListShareThread, policyConfig, signalBus)
from app.view.components.file_card import FileCard, ShareFileCard
from app.view.widgets.new_folder_messageBox import NewFolderMessageBox
from app.view.widgets.policy_messageBox import PolicyChooseMessageBox
class LinkageSwitchingBase(ScrollArea):
"""文件卡片滚动区域组件基"""
def __init__(self, parent=None):
super().__init__(parent)
self.fileCardsDict = {} # 存储所有文件卡片
self.widgets = QWidget()
self.layouts = QVBoxLayout(self.widgets)
self.layouts.setAlignment(Qt.AlignmentFlag.AlignTop)
self.setWidget(self.widgets)
self.setWidgetResizable(True)
self.layouts.setContentsMargins(5, 5, 5, 0)
self.layouts.setSpacing(5)
self.widgets.setStyleSheet("background-color: transparent; border: none;")
self.setStyleSheet("background-color: transparent; border: none;")
self.setHorizontalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAlwaysOff)
self.setVerticalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAsNeeded)
def addFileCard(self, fileId, data):
"""
添加文件卡片
Args:
fileId: 文件的唯一标识符
data: 文件数据对象
Returns:
创建的文件卡片对象
"""
if fileId in self.fileCardsDict:
logger.warning(f"文件卡片已存在: {fileId}")
return self.fileCardsDict[fileId]
# 安全地获取对象属性提供默认值以避免KeyError
fileId = data.get("id", "")
fileName = data.get("name", "未知文件")
# Cloudreve V4 API使用数字1表示文件夹0表示文件
fileType_num = data.get("type", 0)
# 将数字类型转换为字符串表示
fileType = "folder" if fileType_num == 1 else "file"
filePath = data.get("path", "")
# 使用created_at或updated_at作为日期
fileDate = data.get("created_at", data.get("date", ""))
fileSize = data.get("size", 0)
fileCard = FileCard(
fileId,
fileName,
fileType,
filePath,
fileDate,
fileSize,
self,
)
fileCard.setObjectName(f"fileCard_{fileId}")
self.fileCardsDict[fileId] = fileCard
self.layouts.addWidget(fileCard)
return fileCard
def removeFileCard(self, fileId):
"""移除文件卡片"""
if fileId in self.fileCardsDict:
fileCard = self.fileCardsDict[fileId]
self.layouts.removeWidget(fileCard)
fileCard.deleteLater()
del self.fileCardsDict[fileId]
else:
logger.warning(f"尝试移除不存在的文件卡片: {fileId}")
def clearFileCards(self):
"""清除所有文件卡片"""
logger.debug("清除所有文件卡片")
fileIds = list(self.fileCardsDict.keys())
for fileId in fileIds:
self.removeFileCard(fileId)
def refreshFolderList(self):
logger.debug("刷新文件夹列表")
InfoBar.success(
"成功",
"刷新成功",
Qt.Orientation.Horizontal,
True,
1000,
InfoBarPosition.TOP_RIGHT,
self.window(),
)
signalBus.refreshFolderListSignal.emit()
# 个人文件浏览区域
class OwnFileLinkageSwitching(LinkageSwitchingBase):
"""个人件卡片滚动区域组件"""
def __init__(self, paths, parent=None):
super(OwnFileLinkageSwitching, self).__init__(parent)
self.currentPath = paths
self.fileCardsDict = {} # 存储所有文件卡片
self.loadDict("/")
def contextMenuEvent(self, e):
"""菜单事件"""
logger.debug("触发上下文菜单事件")
menu = RoundMenu(parent=self)
menu.addAction(
Action(FIF.SYNC, lang("刷新当前"), triggered=self.refreshFolderList)
)
menu.addSeparator()
menu.addAction(
Action(FIF.ADD, lang("新建文件夹"), triggered=self._createFolder)
)
menu.addSeparator()
menu.addAction(Action(FIF.UP, lang("上传文件"), triggered=self._uploadFile))
menu.addSeparator()
menu.addAction(
Action(FIF.CLOUD, lang("设置存储策略"), triggered=self._choosePolicy)
)
menu.exec(e.globalPos(), aniType=MenuAnimationType.DROP_DOWN)
def _choosePolicy(self):
w = PolicyChooseMessageBox(self.window())
if w.exec():
...
def _createFolder(self):
w = NewFolderMessageBox(self.window())
if w.exec():
...
def _uploadFile(self):
file_name, _ = QFileDialog.getOpenFileName(
self.window(), "选择文件", "", "所有文件 (*)"
)
if file_name:
signalBus.addUploadFileTask.emit(file_name)
def loadDict(self, paths):
"""加载目录数据"""
logger.info(f"加载目录数据: {paths}")
policyConfig.setCurrentPath(paths)
self.currentPath = paths
self.loadDataThread = ListFileThread(paths)
self.loadDataThread.listDictSignal.connect(self.dealData)
self.loadDataThread.errorSignal.connect(self._errorLoadDict)
self.loadDataThread.start()
def dealData(self, data):
"""处理目录数据"""
self.clearFileCards()
logger.info("设置当前页策略")
# 安全地访问策略信息考虑data["data"]可能是列表的情况
if isinstance(data, dict) and "data" in data:
data_content = data["data"]
if isinstance(data_content, dict):
# Cloudreve V4 API格式处理
if "storage_policy" in data_content:
policyConfig.setPolicy(data_content["storage_policy"])
elif "policy" in data_content:
policyConfig.setPolicy(data_content["policy"])
elif isinstance(data_content, list) and data_content:
# 如果data_content是列表尝试从第一个元素获取策略
logger.warning("data['data']是列表而不是字典可能需要调整API响应处理")
# 处理data["data"]可能是列表或字典的情况
data_content = data.get("data", {})
if isinstance(data_content, list):
# 如果是列表直接使用列表作为objects
logger.info(f"成功加载目录数据,对象数量: {len(data_content)}")
self.objects = data_content
elif isinstance(data_content, dict):
# Cloudreve V4 API格式处理先检查files字段
if "files" in data_content:
# Cloudreve V4 API 使用files字段存储文件列表
logger.info(f"成功加载目录数据,对象数量: {len(data_content['files'])}")
self.objects = data_content["files"]
elif "objects" in data_content:
# 向后兼容旧版API
logger.info(f"成功加载目录数据,对象数量: {len(data_content['objects'])}")
self.objects = data_content["objects"]
else:
logger.error("目录数据格式错误字典中没有files或objects字段")
return
else:
logger.error("目录数据格式错误")
return
for obj in self.objects:
self.addFileCard(obj["id"], obj)
def _errorLoadDict(self, error_msg):
"""处理加载目录数据失败"""
logger.error(f"加载目录数据失败: {error_msg}")
InfoBar.error(
"错误",
f"加载目录数据失败: {error_msg}",
Qt.Orientation.Horizontal,
True,
-1,
InfoBarPosition.TOP_RIGHT,
self.window(),
)
self.loadDict("/")
# 搜索文件浏览区域
class SearchLinkageSwitching(LinkageSwitchingBase):
"""文件卡片滚动区域组件"""
def __init__(self, parent=None):
super(SearchLinkageSwitching, self).__init__(parent)
self.fileCardsDict = {} # 存储所有文件卡片
def search(self, searchType, searchContent):
"""加载数据"""
self.loadDataThread = ListSearchThread(searchContent, searchType)
self.loadDataThread.listDictSignal.connect(self._dealData)
self.loadDataThread.errorSignal.connect(self._error)
self.loadDataThread.start()
def _dealData(self, data):
"""处理数据"""
if not data or "data" not in data or "objects" not in data["data"]:
logger.error("数据格式错误")
return
logger.info(f"成功加载数据,对象数量: {len(data['data']['objects'])}")
self.objects = data["data"]["objects"]
self.clearFileCards()
for obj in self.objects:
self.addFileCard(obj["id"], obj)
def _error(self, msg):
"""处理错误"""
logger.error(f"加载数据失败: {msg}")
InfoBar.error(
"错误",
msg,
Qt.Orientation.Horizontal,
True,
1000,
InfoBarPosition.TOP_RIGHT,
self.window(),
)
# 分享文件浏览区域
class ShareLinkageSwitching(LinkageSwitchingBase):
"""文件卡片滚动区域组件"""
totalItemsSignal = pyqtSignal(int) # 信号:传递总数量
def __init__(self, parent=None):
super().__init__(parent)
self.fileCardsDict = {} # 存储所有文件卡片
logger.debug(f"初始化搜索卡片滚动区域")
def addFileCard(self, fileId, obj):
if fileId in self.fileCardsDict:
logger.warning(f"文件卡片已存在: {fileId}")
return self.fileCardsDict[fileId]
fileCard = ShareFileCard(
obj,
self,
)
fileCard.setObjectName(f"fileCard_{fileId}")
self.fileCardsDict[fileId] = fileCard
self.layouts.addWidget(fileCard)
return fileCard
def search(self, keyword, orderBy, order, page):
"""加载数据"""
self.loadDataThread = ListShareThread(keyword, orderBy, order, page)
self.loadDataThread.listDictSignal.connect(self._dealData)
self.loadDataThread.errorSignal.connect(self._error)
self.loadDataThread.start()
def _dealData(self, data):
"""处理数据"""
if not data or "data" not in data:
logger.error("数据格式错误缺少data字段")
return
# 处理data["data"]可能是列表或字典的情况
data_content = data["data"]
if isinstance(data_content, list):
logger.warning("data['data']是列表而不是字典,将直接使用列表数据")
self.objects = data_content
elif isinstance(data_content, dict):
# 尝试从字典中获取对象列表按照Cloudreve V4 API格式处理
if "files" in data_content:
# Cloudreve V4 API 使用files字段存储文件列表
self.objects = data_content["files"]
elif "items" in data_content:
self.objects = data_content["items"]
elif "objects" in data_content:
self.objects = data_content["objects"]
else:
logger.error("数据格式错误字典中没有files、items或objects字段")
return
else:
logger.error(f"数据格式错误data['data']类型为{type(data_content).__name__},应为列表或字典")
return
logger.info(f"成功加载数据,对象数量: {len(self.objects)}")
# 尝试获取总数,如果不存在则不发送信号
if isinstance(data_content, dict) and "total" in data_content:
self.totalItemsSignal.emit(data_content["total"])
self.clearFileCards()
for obj in self.objects:
# 使用obj中可能存在的不同键名
file_id = obj.get("key", obj.get("id", None))
file_path = obj.get("path", None)
if file_id:
self.addFileCard(file_id, obj)
def _error(self, error):
"""处理错误"""
logger.error(f"加载数据失败: {error}")
InfoBar.error(
"错误",
f"加载数据失败: {error}",
Qt.Orientation.Horizontal,
True,
1000,
InfoBarPosition.TOP_RIGHT,
self.window(),
)