复制代码
import json
import re
from datetime import datetime
from PyQt5.QtCore import *
from PyQt5 import QtWidgets
from PyQt5.QtWidgets import QStyledItemDelegate, QStyle, QGraphicsDropShadowEffect, QAbstractItemView
from DB.UserDB import UserDB
from SecurityCrypto.SecurityCrypto import SecurityCrypto
from MyClass.BLHQT5 import *
# ======================== 全局实例 ========================
crypto = SecurityCrypto(key_salt="海算销售密钥盐", master_password="HaiSuan@2025")
db = UserDB(user_table="product_customer")
# ======================== 自动换行且垂直居中的委托 ========================
class WordWrapDelegate(QStyledItemDelegate):
def paint(self, painter, option, index):
text = index.data(Qt.DisplayRole)
if not text:
return super().paint(painter, option, index)
if option.state & QStyle.State_Selected:
painter.fillRect(option.rect, option.palette.highlight())
painter.save()
doc = QTextDocument()
doc.setDefaultFont(option.font)
doc.setHtml(str(text))
doc.setTextWidth(option.rect.width())
content_height = doc.size().height()
y_offset = option.rect.top() + max(0, (option.rect.height() - content_height) // 2)
painter.translate(option.rect.left(), y_offset)
doc.drawContents(painter)
painter.restore()
def sizeHint(self, option, index):
text = index.data(Qt.DisplayRole)
if not text:
return super().sizeHint(option, index)
doc = QTextDocument()
doc.setDefaultFont(option.font)
doc.setHtml(str(text))
doc.setTextWidth(option.rect.width())
return QSize(int(doc.idealWidth()), int(doc.size().height()))
# ======================== 自定义窗口基类 ========================
class BaseWidget(QWidget):
def __init__(self):
super().__init__()
self.setGraphicsEffect(self.create_shadow())
def create_shadow(self):
shadow = QGraphicsDropShadowEffect()
shadow.setBlurRadius(30)
shadow.setXOffset(0)
shadow.setYOffset(0)
shadow.setColor(QColor(0, 0, 0, 80))
return shadow
# ======================== 登录窗口 ========================
class LoginWindow(BaseWidget):
def __init__(self):
super().__init__()
self.setGraphicsEffect(None)
self.set_theme("暖茶棕")
self.initUI()
def initUI(self):
self.setWindowTitle('企业管理系统 - 登录')
self.setFixedSize(900, 550)
self.setWindowFlags(Qt.FramelessWindowHint)
self.setAttribute(Qt.WA_TranslucentBackground)
main_layout = QHBoxLayout(self)
main_layout.setContentsMargins(0, 0, 0, 0)
main_layout.setSpacing(0)
# 左侧区域(蓝色#4F7EF7)
left_widget = QtWidgets.QWidget()
left_widget.setObjectName("left_widget")
left_layout = QVBoxLayout()
left_layout.setContentsMargins(40, 40, 40, 40)
title_label = QLabel("企业管理系统")
title_label.setAlignment(Qt.AlignHCenter)
title_label.setStyleSheet("font-family: 'Microsoft YaHei'; font-size: 36px; font-weight: bold; color: white;")
subtitle_label = QLabel("精准营销·业绩倍增")
subtitle_label.setAlignment(Qt.AlignHCenter)
subtitle_label.setStyleSheet("font-family: 'Microsoft YaHei'; font-weight: bold; font-size: 24px; color: rgba(255,255,255,0.9); margin-top: 15px;")
slogan_label = QLabel("提升销售效率\n优化客户关系\n加速业务增长")
slogan_label.setAlignment(Qt.AlignCenter)
slogan_label.setStyleSheet("font-family: 'Microsoft YaHei'; font-size: 22px; color: white; margin-top: 60px; line-height: 1.8; background-color: rgba(255,255,255,0.1); border-radius: 10px; padding: 10px;")
support_label = QLabel("技术支持:海算(海南)信息有限公司\n服务热线:15890561786")
support_label.setAlignment(Qt.AlignCenter)
support_label.setStyleSheet("font-family: 'Microsoft YaHei'; font-size: 18px; color: white; margin-top: 40px; line-height: 1.5;")
left_layout.addSpacing(80)
left_layout.addWidget(title_label)
left_layout.addWidget(subtitle_label)
left_layout.addWidget(slogan_label)
left_layout.addWidget(support_label)
left_widget.setLayout(left_layout)
left_widget.setStyleSheet(f"QWidget#left_widget{{background-color: {GlobalTheme.color['primary']}; border-top-left-radius: 15px; border-bottom-left-radius: 15px; }}")
# 右侧区域(白色)
right_widget = QtWidgets.QWidget()
right_widget.setObjectName("right_widget")
right_layout = QVBoxLayout()
right_layout.setContentsMargins(20, 20, 20, 20)
top_bar = QHBoxLayout()
top_bar.addStretch()
close_btn = QToolButton()
close_btn.setText("×")
close_btn.setFixedSize(40, 40)
close_btn.setStyleSheet("QToolButton { font-family: 'Microsoft YaHei'; font-size: 30px; font-weight: bold; color: #999; border: none; background: transparent; } QToolButton:hover { color: #333; background-color: #f5f5f5; border-radius: 10px; }")
close_btn.clicked.connect(self.close)
top_bar.addWidget(close_btn)
version_label = QLabel("海算企业管理系统 V1.01")
version_label.setAlignment(Qt.AlignCenter)
version_label.setStyleSheet("font-family: 'Microsoft YaHei'; font-size: 32px; font-weight: bold; color: #333; margin-top: 10px; margin-bottom: 20px;")
form_layout = QVBoxLayout()
form_layout.setSpacing(25)
username_widget = QtWidgets.QWidget()
username_layout = QVBoxLayout(username_widget)
username_layout.setContentsMargins(50, 0, 50, 0)
username_label = QLabel("账号")
username_label.setStyleSheet("font-family: 'Microsoft YaHei'; font-size: 20px; color: black; margin-bottom: 5px;")
self.username_input = QLineEdit()
self.username_input.setPlaceholderText("请输入账号")
self.username_input.setFixedHeight(45)
# self.username_input.setStyleSheet("QLineEdit { font-family: 'Microsoft YaHei'; font-size: 18px; border: 1px solid #d0d7e5; border-radius: 8px; padding: 0 15px; background-color: white; } QLineEdit:focus { border: 2px solid #4F7EF7; padding: 0 14px; }")
username_layout.addWidget(username_label)
username_layout.addWidget(self.username_input)
password_widget = QtWidgets.QWidget()
password_layout = QVBoxLayout(password_widget)
password_layout.setContentsMargins(50, 0, 50, 0)
password_label = QLabel("密码")
password_label.setStyleSheet("font-family: 'Microsoft YaHei'; font-size: 20px; color: black; margin-bottom: 5px;")
self.password_input = QLineEdit()
self.password_input.setPlaceholderText("请输入密码")
self.password_input.setEchoMode(QLineEdit.Password)
self.password_input.setFixedHeight(45)
# self.password_input.setStyleSheet("QLineEdit { font-family: 'Microsoft YaHei'; font-size: 18px; border: 1px solid #d0d7e5; border-radius: 8px; padding: 0 15px; background-color: white; } QLineEdit:focus { border: 2px solid #4F7EF7; padding: 0 14px; }")
password_layout.addWidget(password_label)
password_layout.addWidget(self.password_input)
form_layout.addWidget(username_widget)
form_layout.addWidget(password_widget)
forgot_layout = QHBoxLayout()
forgot_layout.setContentsMargins(50, 0, 50, 0)
self.forgot_label = QLabel(f"<a href='#' style='color: {GlobalTheme.color['primary']}; text-decoration: none; font-family: \"Microsoft YaHei\"; font-size: 22px;'>忘记密码?</a>")
self.forgot_label.setOpenExternalLinks(False)
self.forgot_label.linkActivated.connect(self.on_forgot_password)
forgot_layout.addStretch()
forgot_layout.addWidget(self.forgot_label)
button_layout = QHBoxLayout()
button_layout.setSpacing(20)
self.login_btn = QPushButton("登录")
self.login_btn.setFixedWidth(120)
self.login_btn.setFixedHeight(45)
self.login_btn.setObjectName("primaryBtn")
self.login_btn.clicked.connect(self.on_login)
self.register_btn = QPushButton("注册")
self.register_btn.setFixedWidth(120)
self.register_btn.setFixedHeight(45)
self.register_btn.setObjectName("secondaryBtn")
self.register_btn.clicked.connect(self.on_register)
button_layout.addStretch()
button_layout.addWidget(self.login_btn)
button_layout.addWidget(self.register_btn)
button_layout.addStretch()
copyright_label = QLabel(f"© {datetime.today().year} Sales Management System. All rights reserved.")
copyright_label.setAlignment(Qt.AlignCenter)
copyright_label.setStyleSheet("font-family: 'Microsoft YaHei'; font-size: 16px; color: #999999; margin-top: 15px;")
right_layout.addLayout(top_bar)
right_layout.addWidget(version_label)
right_layout.addLayout(form_layout)
right_layout.addSpacing(5)
right_layout.addLayout(forgot_layout)
right_layout.addSpacing(20)
right_layout.addLayout(button_layout)
right_layout.addStretch()
right_layout.addWidget(copyright_label)
right_widget.setLayout(right_layout)
right_widget.setStyleSheet("QWidget#right_widget { background-color: #ffffff; border-top-right-radius: 15px; border-bottom-right-radius: 15px; }")
main_layout.addWidget(left_widget, 40)
main_layout.addWidget(right_widget, 60)
self.setStyleSheet("""
QWidget { font-family: "Microsoft YaHei"; }
""")
def on_login(self):
username = self.username_input.text().strip()
password = self.password_input.text().strip()
if not username or not password:
QMessageBox.warning(self, "警告", "请输入账号和密码!")
return
user = db.get_user_by_account(username)
if not user:
QMessageBox.warning(self, "警告", "账号不存在!")
return
if crypto.verify_hash(password, user["customer_pwd"]) != 0:
QMessageBox.warning(self, "警告", "密码错误!")
return
self.close()
self.main_window = MainWindow(account=user["customer_account"], net_name=user["customer_netname"],
phone=crypto.decrypt(user["contact_phone"]), password=password)
self.main_window.show()
def on_register(self):
dialog = RegisterDialog(product_name="云约智能(企业版)", parent=self)
dialog.exec_()
def on_forgot_password(self):
dialog = FindPasswordDialog(customer_account=None, parent=self)
dialog.exec_()
def mousePressEvent(self, event):
if event.button() == Qt.LeftButton:
self.drag_pos = event.globalPos() - self.frameGeometry().topLeft()
event.accept()
def mouseMoveEvent(self, event):
if event.buttons() == Qt.LeftButton:
self.move(event.globalPos() - self.drag_pos)
event.accept()
# ======================== 注册窗口 ========================
class RegisterDialog(QDialog):
def __init__(self, product_name="云约智能(企业版)", parent=None):
super().__init__(parent)
self.product_name = product_name
self.setWindowTitle("用户注册")
self.setFixedWidth(600)
self.custom_index = None
self.previous_index = None
self.ignore_index_change = False
self.init_ui()
def init_ui(self):
main_layout = QVBoxLayout(self)
main_layout.setSpacing(20)
main_layout.setContentsMargins(50, 30, 50, 30)
form_layout = QFormLayout()
form_layout.setLabelAlignment(Qt.AlignRight)
form_layout.setSpacing(15)
self.account_edit = QLineEdit()
self.account_edit.setPlaceholderText("8~12位字母或数字")
form_layout.addRow("用户账号:", self.account_edit)
self.netname_edit = QLineEdit()
self.netname_edit.setPlaceholderText("18个以内汉字、字母或数字")
form_layout.addRow("用户网名:", self.netname_edit)
self.password_edit = QLineEdit()
self.password_edit.setEchoMode(QLineEdit.Password)
self.password_edit.setPlaceholderText("8~18位字母或数字")
form_layout.addRow("登录密码:", self.password_edit)
self.confirm_password_edit = QLineEdit()
self.confirm_password_edit.setEchoMode(QLineEdit.Password)
self.confirm_password_edit.setPlaceholderText("请确认密码")
form_layout.addRow("确认密码:", self.confirm_password_edit)
self.phone_edit = QLineEdit()
self.phone_edit.setPlaceholderText("请输入真实手机号或固定电话")
form_layout.addRow("联系方式:", self.phone_edit)
self.security_combo = QComboBox()
self.security_combo.addItems(["您母亲的姓名?", "您父亲的姓名?", "自定义"])
self.custom_index = 2
self.security_combo.currentIndexChanged.connect(self.on_security_combo_changed)
form_layout.addRow("密保问题:", self.security_combo)
self.security_answer_edit = QLineEdit()
form_layout.addRow("密保答案:", self.security_answer_edit)
self.referral_code_edit = QLineEdit()
self.referral_code_edit.setPlaceholderText("推荐码(选填)")
form_layout.addRow("推荐码:", self.referral_code_edit)
main_layout.addLayout(form_layout)
btn_layout = QHBoxLayout()
btn_layout.setSpacing(20)
self.ok_btn = QPushButton("确 定")
self.ok_btn.setFixedHeight(40)
self.ok_btn.setObjectName("primaryBtn")
self.cancel_btn = QPushButton("取 消")
self.cancel_btn.setFixedHeight(40)
self.cancel_btn.setObjectName("secondaryBtn")
btn_layout.addStretch()
btn_layout.addWidget(self.ok_btn)
btn_layout.addWidget(self.cancel_btn)
btn_layout.addStretch()
main_layout.addLayout(btn_layout)
self.ok_btn.clicked.connect(self.validate_and_register)
self.cancel_btn.clicked.connect(self.reject)
def on_security_combo_changed(self, index):
if self.ignore_index_change:
return
if index == self.custom_index:
new_question, ok = QInputDialog.getText(self, "自定义密保问题", "请输入您的自定义密保问题:")
if ok and new_question.strip():
self.ignore_index_change = True
self.security_combo.insertItem(self.custom_index, new_question.strip())
self.security_combo.setCurrentIndex(self.custom_index)
self.custom_index += 1
self.ignore_index_change = False
else:
self.ignore_index_change = True
self.security_combo.setCurrentIndex(self.previous_index if self.previous_index is not None else 0)
self.ignore_index_change = False
else:
self.previous_index = index
def validate_and_register(self):
account = self.account_edit.text().strip()
netname = self.netname_edit.text().strip()
password = self.password_edit.text().strip()
confirm = self.confirm_password_edit.text().strip()
phone = self.phone_edit.text().strip()
security_q = self.security_combo.currentText().strip()
security_a = self.security_answer_edit.text().strip()
referral = self.referral_code_edit.text().strip()
if not all([account, netname, password, confirm, phone, security_q, security_a]):
QMessageBox.warning(self, "验证失败", "所有带*号为必填项,请填写完整。")
return
if not re.match(r'^[a-zA-Z0-9]{8,12}$', account):
QMessageBox.warning(self, "验证失败", "账号必须为8~12位字母或数字。")
return
if not re.match(r'^[\u4e00-\u9fa5a-zA-Z0-9]{1,18}$', netname):
QMessageBox.warning(self, "验证失败", "网名必须为18个以内汉字、字母或数字。")
return
if not re.match(r'^[a-zA-Z0-9]{8,18}$', password):
QMessageBox.warning(self, "验证失败", "密码必须为8~18位字母或数字。")
return
if password != confirm:
QMessageBox.warning(self, "验证失败", "两次输入的密码不一致。")
return
if not re.match(r'^(\d{11}|\d{3,4}-?\d{7,8})$', phone):
QMessageBox.warning(self, "验证失败", "请输入正确的手机号或固定电话。")
return
if db.is_account_exist(account):
QMessageBox.warning(self, "验证失败", "该账号已存在。")
return
hash_phone = crypto.generate_query_hash(phone)
if db.is_phone_exist(self.product_name, hash_phone):
QMessageBox.warning(self, "验证失败", "该联系方式已在该产品下注册。")
return
if referral:
if not self.verify_referral(referral):
QMessageBox.warning(self, "验证失败", "推荐码无效。")
return
hashed_pwd = crypto.generate_hash(password)
encrypted_phone = crypto.encrypt(phone)
now = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
security_info = json.dumps([{"pwd_question": security_q, "pwd_answer": security_a, "operator": account, "operate_time": now}])
sales_info = json.dumps([{"sales_account": account, "parent_sales": "F008216", "sales_director": "F008888", "operator": account, "operate_time": now}])
register_ip, register_area = self.get_ip_and_area()
register_info = json.dumps([{"register_time": now, "register_area": register_area, "register_ip": register_ip}])
try:
db.register_user(
self.product_name, account, hashed_pwd, netname, encrypted_phone, hash_phone,
now, None, security_info, sales_info, register_info
)
QMessageBox.information(self, "成功", "注册成功!")
self.accept()
except Exception as e:
QMessageBox.warning(self, "失败", f"注册失败:{e}")
def verify_referral(self, code, default=1):
return default == 1
def get_ip_and_area(self):
return "192.168.1.100", "本地测试"
# ======================== 我的资料窗口 ========================
class ProfileDialog(QDialog):
def __init__(self, account, net_name, phone, password, parent=None):
super().__init__(parent)
self.account = account
self.original_net_name = net_name
self.original_phone = phone
self.password = password
self.setWindowTitle("我的资料")
self.setFixedWidth(500)
self.init_ui()
self.load_data()
def init_ui(self):
layout = QVBoxLayout(self)
layout.setSpacing(20)
layout.setContentsMargins(80, 30, 80, 30)
form_layout = QFormLayout()
form_layout.setLabelAlignment(Qt.AlignRight)
form_layout.setSpacing(15)
self.account_edit = QLineEdit()
self.account_edit.setReadOnly(True)
self.netname_edit = QLineEdit()
self.phone_edit = QLineEdit()
form_layout.addRow("用户账号:", self.account_edit)
form_layout.addRow("用户网名:", self.netname_edit)
form_layout.addRow("联系方式:", self.phone_edit)
layout.addLayout(form_layout)
btn_layout = QHBoxLayout()
btn_layout.setSpacing(20)
self.save_btn = QPushButton("保 存")
self.save_btn.setFixedHeight(38)
self.save_btn.setObjectName("primaryBtn")
self.cancel_btn = QPushButton("取 消")
self.cancel_btn.setFixedHeight(38)
self.cancel_btn.setObjectName("secondaryBtn")
btn_layout.addStretch()
btn_layout.addWidget(self.save_btn)
btn_layout.addWidget(self.cancel_btn)
btn_layout.addStretch()
layout.addLayout(btn_layout)
self.save_btn.clicked.connect(self.on_save)
self.cancel_btn.clicked.connect(self.reject)
def load_data(self):
self.account_edit.setText(self.account)
self.netname_edit.setText(self.original_net_name)
self.phone_edit.setText(self.original_phone)
def on_save(self):
new_netname = self.netname_edit.text().strip()
new_phone = self.phone_edit.text().strip()
if not new_netname or not new_phone:
QMessageBox.warning(self, "警告", "网名和联系方式不能为空!")
self.load_data()
return
if not re.match(r'^[\u4e00-\u9fa5a-zA-Z0-9]{1,18}$', new_netname):
QMessageBox.warning(self, "警告", "网名格式不正确。")
return
if not re.match(r'^(\d{11}|\d{3,4}-?\d{7,8})$', new_phone):
QMessageBox.warning(self, "警告", "联系方式格式不正确。")
return
if new_netname == self.original_net_name and new_phone == self.original_phone:
self.accept()
return
hash_phone = crypto.generate_query_hash(new_phone)
if db.is_phone_exist_exclude_account("云约智能(企业版)", hash_phone, self.account):
QMessageBox.warning(self, "警告", "该联系方式已与其他账号绑定。")
return
encrypted_phone = crypto.encrypt(new_phone)
now = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
db.update_profile(self.account, new_netname, encrypted_phone, hash_phone)
old_history = db.get_history_info(self.account)
history_list = json.loads(old_history) if old_history else []
history_entry = {
"phone": crypto.encrypt(self.original_phone),
"password": crypto.generate_hash(self.password),
"operate_time": now
}
history_list.append(history_entry)
db.set_history_info(self.account, json.dumps(history_list))
QMessageBox.information(self, "成功", "资料修改成功!")
self.accept()
# ======================== 修改密码窗口 ========================
class ChangePasswordDialog(QDialog):
def __init__(self, account, phone, parent=None):
super().__init__(parent)
self.account = account
self.phone = phone
self.setWindowTitle("修改密码")
self.setFixedWidth(500)
self.init_ui()
def init_ui(self):
layout = QVBoxLayout(self)
layout.setSpacing(20)
layout.setContentsMargins(60, 30, 60, 30)
form_layout = QFormLayout()
form_layout.setLabelAlignment(Qt.AlignRight)
form_layout.setSpacing(20)
self.old_pwd_edit = QLineEdit()
self.old_pwd_edit.setEchoMode(QLineEdit.Password)
self.old_pwd_edit.setPlaceholderText("请输入原密码")
self.new_pwd_edit = QLineEdit()
self.new_pwd_edit.setEchoMode(QLineEdit.Password)
self.new_pwd_edit.setPlaceholderText("请输入新密码")
self.confirm_pwd_edit = QLineEdit()
self.confirm_pwd_edit.setEchoMode(QLineEdit.Password)
self.confirm_pwd_edit.setPlaceholderText("请再次输入新密码")
form_layout.addRow("原密码:", self.old_pwd_edit)
form_layout.addRow("新密码:", self.new_pwd_edit)
form_layout.addRow("确认密码:", self.confirm_pwd_edit)
layout.addLayout(form_layout)
btn_layout = QHBoxLayout()
btn_layout.setSpacing(20)
self.ok_btn = QPushButton("确 定")
self.ok_btn.setFixedHeight(38)
self.ok_btn.setObjectName("primaryBtn")
self.cancel_btn = QPushButton("取 消")
self.cancel_btn.setFixedHeight(38)
self.cancel_btn.setObjectName("secondaryBtn")
btn_layout.addStretch()
btn_layout.addWidget(self.ok_btn)
btn_layout.addWidget(self.cancel_btn)
btn_layout.addStretch()
layout.addLayout(btn_layout)
self.ok_btn.clicked.connect(self.on_ok)
self.cancel_btn.clicked.connect(self.reject)
def on_ok(self):
old_pwd = self.old_pwd_edit.text().strip()
new_pwd = self.new_pwd_edit.text().strip()
confirm_pwd = self.confirm_pwd_edit.text().strip()
if not all([old_pwd, new_pwd, confirm_pwd]):
QMessageBox.warning(self, "警告", "所有字段不能为空!")
return
stored_hash = db.get_password_hash(self.account)
if not stored_hash or crypto.verify_hash(old_pwd, stored_hash) != 0:
QMessageBox.warning(self, "错误", "原密码不正确!")
return
if not re.match(r'^[a-zA-Z0-9]{8,18}$', new_pwd):
QMessageBox.warning(self, "警告", "新密码必须为8~18位字母或数字。")
return
if new_pwd != confirm_pwd:
QMessageBox.warning(self, "警告", "两次输入的新密码不一致!")
return
if new_pwd == old_pwd:
QMessageBox.warning(self, "警告", "新密码不能与原密码相同!")
return
now = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
hashed_new = crypto.generate_hash(new_pwd)
db.update_password(self.account, hashed_new)
old_history = db.get_history_info(self.account)
history_list = json.loads(old_history) if old_history else []
encrypted_phone = db.get_encrypted_phone(self.account)
history_entry = {
"phone": encrypted_phone,
"password": stored_hash,
"operate_time": now
}
history_list.append(history_entry)
db.set_history_info(self.account, json.dumps(history_list))
QMessageBox.information(self, "成功", "密码修改成功!")
self.accept()
# ======================== 设置新密码弹窗 ========================
class SetNewPasswordDialog(QDialog):
def __init__(self, parent=None):
super().__init__(parent)
self.setWindowTitle("设置新密码")
self.setFixedWidth(460)
self.init_ui()
self.new_password = None
def init_ui(self):
layout = QVBoxLayout(self)
layout.setSpacing(20)
layout.setContentsMargins(50, 30, 50, 30)
form_layout = QFormLayout()
form_layout.setLabelAlignment(Qt.AlignRight)
form_layout.setSpacing(20)
self.new_pwd_edit = QLineEdit()
self.new_pwd_edit.setEchoMode(QLineEdit.Password)
self.new_pwd_edit.setPlaceholderText("请输入新密码")
self.confirm_pwd_edit = QLineEdit()
self.confirm_pwd_edit.setEchoMode(QLineEdit.Password)
self.confirm_pwd_edit.setPlaceholderText("请再次输入新密码")
form_layout.addRow("新密码:", self.new_pwd_edit)
form_layout.addRow("确认密码:", self.confirm_pwd_edit)
layout.addLayout(form_layout)
btn_layout = QHBoxLayout()
btn_layout.setSpacing(20)
self.ok_btn = QPushButton("确 定")
self.ok_btn.setFixedHeight(38)
self.ok_btn.setObjectName("primaryBtn")
self.cancel_btn = QPushButton("取 消")
self.cancel_btn.setFixedHeight(38)
self.cancel_btn.setObjectName("secondaryBtn")
btn_layout.addStretch()
btn_layout.addWidget(self.ok_btn)
btn_layout.addWidget(self.cancel_btn)
btn_layout.addStretch()
layout.addLayout(btn_layout)
self.ok_btn.clicked.connect(self.validate)
self.cancel_btn.clicked.connect(self.reject)
def validate(self):
new_pwd = self.new_pwd_edit.text().strip()
confirm_pwd = self.confirm_pwd_edit.text().strip()
if not new_pwd:
QMessageBox.warning(self, "警告", "新密码不能为空!")
return
if new_pwd != confirm_pwd:
QMessageBox.warning(self, "警告", "两次输入的密码不一致!")
return
self.new_password = new_pwd
self.accept()
# ======================== 快速找回密码窗口 ========================
class FindPasswordDialog(QDialog):
def __init__(self, customer_account=None, parent=None):
super().__init__(parent)
self.customer_account = customer_account
self.setWindowTitle("快速找回密码")
self.setFixedWidth(500)
self.matched_user = None
self.init_ui()
def init_ui(self):
layout = QVBoxLayout(self)
layout.setSpacing(20)
layout.setContentsMargins(60, 30, 60, 30)
hint_label = QLabel("请至少填写用户账号或联系方式其中一项")
layout.addWidget(hint_label)
form_layout = QFormLayout()
form_layout.setLabelAlignment(Qt.AlignRight)
form_layout.setSpacing(20)
self.account_edit = QLineEdit()
self.account_edit.setPlaceholderText("请输入用户账号")
self.phone_edit = QLineEdit()
self.phone_edit.setPlaceholderText("请输入联系方式")
self.question_edit = QLineEdit()
self.question_edit.setReadOnly(True)
self.question_edit.setPlaceholderText("验证通过后自动显示")
self.answer_edit = QLineEdit()
self.answer_edit.setPlaceholderText("请输入密保答案")
form_layout.addRow("用户账号:", self.account_edit)
form_layout.addRow("联系方式:", self.phone_edit)
form_layout.addRow("密保问题:", self.question_edit)
form_layout.addRow("密码答案:", self.answer_edit)
layout.addLayout(form_layout)
btn_layout = QHBoxLayout()
btn_layout.setSpacing(20)
self.ok_btn = QPushButton("确 定")
self.ok_btn.setFixedHeight(40)
self.ok_btn.setObjectName("primaryBtn")
self.cancel_btn = QPushButton("取 消")
self.cancel_btn.setFixedHeight(40)
self.cancel_btn.setObjectName("secondaryBtn")
btn_layout.addStretch()
btn_layout.addWidget(self.ok_btn)
btn_layout.addWidget(self.cancel_btn)
btn_layout.addStretch()
layout.addLayout(btn_layout)
self.account_edit.editingFinished.connect(self.auto_verify)
self.phone_edit.editingFinished.connect(self.auto_verify)
self.ok_btn.clicked.connect(self.on_ok)
self.cancel_btn.clicked.connect(self.reject)
def auto_verify(self):
account = self.account_edit.text().strip()
phone = self.phone_edit.text().strip()
if not account and not phone:
self.question_edit.clear()
self.matched_user = None
return
hash_phone = crypto.generate_query_hash(phone) if phone else None
user = db.find_user_by_account_or_phone(account, hash_phone)
if not user:
QMessageBox.warning(self, "提示", "未找到匹配的账号信息。")
self.question_edit.clear()
self.matched_user = None
return
self.matched_user = user
self.account_edit.setText(user["customer_account"])
self.account_edit.setReadOnly(True)
decrypted_phone = crypto.decrypt(user["contact_phone"])
self.phone_edit.setText(decrypted_phone)
self.phone_edit.setReadOnly(True)
security_info = json.loads(user["security_info"])
if security_info:
self.question_edit.setText(security_info[-1]["pwd_question"])
else:
self.question_edit.clear()
def on_ok(self):
if not self.matched_user:
QMessageBox.warning(self, "警告", "请先完成身份验证。")
return
answer = self.answer_edit.text().strip()
if not answer:
QMessageBox.warning(self, "警告", "请输入密保答案。")
return
security_info = json.loads(self.matched_user["security_info"])
if not security_info or security_info[-1]["pwd_answer"] != answer:
QMessageBox.warning(self, "错误", "密保答案不正确。")
return
dlg = SetNewPasswordDialog(self)
if dlg.exec_() == QDialog.Accepted:
new_pwd = dlg.new_password
now = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
hashed_new = crypto.generate_hash(new_pwd)
db.update_password(self.matched_user["customer_account"], hashed_new)
old_phone = self.matched_user["contact_phone"]
old_pwd_hash = self.matched_user["customer_pwd"]
old_history = db.get_history_info(self.matched_user["customer_account"])
history_list = json.loads(old_history) if old_history else []
history_list.append({
"phone": old_phone,
"password": old_pwd_hash,
"operate_time": now
})
db.set_history_info(self.matched_user["customer_account"], json.dumps(history_list))
QMessageBox.information(self, "成功", "新密码设置成功!")
self.accept()
# ======================== 完善团队信息窗口 ========================
class TeamInfoDialog(QDialog):
DEPARTMENTS = ["工程部", "技术部", "财务部", "行政部", "物资部", "未分组"]
def __init__(self, account, parent=None):
super().__init__(parent)
self.account = account
self.setWindowTitle("完善团队信息")
self.setFixedWidth(500)
self.init_ui()
def init_ui(self):
layout = QVBoxLayout(self)
layout.setSpacing(20)
layout.setContentsMargins(60, 30, 60, 30)
form_layout = QFormLayout()
form_layout.setLabelAlignment(Qt.AlignRight)
form_layout.setSpacing(15)
self.code_edit = QLineEdit()
self.code_edit.setPlaceholderText("12位大写字母(不含I,O)+数字")
self.name_edit = QLineEdit()
self.phone_edit = QLineEdit()
self.role_combo = QComboBox()
self.role_combo.addItems(["", "团长", "管理员", "组长", "队员"])
self.depart_combo = QComboBox()
self.depart_combo.addItem("")
self.depart_combo.addItems(self.DEPARTMENTS)
form_layout.addRow("编码:", self.code_edit)
form_layout.addRow("姓名:", self.name_edit)
form_layout.addRow("电话:", self.phone_edit)
form_layout.addRow("权限:", self.role_combo)
form_layout.addRow("部门:", self.depart_combo)
layout.addLayout(form_layout)
btn_layout = QHBoxLayout()
btn_layout.setSpacing(20)
self.ok_btn = QPushButton("确 定")
self.ok_btn.setFixedHeight(38)
self.ok_btn.setObjectName("primaryBtn")
self.cancel_btn = QPushButton("取 消")
self.cancel_btn.setFixedHeight(38)
self.cancel_btn.setObjectName("secondaryBtn")
btn_layout.addStretch()
btn_layout.addWidget(self.ok_btn)
btn_layout.addWidget(self.cancel_btn)
btn_layout.addStretch()
layout.addLayout(btn_layout)
self.ok_btn.clicked.connect(self.on_save)
self.cancel_btn.clicked.connect(self.reject)
def on_save(self):
code = self.code_edit.text().strip()
name = self.name_edit.text().strip()
phone = self.phone_edit.text().strip()
role = self.role_combo.currentText().strip()
depart = self.depart_combo.currentText().strip()
if not all([code, name, phone, role, depart]):
QMessageBox.warning(self, "警告", "所有字段不能为空。")
return
if not re.match(r'^[A-HJ-NP-Z0-9]{12}$', code):
QMessageBox.warning(self, "警告", "成员编码必须为12位大写字母(不含I,O)和数字。")
return
team_info = {
"member_code": code,
"member_name": name,
"member_phone": phone,
"member_role": role,
"member_depart": depart,
"group_departs": self.DEPARTMENTS
}
db.update_team_info(self.account, json.dumps(team_info))
QMessageBox.information(self, "成功", "团队信息更新成功!")
self.accept()
# ======================== 服务记录窗口 ========================
class ServiceRecordDialog(QDialog):
def __init__(self, account, service_record=None, parent=None):
super().__init__(parent)
self.account = account
self.service_record = service_record if service_record else []
self.setWindowTitle("服务记录")
self.resize(700, 500)
self.init_ui()
self.load_data()
def init_ui(self):
layout = QVBoxLayout(self)
layout.setContentsMargins(20, 20, 20, 20)
self.table = QTableWidget()
self.table.setColumnCount(4)
self.table.setHorizontalHeaderLabels(["序号", "内容", "操作人", "时间"])
self.table.verticalHeader().setVisible(False)
self.table.setSelectionBehavior(QAbstractItemView.SelectRows)
self.table.setSelectionMode(QAbstractItemView.SingleSelection)
self.table.setEditTriggers(QAbstractItemView.NoEditTriggers)
self.table.setColumnWidth(0, 80)
self.table.setColumnWidth(1, 280)
self.table.setColumnWidth(2, 150)
self.table.setColumnWidth(3, 180)
self.word_delegate = WordWrapDelegate()
self.table.setItemDelegateForColumn(1, self.word_delegate)
layout.addWidget(self.table)
btn_layout = QHBoxLayout()
btn_layout.addStretch()
self.add_btn = QPushButton("添加记录")
self.add_btn.setObjectName("primaryBtn")
self.close_btn = QPushButton("关闭")
self.close_btn.setObjectName("secondaryBtn")
btn_layout.addWidget(self.add_btn)
btn_layout.addWidget(self.close_btn)
layout.addLayout(btn_layout)
self.add_btn.clicked.connect(self.add_record)
self.close_btn.clicked.connect(self.close)
def load_data(self):
self.table.setRowCount(len(self.service_record))
for row, record in enumerate(self.service_record):
item0 = QTableWidgetItem(str(row + 1))
item0.setTextAlignment(Qt.AlignCenter)
self.table.setItem(row, 0, item0)
item1 = QTableWidgetItem(record.get("content", ""))
item1.setTextAlignment(Qt.AlignLeft | Qt.AlignVCenter)
self.table.setItem(row, 1, item1)
item2 = QTableWidgetItem(record.get("operator", ""))
item2.setTextAlignment(Qt.AlignCenter)
self.table.setItem(row, 2, item2)
item3 = QTableWidgetItem(record.get("operate_time", ""))
item3.setTextAlignment(Qt.AlignCenter)
self.table.setItem(row, 3, item3)
self.table.resizeRowsToContents()
def add_record(self):
dlg = AddRecordDialog(self.account, "添加服务记录", parent=self)
if dlg.exec_() == QDialog.Accepted:
new_record = dlg.get_record()
if new_record:
self.service_record.append(new_record)
db.set_service_records(self.account, self.service_record)
self.load_data()
QMessageBox.information(self, "成功", "服务记录添加成功!")
class AddRecordDialog(QDialog):
def __init__(self, account, title="添加记录", parent=None):
super().__init__(parent)
self.account = account
self.setWindowTitle(title)
self.setFixedSize(420, 320)
self.record = None
self.init_ui()
def init_ui(self):
layout = QVBoxLayout(self)
layout.setContentsMargins(30, 20, 30, 20)
layout.setSpacing(15)
title_label = QLabel("新增记录")
layout.addWidget(title_label)
self.text_edit = QTextEdit()
self.text_edit.setPlaceholderText("请输入记录内容...")
layout.addWidget(self.text_edit)
btn_layout = QHBoxLayout()
btn_layout.setSpacing(20)
self.ok_btn = QPushButton("确 定")
self.ok_btn.setObjectName("primaryBtn")
self.cancel_btn = QPushButton("取 消")
self.cancel_btn.setObjectName("secondaryBtn")
btn_layout.addStretch()
btn_layout.addWidget(self.ok_btn)
btn_layout.addWidget(self.cancel_btn)
layout.addLayout(btn_layout)
self.ok_btn.clicked.connect(self.validate)
self.cancel_btn.clicked.connect(self.reject)
def validate(self):
content = self.text_edit.toPlainText().strip()
if not content:
QMessageBox.warning(self, "警告", "内容不能为空!")
return
now = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
self.record = {"content": content, "operator": self.account, "operate_time": now}
self.accept()
def get_record(self):
return self.record
# ======================== 客户评价窗口 ========================
class CustomerEvaluateDialog(QDialog):
def __init__(self, account, parent=None):
super().__init__(parent)
self.account = account
self.setWindowTitle("客户评价")
self.setFixedSize(420, 320)
self.init_ui()
def init_ui(self):
layout = QVBoxLayout(self)
layout.setContentsMargins(30, 20, 30, 20)
layout.setSpacing(15)
title_label = QLabel("写下您的评价")
layout.addWidget(title_label)
self.text_edit = QTextEdit()
self.text_edit.setPlaceholderText("请在此输入评价内容...")
layout.addWidget(self.text_edit)
btn_layout = QHBoxLayout()
btn_layout.setSpacing(20)
self.ok_btn = QPushButton("提 交")
self.ok_btn.setObjectName("primaryBtn")
self.cancel_btn = QPushButton("取 消")
self.cancel_btn.setObjectName("secondaryBtn")
btn_layout.addStretch()
btn_layout.addWidget(self.ok_btn)
btn_layout.addWidget(self.cancel_btn)
layout.addLayout(btn_layout)
self.ok_btn.clicked.connect(self.on_ok)
self.cancel_btn.clicked.connect(self.reject)
def on_ok(self):
content = self.text_edit.toPlainText().strip()
if not content:
QMessageBox.warning(self, "警告", "评价内容不能为空!")
return
now = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
evaluate_entry = {"content": content, "evaluate_time": now}
evaluate_list = db.get_customer_evaluate(self.account)
evaluate_list.append(evaluate_entry)
try:
db.set_customer_evaluate(self.account, evaluate_list)
QMessageBox.information(self, "成功", "评价提交成功!")
self.accept()
except Exception as e:
QMessageBox.warning(self, "失败", f"提交失败:{e}")
# ======================== 客户评价历史窗口 ========================
class EvaluateHistoryDialog(QDialog):
def __init__(self, customer_evaluate=None, parent=None):
super().__init__(parent)
self.evaluate_data = customer_evaluate if customer_evaluate else []
self.setWindowTitle("客户评价历史记录")
self.resize(600, 400)
self.init_ui()
self.load_data()
def init_ui(self):
layout = QVBoxLayout(self)
layout.setContentsMargins(20, 20, 20, 20)
self.table = QTableWidget()
self.table.setColumnCount(3)
self.table.setHorizontalHeaderLabels(["序号", "内容", "时间"])
self.table.verticalHeader().setVisible(False)
self.table.setSelectionBehavior(QAbstractItemView.SelectRows)
self.table.setSelectionMode(QAbstractItemView.SingleSelection)
self.table.setEditTriggers(QAbstractItemView.NoEditTriggers)
self.table.setColumnWidth(0, 80)
self.table.setColumnWidth(1, 300)
self.table.setColumnWidth(2, 180)
self.word_delegate = WordWrapDelegate()
self.table.setItemDelegateForColumn(1, self.word_delegate)
layout.addWidget(self.table)
btn_layout = QHBoxLayout()
btn_layout.addStretch()
self.close_btn = QPushButton("关 闭")
self.close_btn.setObjectName("secondaryBtn")
btn_layout.addWidget(self.close_btn)
layout.addLayout(btn_layout)
self.close_btn.clicked.connect(self.close)
def load_data(self):
self.table.setRowCount(len(self.evaluate_data))
for row, record in enumerate(self.evaluate_data):
item0 = QTableWidgetItem(str(row + 1))
item0.setTextAlignment(Qt.AlignCenter)
self.table.setItem(row, 0, item0)
item1 = QTableWidgetItem(record.get("content", ""))
item1.setTextAlignment(Qt.AlignLeft | Qt.AlignVCenter)
self.table.setItem(row, 1, item1)
item2 = QTableWidgetItem(record.get("evaluate_time", ""))
item2.setTextAlignment(Qt.AlignCenter)
self.table.setItem(row, 2, item2)
self.table.resizeRowsToContents()
# ======================== 客户备注窗口 ========================
class CustomerRemarkDialog(QDialog):
def __init__(self, account, customer_remark=None, parent=None):
super().__init__(parent)
self.account = account
self.remark_data = customer_remark if customer_remark else []
self.setWindowTitle("客户备注")
self.resize(700, 500)
self.init_ui()
self.load_data()
def init_ui(self):
layout = QVBoxLayout(self)
layout.setContentsMargins(20, 20, 20, 20)
self.table = QTableWidget()
self.table.setColumnCount(4)
self.table.setHorizontalHeaderLabels(["序号", "内容", "操作人", "时间"])
self.table.verticalHeader().setVisible(False)
self.table.setSelectionBehavior(QAbstractItemView.SelectRows)
self.table.setSelectionMode(QAbstractItemView.SingleSelection)
self.table.setEditTriggers(QAbstractItemView.NoEditTriggers)
self.table.setColumnWidth(0, 80)
self.table.setColumnWidth(1, 280)
self.table.setColumnWidth(2, 150)
self.table.setColumnWidth(3, 180)
self.word_delegate = WordWrapDelegate()
self.table.setItemDelegateForColumn(1, self.word_delegate)
layout.addWidget(self.table)
btn_layout = QHBoxLayout()
btn_layout.addStretch()
self.add_btn = QPushButton("添加备注")
self.add_btn.setObjectName("primaryBtn")
self.close_btn = QPushButton("关 闭")
self.close_btn.setObjectName("secondaryBtn")
btn_layout.addWidget(self.add_btn)
btn_layout.addWidget(self.close_btn)
layout.addLayout(btn_layout)
self.add_btn.clicked.connect(self.add_remark)
self.close_btn.clicked.connect(self.close)
def load_data(self):
self.table.setRowCount(len(self.remark_data))
for row, record in enumerate(self.remark_data):
item0 = QTableWidgetItem(str(row + 1))
item0.setTextAlignment(Qt.AlignCenter)
self.table.setItem(row, 0, item0)
item1 = QTableWidgetItem(record.get("content", ""))
item1.setTextAlignment(Qt.AlignLeft | Qt.AlignVCenter)
self.table.setItem(row, 1, item1)
item2 = QTableWidgetItem(record.get("operator", ""))
item2.setTextAlignment(Qt.AlignCenter)
self.table.setItem(row, 2, item2)
item3 = QTableWidgetItem(record.get("operate_time", ""))
item3.setTextAlignment(Qt.AlignCenter)
self.table.setItem(row, 3, item3)
self.table.resizeRowsToContents()
def add_remark(self):
dlg = AddRecordDialog(self.account, "添加备注", parent=self)
if dlg.exec_() == QDialog.Accepted:
new_record = dlg.get_record()
if new_record:
self.remark_data.append(new_record)
db.set_customer_remark(self.account, self.remark_data)
self.load_data()
QMessageBox.information(self, "成功", "备注添加成功!")
# ======================== 授权窗口 ========================
class AuthorizationDialog(QDialog):
def __init__(self, account=None, parent=None):
super().__init__(parent)
self.setWindowTitle("授权管理")
self.setFixedSize(400, 200)
self.init_ui()
def init_ui(self):
layout = QVBoxLayout(self)
layout.setContentsMargins(30, 30, 30, 30)
self.auth_edit = QLineEdit()
self.auth_edit.setPlaceholderText("请输入32位授权码(大写字母不含I,O和数字)")
layout.addWidget(self.auth_edit)
btn_layout = QHBoxLayout()
btn_layout.setSpacing(20)
self.ok_btn = QPushButton("确 定")
self.ok_btn.setObjectName("primaryBtn")
self.cancel_btn = QPushButton("取 消")
self.cancel_btn.setObjectName("secondaryBtn")
btn_layout.addStretch()
btn_layout.addWidget(self.ok_btn)
btn_layout.addWidget(self.cancel_btn)
layout.addLayout(btn_layout)
self.ok_btn.clicked.connect(self.on_ok)
self.cancel_btn.clicked.connect(self.reject)
def on_ok(self):
auth_code = self.auth_edit.text().strip()
if not auth_code:
QMessageBox.warning(self, "警告", "授权码不能为空!")
return
if not re.match(r'^[A-HJ-NP-Z0-9]{32}$', auth_code):
QMessageBox.warning(self, "警告", "授权码格式不正确,应为32位大写字母(不含I,O)和数字。")
return
QMessageBox.information(self, "成功", "恭喜,授权成功!")
self.accept()
# ======================== 主窗口 ========================
class MainWindow(QWidget):
def __init__(self, account, net_name, phone, password):
super().__init__()
self.account = account
self.net_name = net_name
self.phone = phone
self.password = password
self.set_theme("中国红")
self.setWindowTitle("我的主窗口")
self.resize(1000, 700)
self.init_ui()
def init_ui(self):
layout = QVBoxLayout(self)
layout.setSpacing(20)
layout.setContentsMargins(30, 30, 30, 30)
title = QLabel(f"欢迎,{self.net_name}")
title.setAlignment(Qt.AlignCenter)
layout.addWidget(title)
grid = QGridLayout()
grid.setSpacing(15)
buttons_info = [
("我的资料", self.open_profile),
("修改密码", self.open_change_password),
("找回密码", self.open_find_password),
("团队信息", self.open_team_info),
("服务记录", self.open_service_record),
("客户评价", self.open_customer_evaluate),
("评价历史", self.open_evaluate_history),
("客户备注", self.open_customer_remark),
("授权管理", self.open_authorization),
]
for i in range(21):
buttons_info.append((f"备用按钮{i+1}", lambda _, name=f"备用按钮{i+1}": self.show_spare(name)))
row, col = 0, 0
for text, func in buttons_info:
btn = QPushButton(text)
btn.setFixedSize(160, 50)
btn.setObjectName("primaryBtn" if "备用" not in text else "secondaryBtn")
btn.clicked.connect(func)
grid.addWidget(btn, row, col)
col += 1
if col >= 5:
col = 0
row += 1
layout.addLayout(grid)
def open_profile(self):
dlg = ProfileDialog(self.account, self.net_name, self.phone, self.password)
dlg.exec_()
def open_change_password(self):
dlg = ChangePasswordDialog(self.account, self.phone)
dlg.exec_()
def open_find_password(self):
dlg = FindPasswordDialog(customer_account=self.account)
dlg.exec_()
def open_team_info(self):
dlg = TeamInfoDialog(self.account)
dlg.exec_()
def open_service_record(self):
data = db.get_service_records(self.account)
dlg = ServiceRecordDialog(self.account, data)
dlg.exec_()
def open_customer_evaluate(self):
dlg = CustomerEvaluateDialog(self.account)
dlg.exec_()
def open_evaluate_history(self):
data = db.get_customer_evaluate(self.account)
dlg = EvaluateHistoryDialog(data)
dlg.exec_()
def open_customer_remark(self):
data = db.get_customer_remark(self.account)
dlg = CustomerRemarkDialog(self.account, data)
dlg.exec_()
def open_authorization(self):
dlg = AuthorizationDialog(self.account)
dlg.exec_()
def show_spare(self, name):
QMessageBox.information(self, "备用按钮", f"您点击了:{name}")
# ======================== 程序入口 ========================
if __name__ == "__main__":
config = {
'applicationName': 'PyQt5 主题化控件库演示',
'organizationName': 'MyCompany',
'organizationDomain': 'mycompany.com',
'applicationVersion': '2.0',
'windowIcon': 'fox.ico',
'font': {
'family': 'Microsoft YaHei',
'pointSize': 9,
'weight': QFont.Normal
},
'translatorPath': 'qt_zh_CN.qm'
}
app = QApplication(sys.argv,config)
app.setFont(QFont("Microsoft YaHei", 10))
login = LoginWindow()
login.show()
sys.exit(app.exec_())
python
复制代码
import sys
import re
import json
import base64
import hashlib
import os
import pymysql
from datetime import datetime
from cryptography.fernet import Fernet
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC
from PyQt5.QtWidgets import (
QApplication, QWidget, QDialog, QLabel, QLineEdit, QPushButton,
QVBoxLayout, QHBoxLayout, QFormLayout, QComboBox, QMessageBox,
QTableView, QHeaderView, QAbstractItemView, QTextEdit, QToolButton,
QGraphicsDropShadowEffect, QTableWidget, QTableWidgetItem, QGridLayout,
QInputDialog, QStyledItemDelegate, QStyle
)
from PyQt5.QtCore import Qt, QSize
from PyQt5.QtGui import QFont, QColor, QPixmap, QTextDocument
from SecurityCrypto.SecurityCrypto import SecurityCrypto
from LhPyQt5.LhQt5 import *
# ======================== 数据库操作类(UserDB) ========================
class UserDB:
"""用户数据库操作类,通过参数配置数据库连接和表名"""
def __init__(self, sql_dict, user_table="product_customer"):
"""
:param sql_dict: pymysql 连接参数字典(不含 autocommit)
:param user_table: 操作的主表名,默认 product_customer
"""
self.sql_dict = sql_dict
self.user_table = user_table
# ---- 原始 execute_mysql 函数作为静态方法 ----
@staticmethod
def execute_mysql(
sql_dict,
sql_text,
params=None,
fetch=False,
executemany=False,
dictionary=True,
autocommit=True,
transaction=False
):
conn = None
cursor = None
try:
# 尝试建立数据库连接
try:
conn = pymysql.connect(**sql_dict)
except pymysql.err.OperationalError:
# 网络断开或无法连接时返回 None
return None
# 设置游标类型
cursor_type = pymysql.cursors.DictCursor if dictionary else pymysql.cursors.Cursor
cursor = conn.cursor(cursor_type)
# 事务控制逻辑
if transaction:
conn.begin()
# 执行核心逻辑
try:
if executemany:
cursor.executemany(sql_text, params)
else:
cursor.execute(sql_text, params)
except pymysql.err.OperationalError:
# 执行过程中网络断开,回滚后返回 None
if conn:
conn.rollback()
return None
# 结果处理
if fetch or sql_text.strip().upper().startswith('SELECT'):
result = cursor.fetchall()
elif sql_text.strip().upper().startswith('INSERT'):
result = cursor.lastrowid
else:
result = cursor.rowcount
# 提交控制
if autocommit and not conn.get_autocommit():
conn.commit()
return result
except pymysql.MySQLError as e:
if conn:
conn.rollback()
raise RuntimeError(f"数据库操作失败: {e}") from e
finally:
if cursor:
cursor.close()
if conn:
conn.close()
# ---------- 用户相关 ----------
def get_user_by_account(self, account):
"""根据账号查询用户完整记录,返回 dict 或 None"""
try:
sql = f"SELECT * FROM {self.user_table} WHERE customer_account = %s"
users = UserDB.execute_mysql(self.sql_dict, sql, (account,), fetch=True)
return users[0] if users else None
except:
return None
def is_account_exist(self, account):
"""判断账号是否存在"""
try:
sql = f"SELECT id FROM {self.user_table} WHERE customer_account = %s"
res = UserDB.execute_mysql(self.sql_dict, sql, (account,), fetch=True)
return res is not None and len(res) > 0
except:
return False
def is_phone_exist(self, product_name, hash_phone):
"""判断联系方式是否已存在(同一产品下)"""
try:
sql = f"SELECT id FROM {self.user_table} WHERE product_name = %s AND hash_phone = %s"
res = UserDB.execute_mysql(self.sql_dict, sql, (product_name, hash_phone), fetch=True)
return res is not None and len(res) > 0
except:
return False
def is_phone_exist_exclude_account(self, product_name, hash_phone, account):
"""判断联系方式是否与其他账号绑定(排除自身)"""
try:
sql = f"SELECT id FROM {self.user_table} WHERE product_name = %s AND hash_phone = %s AND customer_account != %s"
res = UserDB.execute_mysql(self.sql_dict, sql, (product_name, hash_phone, account), fetch=True)
return res is not None and len(res) > 0
except:
return False
def register_user(self, product_name, account, hashed_pwd, netname, encrypted_phone, hash_phone,
right_date, customer_status, security_info, sales_info, register_info):
"""注册新用户"""
sql = f"""
INSERT INTO {self.user_table}
(product_name, customer_account, customer_pwd, customer_netname, contact_phone, hash_phone,
right_date, customer_status, security_info, sales_info, register_info)
VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s)
"""
params = (product_name, account, hashed_pwd, netname, encrypted_phone, hash_phone,
right_date, customer_status, security_info, sales_info, register_info)
UserDB.execute_mysql(self.sql_dict, sql, params)
# ---------- 资料修改 ----------
def update_profile(self, account, netname, encrypted_phone, hash_phone):
"""更新网名和联系方式"""
sql = f"UPDATE {self.user_table} SET customer_netname = %s, contact_phone = %s, hash_phone = %s WHERE customer_account = %s"
UserDB.execute_mysql(self.sql_dict, sql, (netname, encrypted_phone, hash_phone, account))
def get_history_info(self, account):
"""获取 history_info 字段"""
sql = f"SELECT history_info FROM {self.user_table} WHERE customer_account = %s"
res = UserDB.execute_mysql(self.sql_dict, sql, (account,), fetch=True)
if res and res[0]["history_info"]:
return res[0]["history_info"]
return None
def set_history_info(self, account, history_json):
"""设置 history_info 字段"""
sql = f"UPDATE {self.user_table} SET history_info = %s WHERE customer_account = %s"
UserDB.execute_mysql(self.sql_dict, sql, (history_json, account))
def get_password_hash(self, account):
"""获取当前密码哈希值"""
sql = f"SELECT customer_pwd FROM {self.user_table} WHERE customer_account = %s"
res = UserDB.execute_mysql(self.sql_dict, sql, (account,), fetch=True)
return res[0]["customer_pwd"] if res else None
def update_password(self, account, new_hash):
"""修改密码"""
sql = f"UPDATE {self.user_table} SET customer_pwd = %s WHERE customer_account = %s"
UserDB.execute_mysql(self.sql_dict, sql, (new_hash, account))
def get_encrypted_phone(self, account):
"""获取加密的手机号(用于 history 记录)"""
sql = f"SELECT contact_phone FROM {self.user_table} WHERE customer_account = %s"
res = UserDB.execute_mysql(self.sql_dict, sql, (account,), fetch=True)
return res[0]["contact_phone"] if res else None
# ---------- 团队信息 ----------
def update_team_info(self, account, team_info_json):
"""更新团队信息"""
sql = f"UPDATE {self.user_table} SET team_info = %s WHERE customer_account = %s"
UserDB.execute_mysql(self.sql_dict, sql, (team_info_json, account))
# ---------- 服务记录 ----------
def get_service_records(self, account):
"""获取服务记录,返回列表"""
sql = f"SELECT service_record FROM {self.user_table} WHERE customer_account = %s"
res = UserDB.execute_mysql(self.sql_dict, sql, (account,), fetch=True)
if res and res[0]["service_record"]:
return json.loads(res[0]["service_record"])
return []
def set_service_records(self, account, records):
"""更新服务记录"""
sql = f"UPDATE {self.user_table} SET service_record = %s WHERE customer_account = %s"
UserDB.execute_mysql(self.sql_dict, sql, (json.dumps(records), account))
# ---------- 客户评价 ----------
def get_customer_evaluate(self, account):
"""获取客户评价列表"""
sql = f"SELECT customer_evaluate FROM {self.user_table} WHERE customer_account = %s"
res = UserDB.execute_mysql(self.sql_dict, sql, (account,), fetch=True)
if res and res[0]["customer_evaluate"]:
return json.loads(res[0]["customer_evaluate"])
return []
def set_customer_evaluate(self, account, records):
"""更新客户评价"""
sql = f"UPDATE {self.user_table} SET customer_evaluate = %s WHERE customer_account = %s"
UserDB.execute_mysql(self.sql_dict, sql, (json.dumps(records), account))
# ---------- 客户备注 ----------
def get_customer_remark(self, account):
"""获取客户备注列表"""
sql = f"SELECT customer_remark FROM {self.user_table} WHERE customer_account = %s"
res = UserDB.execute_mysql(self.sql_dict, sql, (account,), fetch=True)
if res and res[0]["customer_remark"]:
return json.loads(res[0]["customer_remark"])
return []
def set_customer_remark(self, account, records):
"""更新客户备注"""
sql = f"UPDATE {self.user_table} SET customer_remark = %s WHERE customer_account = %s"
UserDB.execute_mysql(self.sql_dict, sql, (json.dumps(records), account))
# ---------- 找回密码 ----------
def find_user_by_account_or_phone(self, account, hash_phone):
"""通过账号或联系方式查找用户,返回完整记录或 None"""
conditions = []
params = []
if account:
conditions.append("customer_account = %s")
params.append(account)
if hash_phone:
conditions.append("hash_phone = %s")
params.append(hash_phone)
if not conditions:
return None
sql = f"SELECT * FROM {self.user_table} WHERE {' OR '.join(conditions)}"
users = UserDB.execute_mysql(self.sql_dict, sql, tuple(params), fetch=True)
return users[0] if users else None
# ======================== 全局实例 ========================
crypto = SecurityCrypto(key_salt="海算销售密钥盐", master_password="HaiSuan@2025")
db = UserDB(
sql_dict={
"host": "localhost",
"user": "root",
"password": "root",
"db": "my_sale_system",
"charset": "utf8mb4"
},
user_table="product_customer"
)
# ======================== 自动换行且垂直居中的委托 ========================
class WordWrapDelegate(QStyledItemDelegate):
def paint(self, painter, option, index):
text = index.data(Qt.DisplayRole)
if not text:
return super().paint(painter, option, index)
if option.state & QStyle.State_Selected:
painter.fillRect(option.rect, option.palette.highlight())
painter.save()
doc = QTextDocument()
doc.setDefaultFont(option.font)
doc.setHtml(str(text))
doc.setTextWidth(option.rect.width())
content_height = doc.size().height()
y_offset = option.rect.top() + max(0, (option.rect.height() - content_height) // 2)
painter.translate(option.rect.left(), y_offset)
doc.drawContents(painter)
painter.restore()
def sizeHint(self, option, index):
text = index.data(Qt.DisplayRole)
if not text:
return super().sizeHint(option, index)
doc = QTextDocument()
doc.setDefaultFont(option.font)
doc.setHtml(str(text))
doc.setTextWidth(option.rect.width())
return QSize(int(doc.idealWidth()), int(doc.size().height()))
# ======================== 自定义窗口基类 ========================
class BaseWidget(QWidget):
def __init__(self):
super().__init__()
self.setGraphicsEffect(self.create_shadow())
def create_shadow(self):
shadow = QGraphicsDropShadowEffect()
shadow.setBlurRadius(30)
shadow.setXOffset(0)
shadow.setYOffset(0)
shadow.setColor(QColor(0, 0, 0, 80))
return shadow
# ======================== 登录窗口 ========================
class LoginWindow(BaseWidget):
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
self.setWindowTitle('销售管理系统 - 登录')
self.setFixedSize(900, 550)
self.setWindowFlags(Qt.FramelessWindowHint)
self.setAttribute(Qt.WA_TranslucentBackground)
container = QWidget(self)
container.setObjectName("container")
container.setGeometry(0, 0, 900, 550)
container.setStyleSheet("QWidget#container { background-color: transparent; border-radius: 15px; }")
main_layout = QHBoxLayout(container)
main_layout.setContentsMargins(0, 0, 0, 0)
main_layout.setSpacing(0)
# 左侧区域(蓝色#4F7EF7)
left_widget = QWidget()
left_widget.setObjectName("left_widget")
left_layout = QVBoxLayout()
left_layout.setContentsMargins(40, 40, 40, 40)
title_label = QLabel("销售管理系统")
title_label.setAlignment(Qt.AlignHCenter)
title_label.setStyleSheet("font-family: 'Microsoft YaHei'; font-size: 36px; font-weight: bold; color: white;")
subtitle_label = QLabel("精准营销·业绩倍增")
subtitle_label.setAlignment(Qt.AlignHCenter)
subtitle_label.setStyleSheet("font-family: 'Microsoft YaHei'; font-weight: bold; font-size: 24px; color: rgba(255,255,255,0.9); margin-top: 15px;")
slogan_label = QLabel("提升销售效率\n优化客户关系\n加速业务增长")
slogan_label.setAlignment(Qt.AlignCenter)
slogan_label.setStyleSheet("font-family: 'Microsoft YaHei'; font-size: 22px; color: white; margin-top: 60px; line-height: 1.8; background-color: rgba(255,255,255,0.1); border-radius: 10px; padding: 10px;")
support_label = QLabel("技术支持:海算(海南)信息有限公司\n服务热线:15890561786")
support_label.setAlignment(Qt.AlignCenter)
support_label.setStyleSheet("font-family: 'Microsoft YaHei'; font-size: 18px; color: white; margin-top: 40px; line-height: 1.5;")
left_layout.addSpacing(80)
left_layout.addWidget(title_label)
left_layout.addWidget(subtitle_label)
left_layout.addWidget(slogan_label)
left_layout.addWidget(support_label)
left_widget.setLayout(left_layout)
left_widget.setStyleSheet("QWidget#left_widget { background-color: #4F7EF7; border-top-left-radius: 15px; border-bottom-left-radius: 15px; }")
# 右侧区域(白色)
right_widget = QWidget()
right_widget.setObjectName("right_widget")
right_layout = QVBoxLayout()
right_layout.setContentsMargins(20, 20, 20, 20)
top_bar = QHBoxLayout()
top_bar.addStretch()
close_btn = QToolButton()
close_btn.setText("×")
close_btn.setFixedSize(40, 40)
close_btn.setStyleSheet("QToolButton { font-family: 'Microsoft YaHei'; font-size: 30px; font-weight: bold; color: #999; border: none; background: transparent; } QToolButton:hover { color: #333; background-color: #f5f5f5; border-radius: 10px; }")
close_btn.clicked.connect(self.close)
top_bar.addWidget(close_btn)
version_label = QLabel("海算销售管理系统 V1.01")
version_label.setAlignment(Qt.AlignCenter)
version_label.setStyleSheet("font-family: 'Microsoft YaHei'; font-size: 32px; font-weight: bold; color: #333; margin-top: 10px; margin-bottom: 20px;")
form_layout = QVBoxLayout()
form_layout.setSpacing(25)
username_widget = QWidget()
username_layout = QVBoxLayout(username_widget)
username_layout.setContentsMargins(50, 0, 50, 0)
username_label = QLabel("账号")
username_label.setStyleSheet("font-family: 'Microsoft YaHei'; font-size: 20px; color: black; margin-bottom: 5px;")
self.username_input = QLineEdit()
self.username_input.setPlaceholderText("请输入账号")
self.username_input.setFixedHeight(45)
self.username_input.setStyleSheet("QLineEdit { font-family: 'Microsoft YaHei'; font-size: 18px; border: 1px solid #d0d7e5; border-radius: 8px; padding: 0 15px; background-color: white; } QLineEdit:focus { border: 2px solid #4F7EF7; padding: 0 14px; }")
username_layout.addWidget(username_label)
username_layout.addWidget(self.username_input)
password_widget = QWidget()
password_layout = QVBoxLayout(password_widget)
password_layout.setContentsMargins(50, 0, 50, 0)
password_label = QLabel("密码")
password_label.setStyleSheet("font-family: 'Microsoft YaHei'; font-size: 20px; color: black; margin-bottom: 5px;")
self.password_input = QLineEdit()
self.password_input.setPlaceholderText("请输入密码")
self.password_input.setEchoMode(QLineEdit.Password)
self.password_input.setFixedHeight(45)
self.password_input.setStyleSheet("QLineEdit { font-family: 'Microsoft YaHei'; font-size: 18px; border: 1px solid #d0d7e5; border-radius: 8px; padding: 0 15px; background-color: white; } QLineEdit:focus { border: 2px solid #4F7EF7; padding: 0 14px; }")
password_layout.addWidget(password_label)
password_layout.addWidget(self.password_input)
form_layout.addWidget(username_widget)
form_layout.addWidget(password_widget)
forgot_layout = QHBoxLayout()
forgot_layout.setContentsMargins(50, 0, 50, 0)
self.forgot_label = QLabel("<a href='#' style='color: #4F7EF7; text-decoration: none; font-family: \"Microsoft YaHei\"; font-size: 22px;'>忘记密码?</a>")
self.forgot_label.setOpenExternalLinks(False)
self.forgot_label.linkActivated.connect(self.on_forgot_password)
forgot_layout.addStretch()
forgot_layout.addWidget(self.forgot_label)
button_layout = QHBoxLayout()
button_layout.setSpacing(20)
self.login_btn = QPushButton("登录")
self.login_btn.setFixedWidth(120)
self.login_btn.setFixedHeight(45)
self.login_btn.setObjectName("primaryBtn")
self.login_btn.clicked.connect(self.on_login)
self.register_btn = QPushButton("注册")
self.register_btn.setFixedWidth(120)
self.register_btn.setFixedHeight(45)
self.register_btn.setObjectName("secondaryBtn")
self.register_btn.clicked.connect(self.on_register)
button_layout.addStretch()
button_layout.addWidget(self.login_btn)
button_layout.addWidget(self.register_btn)
button_layout.addStretch()
copyright_label = QLabel(f"© {datetime.today().year} Sales Management System. All rights reserved.")
copyright_label.setAlignment(Qt.AlignCenter)
copyright_label.setStyleSheet("font-family: 'Microsoft YaHei'; font-size: 16px; color: #999999; margin-top: 15px;")
right_layout.addLayout(top_bar)
right_layout.addWidget(version_label)
right_layout.addLayout(form_layout)
right_layout.addSpacing(5)
right_layout.addLayout(forgot_layout)
right_layout.addSpacing(20)
right_layout.addLayout(button_layout)
right_layout.addStretch()
right_layout.addWidget(copyright_label)
right_widget.setLayout(right_layout)
right_widget.setStyleSheet("QWidget#right_widget { background-color: #ffffff; border-top-right-radius: 15px; border-bottom-right-radius: 15px; }")
main_layout.addWidget(left_widget, 40)
main_layout.addWidget(right_widget, 60)
self.setStyleSheet("""
QWidget { font-family: "Microsoft YaHei"; }
QPushButton#primaryBtn { background: qlineargradient(x1:0, y1:0, x2:0, y2:1, stop:0 #6b9af8, stop:1 #4F7EF7); color: white; border: none; border-radius: 8px; font-size: 18px; font-weight: bold; }
QPushButton#primaryBtn:hover { background: qlineargradient(x1:0, y1:0, x2:0, y2:1, stop:0 #7ba8ff, stop:1 #5f8eff); }
QPushButton#primaryBtn:pressed { background: #4F7EF7; }
QPushButton#secondaryBtn { background: white; color: #4F7EF7; border: 2px solid #4F7EF7; border-radius: 8px; font-size: 18px; font-weight: bold; }
QPushButton#secondaryBtn:hover { background: #eaf1ff; }
QPushButton#secondaryBtn:pressed { background: #d4e2ff; }
""")
def on_login(self):
username = self.username_input.text().strip()
password = self.password_input.text().strip()
if not username or not password:
QMessageBox.warning(self, "警告", "请输入账号和密码!")
return
user = db.get_user_by_account(username)
if not user:
QMessageBox.warning(self, "警告", "账号不存在!")
return
if crypto.verify_hash(password, user["customer_pwd"]) != 0:
QMessageBox.warning(self, "警告", "密码错误!")
return
self.close()
self.main_window = MainWindow(account=user["customer_account"], net_name=user["customer_netname"],
phone=crypto.decrypt(user["contact_phone"]), password=password)
self.main_window.show()
def on_register(self):
dialog = RegisterDialog(product_name="云约智能(企业版)", parent=self)
dialog.exec_()
def on_forgot_password(self):
dialog = FindPasswordDialog(customer_account=None, parent=self)
dialog.exec_()
def mousePressEvent(self, event):
if event.button() == Qt.LeftButton:
self.drag_pos = event.globalPos() - self.frameGeometry().topLeft()
event.accept()
def mouseMoveEvent(self, event):
if event.buttons() == Qt.LeftButton:
self.move(event.globalPos() - self.drag_pos)
event.accept()
# ======================== 注册窗口 ========================
class RegisterDialog(QDialog):
def __init__(self, product_name="云约智能(企业版)", parent=None):
super().__init__(parent)
self.product_name = product_name
self.setWindowTitle("用户注册")
self.setFixedWidth(600)
self.custom_index = None
self.previous_index = None
self.ignore_index_change = False
self.init_ui()
self.apply_styles()
def init_ui(self):
main_layout = QVBoxLayout(self)
main_layout.setSpacing(20)
main_layout.setContentsMargins(50, 30, 50, 30)
form_layout = QFormLayout()
form_layout.setLabelAlignment(Qt.AlignRight)
form_layout.setSpacing(15)
self.account_edit = QLineEdit()
self.account_edit.setPlaceholderText("8~12位字母或数字")
form_layout.addRow("用户账号:", self.account_edit)
self.netname_edit = QLineEdit()
self.netname_edit.setPlaceholderText("18个以内汉字、字母或数字")
form_layout.addRow("用户网名:", self.netname_edit)
self.password_edit = QLineEdit()
self.password_edit.setEchoMode(QLineEdit.Password)
self.password_edit.setPlaceholderText("8~18位字母或数字")
form_layout.addRow("登录密码:", self.password_edit)
self.confirm_password_edit = QLineEdit()
self.confirm_password_edit.setEchoMode(QLineEdit.Password)
self.confirm_password_edit.setPlaceholderText("请确认密码")
form_layout.addRow("确认密码:", self.confirm_password_edit)
self.phone_edit = QLineEdit()
self.phone_edit.setPlaceholderText("请输入真实手机号或固定电话")
form_layout.addRow("联系方式:", self.phone_edit)
self.security_combo = QComboBox()
self.security_combo.addItems(["您母亲的姓名?", "您父亲的姓名?", "自定义"])
self.custom_index = 2
self.security_combo.currentIndexChanged.connect(self.on_security_combo_changed)
form_layout.addRow("密保问题:", self.security_combo)
self.security_answer_edit = QLineEdit()
form_layout.addRow("密保答案:", self.security_answer_edit)
self.referral_code_edit = QLineEdit()
self.referral_code_edit.setPlaceholderText("推荐码(选填)")
form_layout.addRow("推荐码:", self.referral_code_edit)
main_layout.addLayout(form_layout)
btn_layout = QHBoxLayout()
btn_layout.setSpacing(20)
self.ok_btn = QPushButton("确 定")
self.ok_btn.setFixedHeight(40)
self.ok_btn.setObjectName("primaryBtn")
self.cancel_btn = QPushButton("取 消")
self.cancel_btn.setFixedHeight(40)
self.cancel_btn.setObjectName("secondaryBtn")
btn_layout.addStretch()
btn_layout.addWidget(self.ok_btn)
btn_layout.addWidget(self.cancel_btn)
btn_layout.addStretch()
main_layout.addLayout(btn_layout)
self.ok_btn.clicked.connect(self.validate_and_register)
self.cancel_btn.clicked.connect(self.reject)
def apply_styles(self):
self.setStyleSheet("""
QDialog { background-color: #f8faff; font-family: "Microsoft YaHei"; }
QLabel { color: #333; font-size: 18px; }
QLineEdit { border: 1px solid #d0d7e5; border-radius: 5px; padding: 8px 12px; background: white; font-size: 18px; }
QLineEdit:focus { border: 2px solid #4F7EF7; padding: 7px 11px; }
QComboBox { border: 1px solid #d0d7e5; border-radius: 5px; padding: 8px 12px; background: white; font-size: 18px; }
QPushButton#primaryBtn { background: qlineargradient(x1:0, y1:0, x2:0, y2:1, stop:0 #6b9af8, stop:1 #4F7EF7); color: white; border: none; border-radius: 5px; font-size: 18px; font-weight: bold; min-width: 100px; }
QPushButton#primaryBtn:hover { background: qlineargradient(x1:0, y1:0, x2:0, y2:1, stop:0 #7ba8ff, stop:1 #5f8eff); }
QPushButton#secondaryBtn { background: white; color: #4F7EF7; border: 2px solid #4F7EF7; border-radius: 5px; font-size: 18px; font-weight: bold; min-width: 100px; }
QPushButton#secondaryBtn:hover { background: #eaf1ff; }
""")
def on_security_combo_changed(self, index):
if self.ignore_index_change:
return
if index == self.custom_index:
new_question, ok = QInputDialog.getText(self, "自定义密保问题", "请输入您的自定义密保问题:")
if ok and new_question.strip():
self.ignore_index_change = True
self.security_combo.insertItem(self.custom_index, new_question.strip())
self.security_combo.setCurrentIndex(self.custom_index)
self.custom_index += 1
self.ignore_index_change = False
else:
self.ignore_index_change = True
self.security_combo.setCurrentIndex(self.previous_index if self.previous_index is not None else 0)
self.ignore_index_change = False
else:
self.previous_index = index
def validate_and_register(self):
account = self.account_edit.text().strip()
netname = self.netname_edit.text().strip()
password = self.password_edit.text().strip()
confirm = self.confirm_password_edit.text().strip()
phone = self.phone_edit.text().strip()
security_q = self.security_combo.currentText().strip()
security_a = self.security_answer_edit.text().strip()
referral = self.referral_code_edit.text().strip()
if not all([account, netname, password, confirm, phone, security_q, security_a]):
QMessageBox.warning(self, "验证失败", "所有带*号为必填项,请填写完整。")
return
if not re.match(r'^[a-zA-Z0-9]{8,12}$', account):
QMessageBox.warning(self, "验证失败", "账号必须为8~12位字母或数字。")
return
if not re.match(r'^[\u4e00-\u9fa5a-zA-Z0-9]{1,18}$', netname):
QMessageBox.warning(self, "验证失败", "网名必须为18个以内汉字、字母或数字。")
return
if not re.match(r'^[a-zA-Z0-9]{8,18}$', password):
QMessageBox.warning(self, "验证失败", "密码必须为8~18位字母或数字。")
return
if password != confirm:
QMessageBox.warning(self, "验证失败", "两次输入的密码不一致。")
return
if not re.match(r'^(\d{11}|\d{3,4}-?\d{7,8})$', phone):
QMessageBox.warning(self, "验证失败", "请输入正确的手机号或固定电话。")
return
if db.is_account_exist(account):
QMessageBox.warning(self, "验证失败", "该账号已存在。")
return
hash_phone = crypto.generate_query_hash(phone)
if db.is_phone_exist(self.product_name, hash_phone):
QMessageBox.warning(self, "验证失败", "该联系方式已在该产品下注册。")
return
if referral:
if not self.verify_referral(referral):
QMessageBox.warning(self, "验证失败", "推荐码无效。")
return
hashed_pwd = crypto.generate_hash(password)
encrypted_phone = crypto.encrypt(phone)
now = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
security_info = json.dumps([{"pwd_question": security_q, "pwd_answer": security_a, "operator": account, "operate_time": now}])
sales_info = json.dumps([{"sales_account": account, "parent_sales": "F008216", "sales_director": "F008888", "operator": account, "operate_time": now}])
register_ip, register_area = self.get_ip_and_area()
register_info = json.dumps([{"register_time": now, "register_area": register_area, "register_ip": register_ip}])
try:
db.register_user(
self.product_name, account, hashed_pwd, netname, encrypted_phone, hash_phone,
now, None, security_info, sales_info, register_info
)
QMessageBox.information(self, "成功", "注册成功!")
self.accept()
except Exception as e:
QMessageBox.warning(self, "失败", f"注册失败:{e}")
def verify_referral(self, code, default=1):
return default == 1
def get_ip_and_area(self):
return "192.168.1.100", "本地测试"
# ======================== 我的资料窗口 ========================
class ProfileDialog(QDialog):
def __init__(self, account, net_name, phone, password, parent=None):
super().__init__(parent)
self.account = account
self.original_net_name = net_name
self.original_phone = phone
self.password = password
self.setWindowTitle("我的资料")
self.setFixedWidth(500)
self.init_ui()
self.apply_styles()
self.load_data()
def init_ui(self):
layout = QVBoxLayout(self)
layout.setSpacing(20)
layout.setContentsMargins(80, 30, 80, 30)
form_layout = QFormLayout()
form_layout.setLabelAlignment(Qt.AlignRight)
form_layout.setSpacing(15)
self.account_edit = QLineEdit()
self.account_edit.setReadOnly(True)
self.netname_edit = QLineEdit()
self.phone_edit = QLineEdit()
form_layout.addRow("用户账号:", self.account_edit)
form_layout.addRow("用户网名:", self.netname_edit)
form_layout.addRow("联系方式:", self.phone_edit)
layout.addLayout(form_layout)
btn_layout = QHBoxLayout()
btn_layout.setSpacing(20)
self.save_btn = QPushButton("保 存")
self.save_btn.setFixedHeight(38)
self.save_btn.setObjectName("primaryBtn")
self.cancel_btn = QPushButton("取 消")
self.cancel_btn.setFixedHeight(38)
self.cancel_btn.setObjectName("secondaryBtn")
btn_layout.addStretch()
btn_layout.addWidget(self.save_btn)
btn_layout.addWidget(self.cancel_btn)
btn_layout.addStretch()
layout.addLayout(btn_layout)
self.save_btn.clicked.connect(self.on_save)
self.cancel_btn.clicked.connect(self.reject)
def apply_styles(self):
self.setStyleSheet("""
QLabel { color: #333; font-size: 18px; }
QLineEdit { border: 1px solid #d0d7e5; border-radius: 5px; padding: 8px 12px; background: white; font-size: 18px; }
QLineEdit:focus { border: 2px solid #4F7EF7; padding: 7px 11px; }
QLineEdit[readOnly="true"] { background: #f0f3f9; color: #666; }
QPushButton#primaryBtn { background: qlineargradient(x1:0, y1:0, x2:0, y2:1, stop:0 #6b9af8, stop:1 #4F7EF7); color: white; border: none; border-radius: 5px; font-size: 18px; font-weight: bold; min-width: 100px; }
QPushButton#primaryBtn:hover { background: qlineargradient(x1:0, y1:0, x2:0, y2:1, stop:0 #7ba8ff, stop:1 #5f8eff); }
QPushButton#secondaryBtn { background: white; color: #4F7EF7; border: 2px solid #4F7EF7; border-radius: 5px; font-size: 18px; font-weight: bold; min-width: 100px; }
QPushButton#secondaryBtn:hover { background: #eaf1ff; }
""")
def load_data(self):
self.account_edit.setText(self.account)
self.netname_edit.setText(self.original_net_name)
self.phone_edit.setText(self.original_phone)
def on_save(self):
new_netname = self.netname_edit.text().strip()
new_phone = self.phone_edit.text().strip()
if not new_netname or not new_phone:
QMessageBox.warning(self, "警告", "网名和联系方式不能为空!")
self.load_data()
return
if not re.match(r'^[\u4e00-\u9fa5a-zA-Z0-9]{1,18}$', new_netname):
QMessageBox.warning(self, "警告", "网名格式不正确。")
return
if not re.match(r'^(\d{11}|\d{3,4}-?\d{7,8})$', new_phone):
QMessageBox.warning(self, "警告", "联系方式格式不正确。")
return
if new_netname == self.original_net_name and new_phone == self.original_phone:
self.accept()
return
hash_phone = crypto.generate_query_hash(new_phone)
if db.is_phone_exist_exclude_account("云约智能(企业版)", hash_phone, self.account):
QMessageBox.warning(self, "警告", "该联系方式已与其他账号绑定。")
return
encrypted_phone = crypto.encrypt(new_phone)
now = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
db.update_profile(self.account, new_netname, encrypted_phone, hash_phone)
old_history = db.get_history_info(self.account)
history_list = json.loads(old_history) if old_history else []
history_entry = {
"phone": crypto.encrypt(self.original_phone),
"password": crypto.generate_hash(self.password),
"operate_time": now
}
history_list.append(history_entry)
db.set_history_info(self.account, json.dumps(history_list))
QMessageBox.information(self, "成功", "资料修改成功!")
self.accept()
# ======================== 修改密码窗口 ========================
class ChangePasswordDialog(QDialog):
def __init__(self, account, phone, parent=None):
super().__init__(parent)
self.account = account
self.phone = phone
self.setWindowTitle("修改密码")
self.setFixedWidth(500)
self.init_ui()
self.apply_styles()
def init_ui(self):
layout = QVBoxLayout(self)
layout.setSpacing(20)
layout.setContentsMargins(60, 30, 60, 30)
form_layout = QFormLayout()
form_layout.setLabelAlignment(Qt.AlignRight)
form_layout.setSpacing(20)
self.old_pwd_edit = QLineEdit()
self.old_pwd_edit.setEchoMode(QLineEdit.Password)
self.old_pwd_edit.setPlaceholderText("请输入原密码")
self.new_pwd_edit = QLineEdit()
self.new_pwd_edit.setEchoMode(QLineEdit.Password)
self.new_pwd_edit.setPlaceholderText("请输入新密码")
self.confirm_pwd_edit = QLineEdit()
self.confirm_pwd_edit.setEchoMode(QLineEdit.Password)
self.confirm_pwd_edit.setPlaceholderText("请再次输入新密码")
form_layout.addRow("原密码:", self.old_pwd_edit)
form_layout.addRow("新密码:", self.new_pwd_edit)
form_layout.addRow("确认密码:", self.confirm_pwd_edit)
layout.addLayout(form_layout)
btn_layout = QHBoxLayout()
btn_layout.setSpacing(20)
self.ok_btn = QPushButton("确 定")
self.ok_btn.setFixedHeight(38)
self.ok_btn.setObjectName("primaryBtn")
self.cancel_btn = QPushButton("取 消")
self.cancel_btn.setFixedHeight(38)
self.cancel_btn.setObjectName("secondaryBtn")
btn_layout.addStretch()
btn_layout.addWidget(self.ok_btn)
btn_layout.addWidget(self.cancel_btn)
btn_layout.addStretch()
layout.addLayout(btn_layout)
self.ok_btn.clicked.connect(self.on_ok)
self.cancel_btn.clicked.connect(self.reject)
def apply_styles(self):
self.setStyleSheet("""
QLabel { color: #333; font-size: 18px; }
QLineEdit { border: 1px solid #d0d7e5; border-radius: 5px; padding: 8px 12px; background: white; font-size: 18px; }
QLineEdit:focus { border: 2px solid #4F7EF7; padding: 7px 11px; }
QPushButton#primaryBtn { background: qlineargradient(x1:0, y1:0, x2:0, y2:1, stop:0 #6b9af8, stop:1 #4F7EF7); color: white; border: none; border-radius: 5px; font-size: 18px; font-weight: bold; min-width: 100px; }
QPushButton#primaryBtn:hover { background: qlineargradient(x1:0, y1:0, x2:0, y2:1, stop:0 #7ba8ff, stop:1 #5f8eff); }
QPushButton#secondaryBtn { background: white; color: #4F7EF7; border: 2px solid #4F7EF7; border-radius: 5px; font-size: 18px; font-weight: bold; min-width: 100px; }
QPushButton#secondaryBtn:hover { background: #eaf1ff; }
""")
def on_ok(self):
old_pwd = self.old_pwd_edit.text().strip()
new_pwd = self.new_pwd_edit.text().strip()
confirm_pwd = self.confirm_pwd_edit.text().strip()
if not all([old_pwd, new_pwd, confirm_pwd]):
QMessageBox.warning(self, "警告", "所有字段不能为空!")
return
stored_hash = db.get_password_hash(self.account)
if not stored_hash or crypto.verify_hash(old_pwd, stored_hash) != 0:
QMessageBox.warning(self, "错误", "原密码不正确!")
return
if not re.match(r'^[a-zA-Z0-9]{8,18}$', new_pwd):
QMessageBox.warning(self, "警告", "新密码必须为8~18位字母或数字。")
return
if new_pwd != confirm_pwd:
QMessageBox.warning(self, "警告", "两次输入的新密码不一致!")
return
if new_pwd == old_pwd:
QMessageBox.warning(self, "警告", "新密码不能与原密码相同!")
return
now = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
hashed_new = crypto.generate_hash(new_pwd)
db.update_password(self.account, hashed_new)
old_history = db.get_history_info(self.account)
history_list = json.loads(old_history) if old_history else []
encrypted_phone = db.get_encrypted_phone(self.account)
history_entry = {
"phone": encrypted_phone,
"password": stored_hash,
"operate_time": now
}
history_list.append(history_entry)
db.set_history_info(self.account, json.dumps(history_list))
QMessageBox.information(self, "成功", "密码修改成功!")
self.accept()
# ======================== 设置新密码弹窗 ========================
class SetNewPasswordDialog(QDialog):
def __init__(self, parent=None):
super().__init__(parent)
self.setWindowTitle("设置新密码")
self.setFixedWidth(460)
self.init_ui()
self.apply_styles()
self.new_password = None
def init_ui(self):
layout = QVBoxLayout(self)
layout.setSpacing(20)
layout.setContentsMargins(50, 30, 50, 30)
form_layout = QFormLayout()
form_layout.setLabelAlignment(Qt.AlignRight)
form_layout.setSpacing(20)
self.new_pwd_edit = QLineEdit()
self.new_pwd_edit.setEchoMode(QLineEdit.Password)
self.new_pwd_edit.setPlaceholderText("请输入新密码")
self.confirm_pwd_edit = QLineEdit()
self.confirm_pwd_edit.setEchoMode(QLineEdit.Password)
self.confirm_pwd_edit.setPlaceholderText("请再次输入新密码")
form_layout.addRow("新密码:", self.new_pwd_edit)
form_layout.addRow("确认密码:", self.confirm_pwd_edit)
layout.addLayout(form_layout)
btn_layout = QHBoxLayout()
btn_layout.setSpacing(20)
self.ok_btn = QPushButton("确 定")
self.ok_btn.setFixedHeight(38)
self.ok_btn.setObjectName("primaryBtn")
self.cancel_btn = QPushButton("取 消")
self.cancel_btn.setFixedHeight(38)
self.cancel_btn.setObjectName("secondaryBtn")
btn_layout.addStretch()
btn_layout.addWidget(self.ok_btn)
btn_layout.addWidget(self.cancel_btn)
btn_layout.addStretch()
layout.addLayout(btn_layout)
self.ok_btn.clicked.connect(self.validate)
self.cancel_btn.clicked.connect(self.reject)
def apply_styles(self):
self.setStyleSheet("""
QDialog { background-color: #f8faff; }
QLabel { color: #333; font-size: 18px; }
QLineEdit { border: 1px solid #d0d7e5; border-radius: 5px; padding: 8px 12px; background: white; font-size: 18px; }
QLineEdit:focus { border: 2px solid #4F7EF7; padding: 7px 11px; }
""")
def validate(self):
new_pwd = self.new_pwd_edit.text().strip()
confirm_pwd = self.confirm_pwd_edit.text().strip()
if not new_pwd:
QMessageBox.warning(self, "警告", "新密码不能为空!")
return
if new_pwd != confirm_pwd:
QMessageBox.warning(self, "警告", "两次输入的密码不一致!")
return
self.new_password = new_pwd
self.accept()
# ======================== 快速找回密码窗口 ========================
class FindPasswordDialog(QDialog):
def __init__(self, customer_account=None, parent=None):
super().__init__(parent)
self.customer_account = customer_account
self.setWindowTitle("快速找回密码")
self.setFixedWidth(500)
self.matched_user = None
self.init_ui()
self.apply_styles()
def init_ui(self):
layout = QVBoxLayout(self)
layout.setSpacing(20)
layout.setContentsMargins(60, 30, 60, 30)
hint_label = QLabel("请至少填写用户账号或联系方式其中一项")
hint_label.setStyleSheet("color: #4F7EF7; font-size: 18px; margin-bottom: 10px;")
layout.addWidget(hint_label)
form_layout = QFormLayout()
form_layout.setLabelAlignment(Qt.AlignRight)
form_layout.setSpacing(20)
self.account_edit = QLineEdit()
self.account_edit.setPlaceholderText("请输入用户账号")
self.phone_edit = QLineEdit()
self.phone_edit.setPlaceholderText("请输入联系方式")
self.question_edit = QLineEdit()
self.question_edit.setReadOnly(True)
self.question_edit.setPlaceholderText("验证通过后自动显示")
self.answer_edit = QLineEdit()
self.answer_edit.setPlaceholderText("请输入密保答案")
form_layout.addRow("用户账号:", self.account_edit)
form_layout.addRow("联系方式:", self.phone_edit)
form_layout.addRow("密保问题:", self.question_edit)
form_layout.addRow("密码答案:", self.answer_edit)
layout.addLayout(form_layout)
btn_layout = QHBoxLayout()
btn_layout.setSpacing(20)
self.ok_btn = QPushButton("确 定")
self.ok_btn.setFixedHeight(40)
self.ok_btn.setObjectName("primaryBtn")
self.cancel_btn = QPushButton("取 消")
self.cancel_btn.setFixedHeight(40)
self.cancel_btn.setObjectName("secondaryBtn")
btn_layout.addStretch()
btn_layout.addWidget(self.ok_btn)
btn_layout.addWidget(self.cancel_btn)
btn_layout.addStretch()
layout.addLayout(btn_layout)
self.account_edit.editingFinished.connect(self.auto_verify)
self.phone_edit.editingFinished.connect(self.auto_verify)
self.ok_btn.clicked.connect(self.on_ok)
self.cancel_btn.clicked.connect(self.reject)
def apply_styles(self):
self.setStyleSheet("""
QDialog { background-color: #f8faff; }
QLabel { color: #333; font-size: 18px; }
QLineEdit { border: 1px solid #d0d7e5; border-radius: 5px; padding: 8px 12px; background: white; font-size: 18px; }
QLineEdit:focus { border: 2px solid #4F7EF7; padding: 7px 11px; }
QLineEdit[readOnly="true"] { background: #f0f3f9; color: #666; }
""")
def auto_verify(self):
account = self.account_edit.text().strip()
phone = self.phone_edit.text().strip()
if not account and not phone:
self.question_edit.clear()
self.matched_user = None
return
hash_phone = crypto.generate_query_hash(phone) if phone else None
user = db.find_user_by_account_or_phone(account, hash_phone)
if not user:
QMessageBox.warning(self, "提示", "未找到匹配的账号信息。")
self.question_edit.clear()
self.matched_user = None
return
self.matched_user = user
self.account_edit.setText(user["customer_account"])
self.account_edit.setReadOnly(True)
decrypted_phone = crypto.decrypt(user["contact_phone"])
self.phone_edit.setText(decrypted_phone)
self.phone_edit.setReadOnly(True)
security_info = json.loads(user["security_info"])
if security_info:
self.question_edit.setText(security_info[-1]["pwd_question"])
else:
self.question_edit.clear()
def on_ok(self):
if not self.matched_user:
QMessageBox.warning(self, "警告", "请先完成身份验证。")
return
answer = self.answer_edit.text().strip()
if not answer:
QMessageBox.warning(self, "警告", "请输入密保答案。")
return
security_info = json.loads(self.matched_user["security_info"])
if not security_info or security_info[-1]["pwd_answer"] != answer:
QMessageBox.warning(self, "错误", "密保答案不正确。")
return
dlg = SetNewPasswordDialog(self)
if dlg.exec_() == QDialog.Accepted:
new_pwd = dlg.new_password
now = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
hashed_new = crypto.generate_hash(new_pwd)
db.update_password(self.matched_user["customer_account"], hashed_new)
old_phone = self.matched_user["contact_phone"]
old_pwd_hash = self.matched_user["customer_pwd"]
old_history = db.get_history_info(self.matched_user["customer_account"])
history_list = json.loads(old_history) if old_history else []
history_list.append({
"phone": old_phone,
"password": old_pwd_hash,
"operate_time": now
})
db.set_history_info(self.matched_user["customer_account"], json.dumps(history_list))
QMessageBox.information(self, "成功", "新密码设置成功!")
self.accept()
# ======================== 完善团队信息窗口 ========================
class TeamInfoDialog(QDialog):
DEPARTMENTS = ["工程部", "技术部", "财务部", "行政部", "物资部", "未分组"]
def __init__(self, account, parent=None):
super().__init__(parent)
self.account = account
self.setWindowTitle("完善团队信息")
self.setFixedWidth(500)
self.init_ui()
self.apply_styles()
def init_ui(self):
layout = QVBoxLayout(self)
layout.setSpacing(20)
layout.setContentsMargins(60, 30, 60, 30)
form_layout = QFormLayout()
form_layout.setLabelAlignment(Qt.AlignRight)
form_layout.setSpacing(15)
self.code_edit = QLineEdit()
self.code_edit.setPlaceholderText("12位大写字母(不含I,O)+数字")
self.name_edit = QLineEdit()
self.phone_edit = QLineEdit()
self.role_combo = QComboBox()
self.role_combo.addItems(["", "团长", "管理员", "组长", "队员"])
self.depart_combo = QComboBox()
self.depart_combo.addItem("")
self.depart_combo.addItems(self.DEPARTMENTS)
form_layout.addRow("编码:", self.code_edit)
form_layout.addRow("姓名:", self.name_edit)
form_layout.addRow("电话:", self.phone_edit)
form_layout.addRow("权限:", self.role_combo)
form_layout.addRow("部门:", self.depart_combo)
layout.addLayout(form_layout)
btn_layout = QHBoxLayout()
btn_layout.setSpacing(20)
self.ok_btn = QPushButton("确 定")
self.ok_btn.setFixedHeight(38)
self.ok_btn.setObjectName("primaryBtn")
self.cancel_btn = QPushButton("取 消")
self.cancel_btn.setFixedHeight(38)
self.cancel_btn.setObjectName("secondaryBtn")
btn_layout.addStretch()
btn_layout.addWidget(self.ok_btn)
btn_layout.addWidget(self.cancel_btn)
btn_layout.addStretch()
layout.addLayout(btn_layout)
self.ok_btn.clicked.connect(self.on_save)
self.cancel_btn.clicked.connect(self.reject)
def apply_styles(self):
self.setStyleSheet("""
QLabel { color: #333; font-size: 18px; }
QLineEdit, QComboBox { border: 1px solid #d0d7e5; border-radius: 5px; padding: 8px 12px; background: white; font-size: 18px; }
QLineEdit:focus, QComboBox:focus { border: 2px solid #4F7EF7; padding: 7px 11px; }
""")
def on_save(self):
code = self.code_edit.text().strip()
name = self.name_edit.text().strip()
phone = self.phone_edit.text().strip()
role = self.role_combo.currentText().strip()
depart = self.depart_combo.currentText().strip()
if not all([code, name, phone, role, depart]):
QMessageBox.warning(self, "警告", "所有字段不能为空。")
return
if not re.match(r'^[A-HJ-NP-Z0-9]{12}$', code):
QMessageBox.warning(self, "警告", "成员编码必须为12位大写字母(不含I,O)和数字。")
return
team_info = {
"member_code": code,
"member_name": name,
"member_phone": phone,
"member_role": role,
"member_depart": depart,
"group_departs": self.DEPARTMENTS
}
db.update_team_info(self.account, json.dumps(team_info))
QMessageBox.information(self, "成功", "团队信息更新成功!")
self.accept()
# ======================== 服务记录窗口 ========================
class ServiceRecordDialog(QDialog):
def __init__(self, account, service_record=None, parent=None):
super().__init__(parent)
self.account = account
self.service_record = service_record if service_record else []
self.setWindowTitle("服务记录")
self.resize(700, 500)
self.init_ui()
self.load_data()
def init_ui(self):
layout = QVBoxLayout(self)
layout.setContentsMargins(20, 20, 20, 20)
self.table = QTableWidget()
self.table.setColumnCount(4)
self.table.setHorizontalHeaderLabels(["序号", "内容", "操作人", "时间"])
self.table.verticalHeader().setVisible(False)
self.table.setSelectionBehavior(QAbstractItemView.SelectRows)
self.table.setSelectionMode(QAbstractItemView.SingleSelection)
self.table.setEditTriggers(QAbstractItemView.NoEditTriggers)
self.table.setColumnWidth(0, 80)
self.table.setColumnWidth(1, 280)
self.table.setColumnWidth(2, 150)
self.table.setColumnWidth(3, 180)
self.table.setStyleSheet("""
QHeaderView::section { background-color: #4F7EF7; color: white; }
QTableWidget::item:selected { background-color: #4F7EF7; color: white; }
""")
self.word_delegate = WordWrapDelegate()
self.table.setItemDelegateForColumn(1, self.word_delegate)
layout.addWidget(self.table)
btn_layout = QHBoxLayout()
btn_layout.addStretch()
self.add_btn = QPushButton("添加记录")
self.add_btn.setObjectName("primaryBtn")
self.close_btn = QPushButton("关闭")
self.close_btn.setObjectName("secondaryBtn")
btn_layout.addWidget(self.add_btn)
btn_layout.addWidget(self.close_btn)
layout.addLayout(btn_layout)
self.add_btn.clicked.connect(self.add_record)
self.close_btn.clicked.connect(self.close)
def load_data(self):
self.table.setRowCount(len(self.service_record))
for row, record in enumerate(self.service_record):
item0 = QTableWidgetItem(str(row + 1))
item0.setTextAlignment(Qt.AlignCenter)
self.table.setItem(row, 0, item0)
item1 = QTableWidgetItem(record.get("content", ""))
item1.setTextAlignment(Qt.AlignLeft | Qt.AlignVCenter)
self.table.setItem(row, 1, item1)
item2 = QTableWidgetItem(record.get("operator", ""))
item2.setTextAlignment(Qt.AlignCenter)
self.table.setItem(row, 2, item2)
item3 = QTableWidgetItem(record.get("operate_time", ""))
item3.setTextAlignment(Qt.AlignCenter)
self.table.setItem(row, 3, item3)
self.table.resizeRowsToContents()
def add_record(self):
dlg = AddRecordDialog(self.account, "添加服务记录", parent=self)
if dlg.exec_() == QDialog.Accepted:
new_record = dlg.get_record()
if new_record:
self.service_record.append(new_record)
db.set_service_records(self.account, self.service_record)
self.load_data()
QMessageBox.information(self, "成功", "服务记录添加成功!")
class AddRecordDialog(QDialog):
def __init__(self, account, title="添加记录", parent=None):
super().__init__(parent)
self.account = account
self.setWindowTitle(title)
self.setFixedSize(420, 320)
self.record = None
self.init_ui()
self.apply_styles()
def init_ui(self):
layout = QVBoxLayout(self)
layout.setContentsMargins(30, 20, 30, 20)
layout.setSpacing(15)
title_label = QLabel("新增记录")
title_label.setStyleSheet("font-size: 20px; font-weight: bold; color: #4F7EF7;")
layout.addWidget(title_label)
self.text_edit = QTextEdit()
self.text_edit.setPlaceholderText("请输入记录内容...")
layout.addWidget(self.text_edit)
btn_layout = QHBoxLayout()
btn_layout.setSpacing(20)
self.ok_btn = QPushButton("确 定")
self.ok_btn.setObjectName("primaryBtn")
self.cancel_btn = QPushButton("取 消")
self.cancel_btn.setObjectName("secondaryBtn")
btn_layout.addStretch()
btn_layout.addWidget(self.ok_btn)
btn_layout.addWidget(self.cancel_btn)
layout.addLayout(btn_layout)
self.ok_btn.clicked.connect(self.validate)
self.cancel_btn.clicked.connect(self.reject)
def apply_styles(self):
self.setStyleSheet("""
QTextEdit { border: 1px solid #d0d7e5; border-radius: 5px; padding: 8px; font-size: 16px; }
""")
def validate(self):
content = self.text_edit.toPlainText().strip()
if not content:
QMessageBox.warning(self, "警告", "内容不能为空!")
return
now = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
self.record = {"content": content, "operator": self.account, "operate_time": now}
self.accept()
def get_record(self):
return self.record
# ======================== 客户评价窗口 ========================
class CustomerEvaluateDialog(QDialog):
def __init__(self, account, parent=None):
super().__init__(parent)
self.account = account
self.setWindowTitle("客户评价")
self.setFixedSize(420, 320)
self.init_ui()
self.apply_styles()
def init_ui(self):
layout = QVBoxLayout(self)
layout.setContentsMargins(30, 20, 30, 20)
layout.setSpacing(15)
title_label = QLabel("写下您的评价")
title_label.setStyleSheet("font-size: 20px; font-weight: bold; color: #4F7EF7;")
layout.addWidget(title_label)
self.text_edit = QTextEdit()
self.text_edit.setPlaceholderText("请在此输入评价内容...")
layout.addWidget(self.text_edit)
btn_layout = QHBoxLayout()
btn_layout.setSpacing(20)
self.ok_btn = QPushButton("提 交")
self.ok_btn.setObjectName("primaryBtn")
self.cancel_btn = QPushButton("取 消")
self.cancel_btn.setObjectName("secondaryBtn")
btn_layout.addStretch()
btn_layout.addWidget(self.ok_btn)
btn_layout.addWidget(self.cancel_btn)
layout.addLayout(btn_layout)
self.ok_btn.clicked.connect(self.on_ok)
self.cancel_btn.clicked.connect(self.reject)
def apply_styles(self):
self.setStyleSheet("""
QTextEdit { border: 1px solid #d0d7e5; border-radius: 5px; padding: 8px; font-size: 16px; }
""")
def on_ok(self):
content = self.text_edit.toPlainText().strip()
if not content:
QMessageBox.warning(self, "警告", "评价内容不能为空!")
return
now = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
evaluate_entry = {"content": content, "evaluate_time": now}
evaluate_list = db.get_customer_evaluate(self.account)
evaluate_list.append(evaluate_entry)
try:
db.set_customer_evaluate(self.account, evaluate_list)
QMessageBox.information(self, "成功", "评价提交成功!")
self.accept()
except Exception as e:
QMessageBox.warning(self, "失败", f"提交失败:{e}")
# ======================== 客户评价历史窗口 ========================
class EvaluateHistoryDialog(QDialog):
def __init__(self, customer_evaluate=None, parent=None):
super().__init__(parent)
self.evaluate_data = customer_evaluate if customer_evaluate else []
self.setWindowTitle("客户评价历史记录")
self.resize(600, 400)
self.init_ui()
self.load_data()
def init_ui(self):
layout = QVBoxLayout(self)
layout.setContentsMargins(20, 20, 20, 20)
self.table = QTableWidget()
self.table.setColumnCount(3)
self.table.setHorizontalHeaderLabels(["序号", "内容", "时间"])
self.table.verticalHeader().setVisible(False)
self.table.setSelectionBehavior(QAbstractItemView.SelectRows)
self.table.setSelectionMode(QAbstractItemView.SingleSelection)
self.table.setEditTriggers(QAbstractItemView.NoEditTriggers)
self.table.setColumnWidth(0, 80)
self.table.setColumnWidth(1, 300)
self.table.setColumnWidth(2, 180)
self.table.setStyleSheet("""
QHeaderView::section { background-color: #4F7EF7; color: white; }
QTableWidget::item:selected { background-color: #4F7EF7; color: white; }
""")
self.word_delegate = WordWrapDelegate()
self.table.setItemDelegateForColumn(1, self.word_delegate)
layout.addWidget(self.table)
btn_layout = QHBoxLayout()
btn_layout.addStretch()
self.close_btn = QPushButton("关 闭")
self.close_btn.setObjectName("secondaryBtn")
btn_layout.addWidget(self.close_btn)
layout.addLayout(btn_layout)
self.close_btn.clicked.connect(self.close)
def load_data(self):
self.table.setRowCount(len(self.evaluate_data))
for row, record in enumerate(self.evaluate_data):
item0 = QTableWidgetItem(str(row + 1))
item0.setTextAlignment(Qt.AlignCenter)
self.table.setItem(row, 0, item0)
item1 = QTableWidgetItem(record.get("content", ""))
item1.setTextAlignment(Qt.AlignLeft | Qt.AlignVCenter)
self.table.setItem(row, 1, item1)
item2 = QTableWidgetItem(record.get("evaluate_time", ""))
item2.setTextAlignment(Qt.AlignCenter)
self.table.setItem(row, 2, item2)
self.table.resizeRowsToContents()
# ======================== 客户备注窗口 ========================
class CustomerRemarkDialog(QDialog):
def __init__(self, account, customer_remark=None, parent=None):
super().__init__(parent)
self.account = account
self.remark_data = customer_remark if customer_remark else []
self.setWindowTitle("客户备注")
self.resize(700, 500)
self.init_ui()
self.load_data()
def init_ui(self):
layout = QVBoxLayout(self)
layout.setContentsMargins(20, 20, 20, 20)
self.table = QTableWidget()
self.table.setColumnCount(4)
self.table.setHorizontalHeaderLabels(["序号", "内容", "操作人", "时间"])
self.table.verticalHeader().setVisible(False)
self.table.setSelectionBehavior(QAbstractItemView.SelectRows)
self.table.setSelectionMode(QAbstractItemView.SingleSelection)
self.table.setEditTriggers(QAbstractItemView.NoEditTriggers)
self.table.setColumnWidth(0, 80)
self.table.setColumnWidth(1, 280)
self.table.setColumnWidth(2, 150)
self.table.setColumnWidth(3, 180)
self.table.setStyleSheet("""
QHeaderView::section { background-color: #4F7EF7; color: white; }
QTableWidget::item:selected { background-color: #4F7EF7; color: white; }
""")
self.word_delegate = WordWrapDelegate()
self.table.setItemDelegateForColumn(1, self.word_delegate)
layout.addWidget(self.table)
btn_layout = QHBoxLayout()
btn_layout.addStretch()
self.add_btn = QPushButton("添加备注")
self.add_btn.setObjectName("primaryBtn")
self.close_btn = QPushButton("关 闭")
self.close_btn.setObjectName("secondaryBtn")
btn_layout.addWidget(self.add_btn)
btn_layout.addWidget(self.close_btn)
layout.addLayout(btn_layout)
self.add_btn.clicked.connect(self.add_remark)
self.close_btn.clicked.connect(self.close)
def load_data(self):
self.table.setRowCount(len(self.remark_data))
for row, record in enumerate(self.remark_data):
item0 = QTableWidgetItem(str(row + 1))
item0.setTextAlignment(Qt.AlignCenter)
self.table.setItem(row, 0, item0)
item1 = QTableWidgetItem(record.get("content", ""))
item1.setTextAlignment(Qt.AlignLeft | Qt.AlignVCenter)
self.table.setItem(row, 1, item1)
item2 = QTableWidgetItem(record.get("operator", ""))
item2.setTextAlignment(Qt.AlignCenter)
self.table.setItem(row, 2, item2)
item3 = QTableWidgetItem(record.get("operate_time", ""))
item3.setTextAlignment(Qt.AlignCenter)
self.table.setItem(row, 3, item3)
self.table.resizeRowsToContents()
def add_remark(self):
dlg = AddRecordDialog(self.account, "添加备注", parent=self)
if dlg.exec_() == QDialog.Accepted:
new_record = dlg.get_record()
if new_record:
self.remark_data.append(new_record)
db.set_customer_remark(self.account, self.remark_data)
self.load_data()
QMessageBox.information(self, "成功", "备注添加成功!")
# ======================== 授权窗口 ========================
class AuthorizationDialog(QDialog):
def __init__(self, account=None, parent=None):
super().__init__(parent)
self.setWindowTitle("授权管理")
self.setFixedSize(400, 200)
self.init_ui()
self.apply_styles()
def init_ui(self):
layout = QVBoxLayout(self)
layout.setContentsMargins(30, 30, 30, 30)
self.auth_edit = QLineEdit()
self.auth_edit.setPlaceholderText("请输入32位授权码(大写字母不含I,O和数字)")
layout.addWidget(self.auth_edit)
btn_layout = QHBoxLayout()
btn_layout.setSpacing(20)
self.ok_btn = QPushButton("确 定")
self.ok_btn.setObjectName("primaryBtn")
self.cancel_btn = QPushButton("取 消")
self.cancel_btn.setObjectName("secondaryBtn")
btn_layout.addStretch()
btn_layout.addWidget(self.ok_btn)
btn_layout.addWidget(self.cancel_btn)
layout.addLayout(btn_layout)
self.ok_btn.clicked.connect(self.on_ok)
self.cancel_btn.clicked.connect(self.reject)
def apply_styles(self):
self.setStyleSheet("""
QLineEdit { border: 1px solid #d0d7e5; border-radius: 5px; padding: 8px 12px; font-size: 18px; }
""")
def on_ok(self):
auth_code = self.auth_edit.text().strip()
if not auth_code:
QMessageBox.warning(self, "警告", "授权码不能为空!")
return
if not re.match(r'^[A-HJ-NP-Z0-9]{32}$', auth_code):
QMessageBox.warning(self, "警告", "授权码格式不正确,应为32位大写字母(不含I,O)和数字。")
return
QMessageBox.information(self, "成功", "恭喜,授权成功!")
self.accept()
# ======================== 主窗口 ========================
class MainWindow(QWidget):
def __init__(self, account, net_name, phone, password):
super().__init__()
self.account = account
self.net_name = net_name
self.phone = phone
self.password = password
self.setWindowTitle("我的主窗口")
self.resize(1000, 700)
self.init_ui()
self.apply_styles()
def init_ui(self):
layout = QVBoxLayout(self)
layout.setSpacing(20)
layout.setContentsMargins(30, 30, 30, 30)
title = QLabel(f"欢迎,{self.net_name}")
title.setAlignment(Qt.AlignCenter)
title.setStyleSheet("font-size: 24px; font-weight: bold; color: #4F7EF7;")
layout.addWidget(title)
grid = QGridLayout()
grid.setSpacing(15)
buttons_info = [
("我的资料", self.open_profile),
("修改密码", self.open_change_password),
("找回密码", self.open_find_password),
("团队信息", self.open_team_info),
("服务记录", self.open_service_record),
("客户评价", self.open_customer_evaluate),
("评价历史", self.open_evaluate_history),
("客户备注", self.open_customer_remark),
("授权管理", self.open_authorization),
]
for i in range(21):
buttons_info.append((f"备用按钮{i+1}", lambda _, name=f"备用按钮{i+1}": self.show_spare(name)))
row, col = 0, 0
for text, func in buttons_info:
btn = QPushButton(text)
btn.setFixedSize(160, 50)
btn.setObjectName("primaryBtn" if "备用" not in text else "secondaryBtn")
btn.clicked.connect(func)
grid.addWidget(btn, row, col)
col += 1
if col >= 5:
col = 0
row += 1
layout.addLayout(grid)
def apply_styles(self):
self.setStyleSheet("""
QWidget { background-color: #f8faff; }
QPushButton#primaryBtn, QPushButton#secondaryBtn {
border-radius: 8px; font-size: 18px; font-weight: bold;
}
QPushButton#primaryBtn { background: qlineargradient(x1:0, y1:0, x2:0, y2:1, stop:0 #6b9af8, stop:1 #4F7EF7); color: white; border: none; }
QPushButton#primaryBtn:hover { background: qlineargradient(x1:0, y1:0, x2:0, y2:1, stop:0 #7ba8ff, stop:1 #5f8eff); }
QPushButton#secondaryBtn { background: white; color: #4F7EF7; border: 2px solid #4F7EF7; }
QPushButton#secondaryBtn:hover { background: #eaf1ff; }
""")
def open_profile(self):
dlg = ProfileDialog(self.account, self.net_name, self.phone, self.password, self)
dlg.exec_()
def open_change_password(self):
dlg = ChangePasswordDialog(self.account, self.phone, self)
dlg.exec_()
def open_find_password(self):
dlg = FindPasswordDialog(customer_account=self.account, parent=self)
dlg.exec_()
def open_team_info(self):
dlg = TeamInfoDialog(self.account, self)
dlg.exec_()
def open_service_record(self):
data = db.get_service_records(self.account)
dlg = ServiceRecordDialog(self.account, data, self)
dlg.exec_()
def open_customer_evaluate(self):
dlg = CustomerEvaluateDialog(self.account, self)
dlg.exec_()
def open_evaluate_history(self):
data = db.get_customer_evaluate(self.account)
dlg = EvaluateHistoryDialog(data, self)
dlg.exec_()
def open_customer_remark(self):
data = db.get_customer_remark(self.account)
dlg = CustomerRemarkDialog(self.account, data, self)
dlg.exec_()
def open_authorization(self):
dlg = AuthorizationDialog(self.account, self)
dlg.exec_()
def show_spare(self, name):
QMessageBox.information(self, "备用按钮", f"您点击了:{name}")
# ======================== 程序入口 ========================
if __name__ == "__main__":
config = {
'applicationName': 'PyQt5 主题化控件库演示',
'organizationName': 'MyCompany',
'organizationDomain': 'mycompany.com',
'applicationVersion': '2.0',
'windowIcon': 'fox.ico',
'font': {
'family': 'Microsoft YaHei',
'pointSize': 9,
'weight': QFont.Normal
},
'translatorPath': 'qt_zh_CN.qm'
}
app = QApplication(sys.argv,config)
app.setFont(QFont("Microsoft YaHei", 10))
login = LoginWindow()
login.show()
sys.exit(app.exec_())