1
This commit is contained in:
72
backend/dvadmin3_build/README.md
Normal file
72
backend/dvadmin3_build/README.md
Normal file
@@ -0,0 +1,72 @@
|
||||
|
||||
|
||||
# dvadmin3-build
|
||||
|
||||
## 介绍
|
||||
一款适用于**django-vue3-admin** 编译打包exe、macOS的dmg文件等打包工具。支持加密代码、一键启动项目,无需考虑环境。
|
||||
|
||||
**dvadmin3-build** 是一个方便的工具,用于将**django-vue3-admin**项目编译打包为可执行文件(如exe)或macOS的dmg文件等。它提供了以下好处:
|
||||
|
||||
- 方便的部署:使用**dvadmin3-build**,您无需担心环境依赖和配置问题。您可以将整个项目打包为一个可执行文件或者安装包,方便快速部署在任何支持的操作系统上。
|
||||
|
||||
- 代码加密:**dvadmin3-build** 支持代码加密,可以将您的项目源代码加密为二进制格式,增加代码的安全性和保护知识产权。
|
||||
|
||||
- 一键启动项目:打包后的可执行文件或安装包可以通过简单的双击来启动项目,无需手动设置和配置环境,减少了部署和启动的复杂性。
|
||||
|
||||
- 跨平台支持:**dvadmin3-build** 可以将**django-vue3-admin**项目打包为适用于多个操作系统的可执行文件或安装包,包括Windows、macOS、Ubuntu、中标麒麟等操作系统中。
|
||||
|
||||
- 易于使用:**dvadmin3-build** 提供了简单易懂的命令行界面,使得打包过程更加简便和高效。只需一个简单的命令,即可完成项目的打包。
|
||||
|
||||
|
||||
|
||||
## 功能支持项
|
||||
|
||||
- [ ] 支持平台
|
||||
- [x] Windows
|
||||
- [x] MacOS
|
||||
- [ ] Ubuntu
|
||||
- [ ] 中标麒麟
|
||||
- [ ] 支持功能
|
||||
- [x] 一键启动 dvadmin3
|
||||
- [x] 托盘最小化
|
||||
- [ ] dvadmin 初始化
|
||||
- [ ] 数据库配置
|
||||
- [ ] 端口配置
|
||||
- [ ] 支持celery异步模块
|
||||
- [ ] 进程守护
|
||||
|
||||
## 功能及使用方法
|
||||
|
||||
### 安装依赖
|
||||
pip install dvadmin3_build-1.0.0-py3-none-any.whl
|
||||
|
||||
### 前端编译
|
||||
yarn run build:local
|
||||
|
||||
### 后端
|
||||
#### settings.py 中添加模块
|
||||
~~~
|
||||
INSTALLED_APPS = [
|
||||
...
|
||||
"dvadmin3_build"
|
||||
]
|
||||
|
||||
HIDDEN_IMPORTS = [
|
||||
'xxxx' # 添加 app 中自己的模块,用于编译
|
||||
]
|
||||
~~~
|
||||
|
||||
#### 迁移与初始化(项目已迁移过的不再需要)
|
||||
~~~
|
||||
python manage.py makemigrations
|
||||
python manage.py migrate
|
||||
python manage.py init
|
||||
python manage.py init_data
|
||||
~~~
|
||||
#### 编译
|
||||
~~~
|
||||
# 编译后位于 dist 目录
|
||||
python manage.py build
|
||||
# windows 打包需要安装 InstallForgeSetup.exe,使用 dvadmin3_InstallForge.ifp 模板(dvadmin3_build/windows_build_tools 目录下)
|
||||
# InstallForge 打包教程:https://www.pythonguis.com/tutorials/packaging-pyqt6-applications-windows-pyinstaller/#setup
|
||||
~~~
|
||||
0
backend/dvadmin3_build/__init__.py
Normal file
0
backend/dvadmin3_build/__init__.py
Normal file
21
backend/dvadmin3_build/builddmg.sh
Normal file
21
backend/dvadmin3_build/builddmg.sh
Normal file
@@ -0,0 +1,21 @@
|
||||
#!/bin/sh
|
||||
# 获取传入的全局参数
|
||||
dist_folder=$1
|
||||
icon_path=$2
|
||||
|
||||
# 清空dmg文件夹。
|
||||
rm -rf "$dist_folder/DVAServer"
|
||||
rm -rf "$dist_folder/main"
|
||||
# 如果DMG已经存在,则删除它。
|
||||
test -f "$dist_folder/DVAServer.dmg" && rm "$dist_folder/DVAServer.dmg"
|
||||
create-dmg \
|
||||
--volname "DVAServer" \
|
||||
--volicon $icon_path \
|
||||
--window-pos 200 120 \
|
||||
--window-size 600 300 \
|
||||
--icon-size 100 \
|
||||
--icon "DVAServer.app" 175 120 \
|
||||
--hide-extension "DVAServer.app" \
|
||||
--app-drop-link 425 120 \
|
||||
"$dist_folder/DVAServer.dmg" \
|
||||
"$dist_folder/"
|
||||
49
backend/dvadmin3_build/dvadmin_main.py
Normal file
49
backend/dvadmin3_build/dvadmin_main.py
Normal file
@@ -0,0 +1,49 @@
|
||||
# Form implementation generated from reading ui file 'dvadmin_main.ui'
|
||||
#
|
||||
# Created by: PyQt6 UI code generator 6.4.2
|
||||
#
|
||||
# WARNING: Any manual changes made to this file will be lost when pyuic6 is
|
||||
# run again. Do not edit this file unless you know what you are doing.
|
||||
|
||||
|
||||
from PyQt6 import QtCore, QtGui, QtWidgets
|
||||
|
||||
|
||||
class Ui_DvadminManager(object):
|
||||
def setupUi(self, DvadminManager):
|
||||
DvadminManager.setObjectName("DvadminManager")
|
||||
DvadminManager.resize(400, 300)
|
||||
DvadminManager.setMinimumSize(QtCore.QSize(400, 300))
|
||||
DvadminManager.setMaximumSize(QtCore.QSize(400, 300))
|
||||
self.label = QtWidgets.QLabel(parent=DvadminManager)
|
||||
self.label.setGeometry(QtCore.QRect(10, 13, 60, 21))
|
||||
self.label.setObjectName("label")
|
||||
self.status_label = QtWidgets.QLabel(parent=DvadminManager)
|
||||
self.status_label.setGeometry(QtCore.QRect(80, 12, 60, 21))
|
||||
self.status_label.setObjectName("status_label")
|
||||
self.start_button = QtWidgets.QPushButton(parent=DvadminManager)
|
||||
self.start_button.setGeometry(QtCore.QRect(210, 10, 81, 26))
|
||||
self.start_button.setObjectName("start_button")
|
||||
self.stop_button = QtWidgets.QPushButton(parent=DvadminManager)
|
||||
self.stop_button.setGeometry(QtCore.QRect(310, 9, 81, 26))
|
||||
self.stop_button.setObjectName("stop_button")
|
||||
self.line = QtWidgets.QFrame(parent=DvadminManager)
|
||||
self.line.setGeometry(QtCore.QRect(0, 40, 401, 16))
|
||||
self.line.setFrameShape(QtWidgets.QFrame.Shape.HLine)
|
||||
self.line.setFrameShadow(QtWidgets.QFrame.Shadow.Sunken)
|
||||
self.line.setObjectName("line")
|
||||
self.log_label = QtWidgets.QTextEdit(parent=DvadminManager)
|
||||
self.log_label.setGeometry(QtCore.QRect(-1, 47, 411, 261))
|
||||
self.log_label.setObjectName("log_label")
|
||||
|
||||
self.retranslateUi(DvadminManager)
|
||||
QtCore.QMetaObject.connectSlotsByName(DvadminManager)
|
||||
|
||||
def retranslateUi(self, DvadminManager):
|
||||
_translate = QtCore.QCoreApplication.translate
|
||||
DvadminManager.setWindowTitle(_translate("DvadminManager", "服务管理器"))
|
||||
self.label.setText(_translate("DvadminManager", "运行状态:"))
|
||||
self.status_label.setText(_translate("DvadminManager", "未启动"))
|
||||
self.start_button.setText(_translate("DvadminManager", "启动服务"))
|
||||
self.stop_button.setText(_translate("DvadminManager", "结束服务"))
|
||||
self.log_label.setHtml(_translate("DvadminManager", ""))
|
||||
113
backend/dvadmin3_build/dvadmin_main.ui
Normal file
113
backend/dvadmin3_build/dvadmin_main.ui
Normal file
@@ -0,0 +1,113 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>DvadminManager</class>
|
||||
<widget class="QDialog" name="DvadminManager">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>400</width>
|
||||
<height>300</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>400</width>
|
||||
<height>300</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>400</width>
|
||||
<height>300</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>服务管理器</string>
|
||||
</property>
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>10</x>
|
||||
<y>13</y>
|
||||
<width>60</width>
|
||||
<height>21</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>运行状态:</string>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QLabel" name="status_label">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>80</x>
|
||||
<y>12</y>
|
||||
<width>60</width>
|
||||
<height>21</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>未启动</string>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QPushButton" name="start_button">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>210</x>
|
||||
<y>10</y>
|
||||
<width>81</width>
|
||||
<height>26</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>启动服务</string>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QPushButton" name="stop_button">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>310</x>
|
||||
<y>9</y>
|
||||
<width>81</width>
|
||||
<height>26</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>结束服务</string>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="Line" name="line">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>40</y>
|
||||
<width>401</width>
|
||||
<height>16</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QTextEdit" name="log_label">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>-1</x>
|
||||
<y>47</y>
|
||||
<width>411</width>
|
||||
<height>261</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="html">
|
||||
<string><!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd">
|
||||
<html><head><meta name="qrichtext" content="1" /><style type="text/css">
|
||||
p, li { white-space: pre-wrap; }
|
||||
</style></head><body style=" font-family:'.AppleSystemUIFont'; font-size:13pt; font-weight:400; font-style:normal;">
|
||||
<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br /></p></body></html></string>
|
||||
</property>
|
||||
</widget>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
||||
0
backend/dvadmin3_build/extra-hooks/__init__.py
Normal file
0
backend/dvadmin3_build/extra-hooks/__init__.py
Normal file
8
backend/dvadmin3_build/extra-hooks/hooks-uvicorn.py
Normal file
8
backend/dvadmin3_build/extra-hooks/hooks-uvicorn.py
Normal file
@@ -0,0 +1,8 @@
|
||||
# extra-hooks/hooks-uvicorn.py
|
||||
from PyInstaller.utils.hooks import get_package_paths, collect_submodules
|
||||
|
||||
datas = [
|
||||
(get_package_paths('uvicorn')[1], 'uvicorn'),
|
||||
]
|
||||
|
||||
hiddenimports = collect_submodules('whitenoise')
|
||||
41
backend/dvadmin3_build/file_version_info.txt
Normal file
41
backend/dvadmin3_build/file_version_info.txt
Normal file
@@ -0,0 +1,41 @@
|
||||
# UTF-8
|
||||
#
|
||||
# For more details about fixed file info 'ffi' see:
|
||||
# http://msdn.microsoft.com/en-us/library/ms646997.aspx
|
||||
VSVersionInfo(
|
||||
ffi=FixedFileInfo(
|
||||
# filevers and prodvers should be always a tuple with four items: (1, 2, 3, 4)
|
||||
# Set not needed items to zero 0.
|
||||
filevers=(1, 0, 0, 0),
|
||||
prodvers=(1, 0, 0, 0),
|
||||
# Contains a bitmask that specifies the valid bits 'flags'r
|
||||
mask=0x3f,
|
||||
# Contains a bitmask that specifies the Boolean attributes of the file.
|
||||
flags=0x0,
|
||||
# The operating system for which this file was designed.
|
||||
# 0x4 - NT and there is no need to change it.
|
||||
OS=0x40004,
|
||||
# The general type of file.
|
||||
# 0x1 - the file is an application.
|
||||
fileType=0x1,
|
||||
# The function of the file.
|
||||
# 0x0 - the function is not defined for this fileType
|
||||
subtype=0x0,
|
||||
# Creation date and time stamp.
|
||||
date=(0, 0)
|
||||
),
|
||||
kids=[
|
||||
StringFileInfo(
|
||||
[
|
||||
StringTable(
|
||||
'080404b0',
|
||||
[StringStruct('CompanyName', '北京信码新创科技有限公司'),
|
||||
StringStruct('FileDescription', '边缘Agent'),
|
||||
StringStruct('FileVersion', '1.0.0.0'),
|
||||
StringStruct('LegalCopyright', 'Copyright (C) 2021-2025 北京信码新创科技有限公司 All Rights Reserved'),
|
||||
StringStruct('ProductName', '边缘Agent'),
|
||||
StringStruct('ProductVersion', '1.0.0.0')])
|
||||
]),
|
||||
VarFileInfo([VarStruct('Translation', [2052, 1200])])
|
||||
]
|
||||
)
|
||||
0
backend/dvadmin3_build/management/__init__.py
Normal file
0
backend/dvadmin3_build/management/__init__.py
Normal file
57
backend/dvadmin3_build/management/commands/build.py
Normal file
57
backend/dvadmin3_build/management/commands/build.py
Normal file
@@ -0,0 +1,57 @@
|
||||
import json
|
||||
import os
|
||||
from pathlib import Path
|
||||
|
||||
from django.core.management.base import BaseCommand
|
||||
|
||||
from application.settings import BASE_DIR
|
||||
|
||||
from application import settings
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
"""
|
||||
生产初始化菜单: python manage.py build
|
||||
"""
|
||||
|
||||
def add_arguments(self, parser):
|
||||
pass
|
||||
|
||||
def handle(self, *args, **options):
|
||||
print(args, options)
|
||||
base_path = Path(__file__).resolve().parent.parent
|
||||
# main.spec 路径
|
||||
main_spec_path = os.path.join(base_path.parent, 'main.spec')
|
||||
|
||||
# 执行编译
|
||||
import subprocess
|
||||
# 执行命令
|
||||
HIDDEN_IMPORTS = ','.join(getattr(settings, 'HIDDEN_IMPORTS', []))
|
||||
command = f'export BASE_DIR="{BASE_DIR}" && export HIDDEN_IMPORTS="{HIDDEN_IMPORTS}" && rm -rf {os.path.join(BASE_DIR, "dist")} && pyinstaller -y --clean {main_spec_path}'
|
||||
if os.sys.platform.startswith('win'):
|
||||
# Windows操作系统
|
||||
# command = f'setx BASE_DIR "{BASE_DIR}" && set HIDDEN_IMPORTS "{HIDDEN_IMPORTS}" && del {os.path.join(BASE_DIR, "dist")} && pyinstaller -y --clean {main_spec_path}'
|
||||
command = f'setx BASE_DIR "{BASE_DIR}" && setx HIDDEN_IMPORTS "{HIDDEN_IMPORTS}" && pyinstaller -y --clean {main_spec_path}'
|
||||
print(command)
|
||||
print("当前环境是 Windows")
|
||||
elif os.sys.platform.startswith('linux'):
|
||||
# Linux操作系统
|
||||
print("当前环境是 Linux")
|
||||
command += f' && rm -rf {os.path.join(BASE_DIR, "build")}'
|
||||
elif os.sys.platform.startswith('darwin'):
|
||||
# macOS操作系统
|
||||
print("当前环境是 macOS")
|
||||
build_dmg_path = os.path.join(base_path.parent, 'builddmg.sh')
|
||||
# 判断logo 是否存在
|
||||
logo_path = os.path.join(BASE_DIR, 'static', 'logo.icns')
|
||||
if not os.path.exists(logo_path):
|
||||
# 文件不存在的处理逻辑
|
||||
logo_path = os.path.join(base_path.parent, 'static', 'logo.icns')
|
||||
command += f' && chmod +x {build_dmg_path} && {build_dmg_path} {os.path.join(BASE_DIR, "dist")} {logo_path}'
|
||||
command += f' && rm -rf {os.path.join(BASE_DIR, "build")}'
|
||||
print(command)
|
||||
process = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True)
|
||||
for line in process.stdout:
|
||||
print(line.replace('\n', ''))
|
||||
# # 等待进程结束
|
||||
process.wait()
|
||||
304
backend/dvadmin3_build/server.py
Normal file
304
backend/dvadmin3_build/server.py
Normal file
@@ -0,0 +1,304 @@
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import signal
|
||||
import subprocess
|
||||
import webbrowser
|
||||
import time
|
||||
import threading
|
||||
from pathlib import Path
|
||||
|
||||
from PyQt6.QtCore import QObject, QRunnable, QThreadPool, QTimer, pyqtSignal, Qt
|
||||
from PyQt6.QtNetwork import QLocalServer
|
||||
from PyQt6.QtWidgets import QApplication, QMainWindow, QLabel, QSystemTrayIcon, QMenu, QMessageBox
|
||||
from PyQt6.QtGui import QIcon, QTextCharFormat, QColor, QTextCursor
|
||||
from PyQt6 import QtCore, QtGui, QtWidgets
|
||||
|
||||
import sys
|
||||
|
||||
|
||||
# # 编译ui
|
||||
# pyuic6 dvadmin_main.ui -o dvadmin_main.py
|
||||
# 由于编译问题,把dvadmin_main.py代码复制到本脚本中
|
||||
class Ui_DvadminManager(object):
|
||||
def setupUi(self, DvadminManager):
|
||||
DvadminManager.setObjectName("DvadminManager")
|
||||
DvadminManager.resize(400, 300)
|
||||
DvadminManager.setMinimumSize(QtCore.QSize(400, 300))
|
||||
DvadminManager.setMaximumSize(QtCore.QSize(400, 300))
|
||||
self.label = QtWidgets.QLabel(parent=DvadminManager)
|
||||
self.label.setGeometry(QtCore.QRect(10, 13, 60, 21))
|
||||
self.label.setObjectName("label")
|
||||
self.status_label = QtWidgets.QLabel(parent=DvadminManager)
|
||||
self.status_label.setGeometry(QtCore.QRect(80, 12, 60, 21))
|
||||
self.status_label.setObjectName("status_label")
|
||||
self.start_button = QtWidgets.QPushButton(parent=DvadminManager)
|
||||
self.start_button.setGeometry(QtCore.QRect(210, 10, 81, 26))
|
||||
self.start_button.setObjectName("start_button")
|
||||
self.stop_button = QtWidgets.QPushButton(parent=DvadminManager)
|
||||
self.stop_button.setGeometry(QtCore.QRect(310, 9, 81, 26))
|
||||
self.stop_button.setObjectName("stop_button")
|
||||
self.line = QtWidgets.QFrame(parent=DvadminManager)
|
||||
self.line.setGeometry(QtCore.QRect(0, 40, 401, 16))
|
||||
self.line.setFrameShape(QtWidgets.QFrame.Shape.HLine)
|
||||
self.line.setFrameShadow(QtWidgets.QFrame.Shadow.Sunken)
|
||||
self.line.setObjectName("line")
|
||||
self.log_label = QtWidgets.QTextEdit(parent=DvadminManager)
|
||||
self.log_label.setGeometry(QtCore.QRect(-1, 47, 411, 261))
|
||||
self.log_label.setObjectName("log_label")
|
||||
|
||||
self.retranslateUi(DvadminManager)
|
||||
QtCore.QMetaObject.connectSlotsByName(DvadminManager)
|
||||
|
||||
def retranslateUi(self, DvadminManager):
|
||||
_translate = QtCore.QCoreApplication.translate
|
||||
DvadminManager.setWindowTitle(_translate("DvadminManager", "服务管理器"))
|
||||
self.label.setText(_translate("DvadminManager", "运行状态:"))
|
||||
self.status_label.setText(_translate("DvadminManager", "未启动"))
|
||||
self.start_button.setText(_translate("DvadminManager", "启动服务"))
|
||||
self.stop_button.setText(_translate("DvadminManager", "结束服务"))
|
||||
self.log_label.setHtml(_translate("DvadminManager", ""))
|
||||
|
||||
|
||||
class SelectWorkerSignals(QObject):
|
||||
result = pyqtSignal(str)
|
||||
stop = pyqtSignal(bool)
|
||||
|
||||
|
||||
list_process = []
|
||||
|
||||
|
||||
class ServerWorkerSignals(QObject):
|
||||
result = pyqtSignal(str)
|
||||
|
||||
|
||||
class SelectWorker(QRunnable):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.signals = SelectWorkerSignals()
|
||||
self.is_run = True
|
||||
|
||||
def run(self):
|
||||
# 模拟异步操作
|
||||
import time
|
||||
import psutil
|
||||
while self.is_run:
|
||||
# 遍历进程列表,结束即关闭服务
|
||||
# 获取所有正在运行的进程列表
|
||||
processes = psutil.process_iter()
|
||||
new_list_process = []
|
||||
for process in processes:
|
||||
# 如果进程名称包含"uvicorn",则进行监控
|
||||
if process.pid in list_process:
|
||||
new_list_process.append(process.pid)
|
||||
if list_process and not new_list_process:
|
||||
self.signals.result.emit("异步,进程不存在!")
|
||||
# 等待1秒
|
||||
time.sleep(1)
|
||||
|
||||
def handle_stop_result(self, result):
|
||||
"""
|
||||
异步获取进程结果后执行
|
||||
"""
|
||||
self.is_run = result
|
||||
|
||||
|
||||
class ServerWorker(QRunnable):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.signals = ServerWorkerSignals()
|
||||
|
||||
def run(self):
|
||||
# 启动dvadmin服务
|
||||
import os
|
||||
print("启动dvadmin服务")
|
||||
# import main
|
||||
# main.run()
|
||||
# 定义要执行的命令
|
||||
command = f"./main"
|
||||
if os.sys.platform.startswith('win'):
|
||||
# Windows操作系统
|
||||
print("当前环境是Windows", Path(__file__).resolve().parent)
|
||||
print("当前环境是Windows", Path(__file__).resolve())
|
||||
command = f"{Path(__file__).resolve().parent.parent}/main.exe"
|
||||
elif os.sys.platform.startswith('linux'):
|
||||
# Linux操作系统
|
||||
print("当前环境是Linux")
|
||||
elif os.sys.platform.startswith('darwin'):
|
||||
# macOS操作系统
|
||||
print("当前环境是macOS")
|
||||
command = f"{Path(__file__).resolve().parent.parent}/MacOS/main"
|
||||
else:
|
||||
# 其他操作系统
|
||||
print("当前环境是其他操作系统")
|
||||
self.signals.result.emit(json.dumps({"code": 2001, "msg": command}))
|
||||
global list_process
|
||||
# 使用subprocess.Popen执行命令
|
||||
process = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True)
|
||||
self.signals.result.emit(json.dumps({"code": 2000, "msg": "服务启动成功..."}))
|
||||
# 持续获取输出结果
|
||||
pid = process.pid
|
||||
list_process.append(pid)
|
||||
for line in process.stdout:
|
||||
match = re.search(r'Started server process \[(\d+)\]', line)
|
||||
# 判断是否匹配成功
|
||||
if match:
|
||||
# 获取匹配到的数字
|
||||
number = match.group(1)
|
||||
list_process.append(number)
|
||||
list_process = list(set(list_process))
|
||||
self.signals.result.emit(json.dumps({"code": 2001, "msg": line.replace('\n', '')}))
|
||||
# 等待进程结束
|
||||
process.wait()
|
||||
|
||||
|
||||
class MainWindow(QMainWindow, Ui_DvadminManager):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.setupUi(self)
|
||||
# 开始、结束按钮
|
||||
self.start_button.clicked.connect(self.start_service)
|
||||
self.stop_button.clicked.connect(self.stop_service)
|
||||
# 托盘按钮及事件
|
||||
self.tray_icon = QSystemTrayIcon(QIcon(os.path.join(Path(__file__).resolve().parent, 'static','logo.icns')), self)
|
||||
self.tray_icon.activated.connect(self.tray_icon_activated)
|
||||
self.tray_menu = QMenu(self)
|
||||
self.tray_menu.addAction(self.start_button.text(), self.start_service)
|
||||
self.tray_menu.addAction(self.stop_button.text(), self.stop_service)
|
||||
self.tray_menu.addSeparator()
|
||||
self.tray_menu.addAction("退出", QApplication.quit)
|
||||
self.tray_icon.setContextMenu(self.tray_menu)
|
||||
self.tray_icon.show()
|
||||
|
||||
self.log_label.setReadOnly(True)
|
||||
|
||||
# 信号
|
||||
self.select_worker = SelectWorker()
|
||||
self.select_worker.signals.result.connect(self.handle_select_result)
|
||||
self.select_worker.signals.stop.connect(self.select_worker.handle_stop_result)
|
||||
self.server_worker = ServerWorker()
|
||||
self.server_worker.signals.result.connect(self.handle_server_servers)
|
||||
# 异步
|
||||
self.select_threadpool = QThreadPool()
|
||||
self.server_threadpool = QThreadPool()
|
||||
|
||||
def handle_select_result(self, result):
|
||||
"""
|
||||
异步获取进程结果后执行
|
||||
"""
|
||||
self.append_to_log('服务进程异常,服务已停止...', color='red')
|
||||
global list_process
|
||||
list_process = []
|
||||
self.status_label.setText("已停止")
|
||||
self.status_label.setStyleSheet("color: red;")
|
||||
|
||||
def handle_server_servers(self, result):
|
||||
"""
|
||||
启动服务
|
||||
"""
|
||||
json_result = json.loads(result)
|
||||
if json_result.get('code') == 2000:
|
||||
self.append_to_log(json_result.get('msg'), color='green')
|
||||
# 启动成功打开浏览器
|
||||
url = "http://127.0.0.1:8000/web/"
|
||||
def open_browser_after_delay(url, delay):
|
||||
time.sleep(delay)
|
||||
webbrowser.open(url)
|
||||
|
||||
threading.Thread(target=open_browser_after_delay, args=(url, 3)).start()
|
||||
self.status_label.setText("运行中")
|
||||
self.status_label.setStyleSheet("color: green;")
|
||||
elif json_result.get('code') == 2001:
|
||||
self.append_to_log(json_result.get('msg'))
|
||||
else:
|
||||
self.append_to_log(json_result.get('msg'), color='red')
|
||||
self.status_label.setText("已停止")
|
||||
self.status_label.setStyleSheet("color: red;")
|
||||
|
||||
def append_to_log(self, message, color=None):
|
||||
"""
|
||||
添加日志颜色
|
||||
"""
|
||||
cursor = self.log_label.textCursor()
|
||||
format = QTextCharFormat()
|
||||
if color:
|
||||
if color == "green":
|
||||
format.setForeground(QColor("green"))
|
||||
elif color == "red":
|
||||
format.setForeground(QColor("red"))
|
||||
cursor.movePosition(QTextCursor.MoveOperation.End)
|
||||
cursor.insertText(message, format)
|
||||
cursor.insertBlock()
|
||||
self.log_label.setTextCursor(cursor)
|
||||
self.log_label.ensureCursorVisible()
|
||||
self.log_label.verticalScrollBar().setValue(self.log_label.verticalScrollBar().maximum())
|
||||
|
||||
def start_service(self):
|
||||
global list_process
|
||||
if not list_process:
|
||||
# 启动服务,执行启动脚本
|
||||
self.server_threadpool.clear()
|
||||
self.select_threadpool.clear()
|
||||
self.select_worker.signals.stop.emit(True)
|
||||
self.select_threadpool.startOnReservedThread(self.select_worker.run)
|
||||
self.server_threadpool.startOnReservedThread(self.server_worker.run)
|
||||
self.status_label.setText("正在启动中")
|
||||
self.status_label.setStyleSheet("color: green;")
|
||||
else:
|
||||
self.append_to_log("服务已启动...")
|
||||
|
||||
def stop_service(self):
|
||||
"""
|
||||
停止服务
|
||||
"""
|
||||
global list_process
|
||||
if list_process:
|
||||
for pid in list_process:
|
||||
try:
|
||||
os.kill(int(pid), signal.SIGTERM)
|
||||
except Exception as e:
|
||||
print('Exception', e)
|
||||
pass
|
||||
list_process = []
|
||||
self.select_worker.signals.stop.emit(False)
|
||||
self.select_threadpool.clear()
|
||||
self.server_threadpool.clear()
|
||||
self.status_label.setText("已停止")
|
||||
self.status_label.setStyleSheet("color: red;")
|
||||
self.service_running = False
|
||||
self.append_to_log("服务已停止...", color='red')
|
||||
|
||||
def tray_icon_activated(self, reason):
|
||||
"""
|
||||
托盘激活
|
||||
"""
|
||||
if reason == QSystemTrayIcon.ActivationReason.Trigger:
|
||||
self.showNormal()
|
||||
self.activateWindow()
|
||||
|
||||
def showEvent(self, event):
|
||||
# 自定义的显示事件处理
|
||||
if self.status_label.text() != '已停止':
|
||||
self.select_worker.signals.stop.emit(True)
|
||||
self.select_threadpool.startOnReservedThread(self.select_worker.run)
|
||||
return super().showEvent(event)
|
||||
|
||||
def closeEvent(self, event):
|
||||
event.ignore()
|
||||
self.hide()
|
||||
self.select_worker.signals.stop.emit(False)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
app = QApplication(sys.argv)
|
||||
server = QLocalServer()
|
||||
if server.listen('DVAServer'):
|
||||
app.setQuitOnLastWindowClosed(False)
|
||||
app.setWindowIcon(QIcon(os.path.join(Path(__file__).resolve().parent, 'static','logo.icns')))
|
||||
window = MainWindow()
|
||||
window.show()
|
||||
sys.exit(app.exec())
|
||||
else:
|
||||
message_box = QMessageBox(QMessageBox.Icon.Information, 'Information', '应用程序已在运行')
|
||||
message_box.exec()
|
||||
sys.exit(0) # 如果已有实例在运行,则退出应用程序
|
||||
28
backend/dvadmin3_build/setup.py
Normal file
28
backend/dvadmin3_build/setup.py
Normal file
@@ -0,0 +1,28 @@
|
||||
import setuptools
|
||||
|
||||
with open("README.md", "r") as fh:
|
||||
long_description = fh.read()
|
||||
|
||||
setuptools.setup(
|
||||
name="dvadmin3-build",
|
||||
version="1.0.0",
|
||||
author="DVAdmin",
|
||||
author_email="liqiang@django-vue-admin.com",
|
||||
description="一款适用于django-vue3-admin 编译打包exe、macOS的dmg文件等打包工具。支持加密代码、一键启动项目,无需考虑环境。",
|
||||
long_description=long_description,
|
||||
long_description_content_type="text/markdown",
|
||||
url="https://gitee.com/huge-dream/dvadmin-build",
|
||||
packages=setuptools.find_packages(),
|
||||
python_requires='>=3.7, <4',
|
||||
install_requires=[
|
||||
"pyinstaller>=6.8.0",
|
||||
"PyQt6>=6.4.2",
|
||||
"psutil==6.0.0",
|
||||
],
|
||||
classifiers=[
|
||||
"Programming Language :: Python :: 3",
|
||||
"License :: OSI Approved :: MIT License",
|
||||
"Operating System :: OS Independent",
|
||||
],
|
||||
include_package_data=True
|
||||
)
|
||||
BIN
backend/dvadmin3_build/static/logo.icns
Normal file
BIN
backend/dvadmin3_build/static/logo.icns
Normal file
Binary file not shown.
BIN
backend/dvadmin3_build/windows_build_tools/InstallForgeSetup.exe
Normal file
BIN
backend/dvadmin3_build/windows_build_tools/InstallForgeSetup.exe
Normal file
Binary file not shown.
Binary file not shown.
Reference in New Issue
Block a user