| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432 |
- # -*- coding: utf-8 -*-
- import ftplib
- import inspect
- import os
- import shutil
- from PyQt5 import QtCore, QtGui, QtWidgets
- from PyQt5.QtCore import QSize, QUrl, Qt
- from PyQt5.QtGui import QIcon
- from PyQt5.QtWidgets import QVBoxLayout, QHBoxLayout, QPushButton, QTreeWidget, QTreeWidgetItem, QMessageBox, \
- QInputDialog, QLineEdit, QWidget, QFileDialog
- from .FtpUitl import FtpOper
- from .FtpConfig import *
- # from .ModelWebView import ModelWebView
- current_directory = os.path.dirname(os.path.abspath(inspect.getfile(inspect.currentframe()))) # 获取当前路径
- class Ui_FTPDockWidget(object):
- def setupUi(self, FTPDockWidget):
- self.ftpOper = FtpOper()
- try:
- self.ftpOper.connect(ftpHost, ftpPort, ftpUsername, ftpPassword, timeout=30, passive=True)
- except Exception as e:
- QMessageBox.critical(None, 'FTP连接失败', f'无法连接到FTP服务器: {str(e)}')
- self.fileItem = None
- self.ftpItem = None
- self.createTempDir()
- FTPDockWidget.setObjectName("FTPDockWidget")
- FTPDockWidget.resize(422, 303)
- font = QtGui.QFont()
- font.setFamily("微软雅黑")
- FTPDockWidget.setFont(font)
- self.dockWidgetContents = QtWidgets.QWidget()
- self.dockWidgetContents.setObjectName("dockWidgetContents")
- # 添加FTP管理界面
- self.mainLayout = QVBoxLayout(self.dockWidgetContents)
- self.layoutH = QHBoxLayout() # 按钮布局
- self.layoutV = QVBoxLayout() # 树结构的布局
- # 功能按钮区域
- self.uploadFileButton = QPushButton()
- self.uploadFileButton.setCursor(Qt.PointingHandCursor)
- self.setBtnStyleSheet(self.uploadFileButton)
- self.uploadFileButton.setIcon(QIcon(os.path.join(current_directory + "\\icon", "文件上传.png")))
- self.uploadFileButton.setIconSize(QSize(20, 20))
- self.uploadFileButton.setFixedSize(27, 27)
- self.uploadFileButton.setToolTip("上传文件")
- self.uploadFileButton.clicked.connect(self.actionUploadHandler)
- self.layoutH.addWidget(self.uploadFileButton)
- self.uploadFolderButton = QPushButton()
- self.uploadFolderButton.setCursor(Qt.PointingHandCursor)
- self.setBtnStyleSheet(self.uploadFolderButton)
- self.uploadFolderButton.setIcon(QIcon(os.path.join(current_directory + "\\icon", "文件夹上传.png")))
- self.uploadFolderButton.setIconSize(QSize(20, 20))
- self.uploadFolderButton.setFixedSize(27, 27)
- self.uploadFolderButton.setToolTip("上传文件夹")
- self.uploadFolderButton.clicked.connect(self.actionUploadDirHandler)
- self.layoutH.addWidget(self.uploadFolderButton)
- self.downloadButton = QPushButton()
- self.downloadButton.setCursor(Qt.PointingHandCursor)
- self.setBtnStyleSheet(self.downloadButton)
- self.downloadButton.setIcon(QIcon(os.path.join(current_directory + "\\icon", "文件下载.png")))
- self.downloadButton.setIconSize(QSize(20, 20))
- self.downloadButton.setFixedSize(27, 27)
- self.downloadButton.setToolTip("下载")
- self.downloadButton.clicked.connect(self.actionDownloadHandler)
- self.layoutH.addWidget(self.downloadButton)
- self.createDirButton = QPushButton()
- self.createDirButton.setCursor(Qt.PointingHandCursor)
- self.setBtnStyleSheet(self.createDirButton)
- self.createDirButton.setIcon(QIcon(os.path.join(current_directory + "\\icon", "新建文件夹.png")))
- self.createDirButton.setIconSize(QSize(20, 20))
- self.createDirButton.setFixedSize(27, 27)
- self.createDirButton.setToolTip("新建目录")
- self.createDirButton.clicked.connect(self.actionCreateHandler)
- self.layoutH.addWidget(self.createDirButton)
- self.renameButton = QPushButton()
- self.renameButton.setCursor(Qt.PointingHandCursor)
- self.setBtnStyleSheet(self.renameButton)
- self.renameButton.setIcon(QIcon(os.path.join(current_directory + "\\icon", "文件重命名.png")))
- self.renameButton.setIconSize(QSize(20, 20))
- self.renameButton.setFixedSize(27, 27)
- self.renameButton.setToolTip("重命名")
- self.renameButton.clicked.connect(self.actionRenameHandler)
- self.layoutH.addWidget(self.renameButton)
- self.deleteButton = QPushButton()
- self.deleteButton.setCursor(Qt.PointingHandCursor)
- self.setBtnStyleSheet(self.deleteButton)
- self.deleteButton.setIcon(QIcon(os.path.join(current_directory + "\\icon", "文件删除.png")))
- self.deleteButton.setIconSize(QSize(20, 20))
- self.deleteButton.setFixedSize(27, 27)
- self.deleteButton.setToolTip("删除")
- self.deleteButton.clicked.connect(self.actionDeleteHandler)
- self.layoutH.addWidget(self.deleteButton)
- self.modelViewButton = QPushButton()
- self.modelViewButton.setCursor(Qt.PointingHandCursor)
- self.setBtnStyleSheet(self.modelViewButton)
- self.modelViewButton.setIcon(QIcon(os.path.join(current_directory + "\\icon", "模型查看.png")))
- self.modelViewButton.setIconSize(QSize(20, 20))
- self.modelViewButton.setFixedSize(27, 27)
- self.modelViewButton.setToolTip("查看模型")
- self.modelViewButton.clicked.connect(self.action3DHandler)
- self.layoutH.addWidget(self.modelViewButton)
- self.openFileButton = QPushButton()
- self.openFileButton.setCursor(Qt.PointingHandCursor)
- self.setBtnStyleSheet(self.openFileButton)
- self.openFileButton.setIcon(QIcon(os.path.join(current_directory + "\\icon", "文件查看.png")))
- self.openFileButton.setIconSize(QSize(20, 20))
- self.openFileButton.setFixedSize(27, 27)
- self.openFileButton.setToolTip("查看文件")
- self.openFileButton.clicked.connect(self.actionFileOpenHandler)
- self.layoutH.addWidget(self.openFileButton)
- # 添加动态布局 元素自动左对齐
- self.layoutH.addStretch()
- # 目录树结构区
- self.treeWidget = QTreeWidget() # QTreeWidget组件定义
- self.treeWidget.headerItem().setText(0, "列表") # FTP服务器节点树
- self.treeWidget.headerItem().setText(1, "类型") # 节点对应的类型:文件,文件夹
- self.treeWidget.headerItem().setText(2, "路径") # 当前节点在FTP服务器中的路径,方便文件操作时获取服务器中路径,为隐藏列
- self.treeWidget.setColumnWidth(0, 375) # 给第1列设置列宽
- self.rootItem = QTreeWidgetItem()
- self.rootItem.setText(0, "文件结构树") # 给根节点增加文本
- self.treeWidget.addTopLevelItem(self.rootItem) # 增加根节点
- self.layoutV.addWidget(self.treeWidget) # 将组件添加到布局中
- self.treeWidget.setColumnHidden(1, True) # 隐藏路径列
- self.treeWidget.setColumnHidden(2, True) # 隐藏路径列
- self.mainLayout.addLayout(self.layoutH)
- self.mainLayout.addLayout(self.layoutV) # 为窗体添加布局
- FTPDockWidget.setWidget(self.dockWidgetContents)
- self.retranslateUi(FTPDockWidget)
- QtCore.QMetaObject.connectSlotsByName(FTPDockWidget)
- # 遍历构建树
- try:
- self.traverseFTPDirectory(self.ftpOper.ensure_connected(), '/', self.rootItem)
- self.rootItem.setExpanded(True)
- except Exception as e:
- QMessageBox.warning(None, 'FTP错误', f'读取目录失败: {str(e)}')
- def retranslateUi(self, SearchDockWidget):
- _translate = QtCore.QCoreApplication.translate
- SearchDockWidget.setWindowTitle(_translate("SearchDockWidget", "文件服务器"))
- # 设置按钮样式
- def setBtnStyleSheet(self, btn):
- btn.setStyleSheet("""
- QPushButton {
- border: 2px solid transparent; /* 默认边框透明 */
- padding: 10px;
- background-color: #f0f0f0;
- }
- QPushButton:hover {
- background-color: #f6f6f6;
- border: 1px solid #958585; /* 鼠标悬浮时显示蓝色边框 */
- }
- """)
- def actionUploadHandler(self):
- if self.treeWidget.currentItem():
- self.currentItem = self.treeWidget.currentItem()
- if self.currentItem.text(1) == '文件': # 如果选中文件节点则获取此节点的父节点
- self.ftp_path = self.currentItem.parent().text(2)
- self.ftpItem = self.currentItem.parent()
- elif self.currentItem.text(1) == '文件夹':
- self.ftp_path = self.currentItem.text(2)
- self.ftpItem = self.currentItem
- file_paths, _ = QFileDialog.getOpenFileNames(None, '选择文件', '', 'All Files (*)')
- if len(file_paths) > 0:
- for file_path in file_paths:
- try:
- self.file_name = os.path.basename(file_path)
- self.ftpOper.uploadfile(file_path, self.ftp_path)
- self.fileItem = QTreeWidgetItem()
- self.fileItem.setText(0, self.file_name)
- self.fileItem.setText(1, '文件')
- self.fileItem.setText(2, self.ftp_path + '/' + self.file_name)
- # 已有同名节点不再增加此名称的节点
- havesame = self.findTreeSameNode(self.ftpItem, self.file_name)
- if not havesame:
- self.ftpItem.addChild(self.fileItem)
- except Exception as e:
- QMessageBox.warning(None, '上传失败', f'上传文件失败: {self.file_name}\n{str(e)}')
- else:
- QMessageBox.information(self, "提示信息", "请选择树节点")
- # 文件上传时查找目录树此节点的子节点中是否已有此名称
- def findTreeSameNode(self, thisitem, filename):
- for i in range(thisitem.childCount()):
- child = thisitem.child(i)
- if child.text(0) == filename:
- return True
- return False
- # 文件夹上传操作
- def actionUploadDirHandler(self):
- if self.treeWidget.currentItem():
- self.currentItem = self.treeWidget.currentItem()
- if self.currentItem.text(1) == '文件': # 如果选中文件节点则获取此节点的父节点
- self.ftp_path = self.currentItem.parent().text(2)
- self.ftpItem = self.currentItem.parent()
- else:
- self.ftp_path = self.currentItem.text(2)
- self.ftpItem = self.currentItem
- dir_path = QFileDialog.getExistingDirectory(None, "选择文件夹", "")
- if dir_path:
- try:
- self.ftpOper.uploaddir(dir_path, self.ftp_path)
- dirname = os.path.split(dir_path)[-1] # 文件夹名称
- rootchildItem = QTreeWidgetItem()
- rootchildItem.setText(0, dirname)
- rootchildItem.setText(1, '文件夹')
- rootchildItem.setText(2, self.ftp_path + '/' + dirname)
- self.ftpItem.addChild(rootchildItem)
- self.traverseLocalDirectory(dir_path, rootchildItem, self.ftp_path + '/' + dirname)
- except Exception as e:
- QMessageBox.warning(None, '上传失败', f'上传文件夹失败: {str(e)}')
- else:
- QMessageBox.information(self, "提示信息", "请选择树节点")
- # 遍历本地目录,构建可视化树结构
- def traverseLocalDirectory(self, dirpath, item, ftppath):
- for file in os.listdir(dirpath):
- src = os.path.join(dirpath, file)
- childItem = QTreeWidgetItem()
- childItem.setText(0, file)
- if os.path.isfile(src):
- childItem.setText(1, '文件')
- childItem.setText(2, ftppath + '/' + file)
- item.addChild(childItem)
- elif os.path.isdir(src):
- childItem.setText(1, '文件夹')
- childItem.setText(2, ftppath + '/' + file)
- item.addChild(childItem)
- # 递归调用自身
- self.traverseLocalDirectory(src, childItem, ftppath + '/' + file)
- # 文件下载操作
- def actionDownloadHandler(self):
- if self.treeWidget.currentItem():
- self.currentItem = self.treeWidget.currentItem()
- dir_path = QFileDialog.getExistingDirectory(None, "选择文件夹", "")
- if dir_path:
- try:
- if self.currentItem.text(1) == '文件':
- localfilepath = dir_path + '/' + self.currentItem.text(0)
- reomtefilepath = self.currentItem.text(2)
- self.ftpOper.downloadfile(localfilepath, reomtefilepath)
- else:
- localdirpath = dir_path + '/' + self.currentItem.text(0)
- remotedirpath = self.currentItem.text(2)
- self.ftpOper.downloaddir(localdirpath, remotedirpath)
- except Exception as e:
- QMessageBox.warning(None, '下载失败', f'下载失败: {str(e)}')
- else:
- QMessageBox.information(self, "提示信息", "请选择树节点")
- # 文件删除操作
- def actionDeleteHandler(self):
- if self.treeWidget.currentItem():
- self.currentItem = self.treeWidget.currentItem()
- questiontext = '是否要删除所选中的:【' + self.currentItem.text(0) + '】?'
- question = QMessageBox.question(None, '删除确认', questiontext)
- if question == QMessageBox.Yes:
- try:
- reomtefilepath = self.currentItem.text(2)
- if self.currentItem.text(1) == '文件':
- remotepath, remotefile_name = os.path.split(reomtefilepath)
- self.ftpOper.deletfile(remotefile_name, remotepath) # 删除FTP服务端文件
- self.currentItem.parent().removeChild(self.currentItem) # 删除目录树对应的节点
- else:
- self.ftpOper.deletedir(reomtefilepath)
- self.currentItem.parent().removeChild(self.currentItem) # 删除目录树对应的节点
- except Exception as e:
- QMessageBox.warning(None, '删除失败', f'删除失败: {str(e)}')
- else:
- QMessageBox.information(self, "提示信息", "请选择树节点")
- # 创建文件夹操作
- def actionCreateHandler(self):
- if self.treeWidget.currentItem():
- self.currentItem = self.treeWidget.currentItem()
- dirname, ok = QInputDialog.getText(QWidget(), '新建文件夹', '请输入文件夹名称:')
- if ok:
- if self.currentItem.text(1):
- if self.currentItem.text(1) == '文件': # 如果选中文件节点则获取此节点的父节点
- self.dirpath = self.currentItem.parent().text(2)
- self.ftpItem = self.currentItem.parent()
- else:
- self.dirpath = self.currentItem.text(2)
- self.ftpItem = self.currentItem
- else:
- self.dirpath = ''
- self.ftpItem = self.currentItem
- self.newdirpath = self.dirpath + '/' + dirname
- self.rusulttext = self.ftpOper.makedir(dirname, self.dirpath, self.newdirpath)
- if self.rusulttext == '文件夹创建成功' or self.rusulttext == '文件夹已存在':
- self.fileItem = QTreeWidgetItem()
- self.fileItem.setText(0, dirname)
- self.fileItem.setText(1, '文件夹')
- self.fileItem.setText(2, self.newdirpath)
- self.ftpItem.addChild(self.fileItem)
- else:
- QMessageBox.information(self, "提示信息", "请选择树节点")
- # 重命名操作
- def actionRenameHandler(self):
- if self.treeWidget.currentItem():
- self.currentItem = self.treeWidget.currentItem()
- extension = None
- oldname = None
- oldpath = None
- if self.currentItem.text(1) == '文件':
- oldname, oldextension = os.path.splitext(self.currentItem.text(0))
- elif self.currentItem.text(1) == '文件夹':
- oldname = self.currentItem.text(0)
- newname, ok = QInputDialog.getText(QWidget(), '重命名', '请输入新名称', QLineEdit.Normal, oldname)
- if ok:
- try:
- if self.currentItem.text(1) == '文件':
- oldpath = self.currentItem.text(2)
- pathparent, name1 = os.path.split(oldpath)
- name2, extension = os.path.splitext(name1)
- newpath = pathparent + '/' + newname + extension
- else:
- oldpath = self.currentItem.text(2)
- if not (self.currentItem.parent().text(1)): # 根目录
- newpath = '/' + newname
- else:
- pathparent, name1 = os.path.split(oldpath)
- newpath = pathparent + '/' + newname
- result = self.ftpOper.rename(oldpath, newpath)
- if result:
- if extension:
- self.currentItem.setText(0, newname + extension)
- else:
- self.currentItem.setText(0, newname)
- self.currentItem.setText(2, newpath)
- except Exception as e:
- QMessageBox.warning(None, '重命名失败', f'重命名失败: {str(e)}')
- else:
- QMessageBox.information(self, "提示信息", "请选择树节点")
- # 查看三维模型操作
- def action3DHandler(self):
- if self.treeWidget.currentItem():
- self.currentItem = self.treeWidget.currentItem()
- if (self.currentItem.text(1) == '文件'):
- try:
- rel_path = self.currentItem.text(2)
- modelUrl = (modelViewerUri + modelBaseUri + rel_path).replace('\\', '/')
- print(f'打开模型URL: {modelUrl}')
- try:
- # 优先使用内置WebView
- from .ModelWebView import ModelWebView # 延迟导入,避免环境缺少QtWebEngine时报错
- self.modelWebView = ModelWebView(weburi=modelUrl, title='数管系统三维模型浏览')
- self.modelWebView.show()
- except Exception as webview_err:
- print(f'ModelWebView不可用,回退到系统浏览器: {webview_err}')
- # 回退到系统默认浏览器
- QtGui.QDesktopServices.openUrl(QUrl(modelUrl))
- QMessageBox.information(None, '提示', '已在默认浏览器中打开模型')
- except Exception as e:
- QMessageBox.warning(None, '打开失败', f'无法打开模型: {str(e)}')
- else:
- QMessageBox.information(self, "提示信息", "请选择文件类型树节点")
- else:
- QMessageBox.information(self, "提示信息", "请选择树节点")
- # 打开文件操作
- def actionFileOpenHandler(self):
- if self.treeWidget.currentItem():
- self.currentItem = self.treeWidget.currentItem()
- if (self.currentItem.text(1) == '文件'):
- localdir = os.path.abspath(os.curdir)
- localtempdir = localdir + '/temp'
- if os.path.exists(localtempdir):
- if self.currentItem.text(1) == '文件':
- localfilepath = localtempdir + '/' + self.currentItem.text(0)
- reomtefilepath = self.currentItem.text(2)
- if os.path.exists(localfilepath):
- try:
- os.startfile(localfilepath) # 打开本地文件
- except Exception as e:
- print(e)
- else:
- self.ftpOper.downloadfile(localfilepath, reomtefilepath) # 下载到本地临时文件夹
- try:
- os.startfile(localfilepath) # 打开本地文件
- except Exception as e:
- print(e)
- else:
- QMessageBox.information(self, "提示信息", "请选择文件类型树节点")
- else:
- QMessageBox.information(self, "提示信息", "请选择树节点")
- # 创建本地文件临时文件夹
- def createTempDir(self):
- localdir = os.path.abspath(os.curdir)
- localtempdir = localdir + '/temp'
- if not (os.path.exists(localtempdir)): # 如不存在,创建本地临时文件夹
- os.makedirs(localtempdir)
- else: # 如存在,删除后重新创建本地临时文件夹
- shutil.rmtree(localtempdir)
- os.mkdir(localtempdir)
- # 遍历FTP服务端目录,构建可视化树结构
- def traverseFTPDirectory(self, ftp, path, item):
- try:
- ftp.cwd(path)
- file_list = ftp.nlst()
- except Exception as e:
- raise e
- for file in file_list:
- try:
- ftp.cwd(path + '/' + file) # 尝试切换到子目录
- # 添加子节点
- childItem = QTreeWidgetItem()
- childItem.setText(0, file)
- childItem.setText(1, '文件夹')
- childItem.setText(2, (path + '/' + file).replace('//', '/'))
- item.addChild(childItem)
- # 递归调用自身
- self.traverseFTPDirectory(ftp, (path + '/' + file).replace('//', '/'), childItem)
- ftp.cwd('..') # 返回到上一级目录
- except ftplib.error_perm:
- # 添加子节点
- childItem = QTreeWidgetItem()
- childItem.setText(0, file)
- childItem.setText(1, '文件')
- childItem.setText(2, (path + '/' + file).replace('//', '/'))
- item.addChild(childItem)
|