python日常记账本源代码,基于PySide6(Qt for Python 6)的账本,界面简洁、功能强大,支持保存文件、快速查询、绘制图表等,是平时记账的不错选择。账目查询、账本编辑、添加/删除、撤销/重做、统计数据、生成图表。
main.py
import sys
from bisect import insort_right
from functools import partial
from os.path import basename
from webbrowser import open_new_tab
from PySide6.QtWidgets import *
from PySide6.QtCore import Slot, QDate
from PySide6.QtGui import QStandardItem, QStandardItemModel
from api import ApiError, openFile, query, saveFile
from dlgAdd import dlgAdd
from dlgCharts import dlgCharts
from dlgSettings import dlgSettings
from ui_dlgHelp import Ui_Dialog as Ui_dlgHelp
from ui_MainWindow import Ui_MainWindow
# Version info
VERSION = '1.2.1'
CHANNEL = 'stable'
BUILD_DATE = '2022-08-25'
FULL_VERSION = f'{VERSION}-{CHANNEL} ({BUILD_DATE}) on {sys.platform}'
app = QApplication(sys.argv)
class AccountBookMainWindow(QMainWindow):
version_str = '账本 ' + VERSION
unsaved_tip = '*'
SUPPORTED_FILTERS = '账本文件(*.abf);;文本文件(*.txt);;所有文件(*.*)'
def __init__(self, parent=None):
# Initialize window
super().__init__(parent)
self.ui = Ui_MainWindow()
self.ui.setupUi(self)
self.setWindowTitle('账本 ' + VERSION)
self.labStatus = QLabel(self)
self.ui.statusBar.addWidget(self.labStatus)
# Initialize table
self.model = QStandardItemModel(0, 4, self)
self.model.setHorizontalHeaderLabels(['日期', '事项', '金额', '备注'])
self.ui.table.setModel(self.model)
self.ui.table.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch)
self.__data = []
self.on_actFile_New_triggered()
self.ui.actEdit_Remove.setEnabled(False)
# Connect slots
self.ui.table.selectionModel().selectionChanged.connect(self.__selectionChanged)
self.model.itemChanged.connect(self.__itemChanged)
def __updateTable(self, data):
self.model.itemChanged.disconnect(self.__itemChanged)
self.model.setRowCount(len(data))
for row in range(len(data)):
for col in range(len(data[row])):
self.model.setItem(row, col, QStandardItem(data[row][col]))
self.model.itemChanged.connect(self.__itemChanged)
def __openFile(self, filename):
try:
self.__data = openFile(filename)
except IOError:
QMessageBox.critical(self, '错误', '文件打开失败。请稍后再试。')
except ApiError:
QMessageBox.critical(self, '错误', '文件格式错误。请检查文件完整性。')
except Exception as e:
QMessageBox.critical(self, '错误', '未知错误:' + str(e.with_traceback()))
else:
self.ui.searchEdit.clear()
self.__key = ''
self.__updateTable(self.__data)
self.labStatus.setText(filename)
self.setWindowTitle(self.version_str)
self.__filename = filename
def __saveFile(self, filename):
try:
saveFile(filename, self.__data)
except IOError:
QMessageBox.critical(self, '错误', '文件保存错误。请稍后再试。')
except Exception as e:
QMessageBox.critical(self, '错误', '未知错误:' + str(e.with_traceback()))
else:
self.labStatus.setText('保存成功:' + filename)
self.setWindowTitle(self.version_str)
self.__filename = filename
@Slot()
def on_actFile_New_triggered(self):
self.__filename = self.__key = ''
self.setWindowTitle(self.unsaved_tip + self.version_str)
self.labStatus.setText('新文件')
self.model.setRowCount(0)
self.__data.clear()
@Slot()
def on_actFile_Open_triggered(self):
filename, _ = QFileDialog.getOpenFileName(self, '打开', filter=self.SUPPORTED_FILTERS)
if filename:
self.__openFile(filename)
@Slot()
def on_actFile_Save_triggered(self):
if self.__filename:
self.__saveFile(self.__filename)
else:
filename, _ = QFileDialog.getSaveFileName(self, '保存', filter=self.SUPPORTED_FILTERS)
if filename:
self.__saveFile(filename)
@Slot()
def on_actFile_SaveAs_triggered(self):
filename, _ = QFileDialog.getSaveFileName(self, '另存为', filter=self.SUPPORTED_FILTERS)
if filename:
self.__saveFile(filename)
@Slot()
def on_actFile_Settings_triggered(self):
dlgSettings(self).exec()
@Slot()
def on_actHelp_About_triggered(self):
dialog = QDialog(self)
ui = Ui_dlgHelp()
ui.setupUi(dialog)
for link in (ui.githubLink, ui.giteeLink, ui.licenseLink, ui.readmeLink):
link.clicked.connect(partial(open_new_tab, link.description()))
ui.labVersion.setText('版本号:' + FULL_VERSION)
ui.btnUpdate.clicked.connect(partial(open_new_tab, 'https://github.com/GoodCoder666/AccountBook/releases'))
dialog.exec()
@Slot()
def on_actHelp_AboutQt_triggered(self):
QMessageBox.aboutQt(self, '关于Qt')
@Slot()
def on_actEdit_Add_triggered(self):
dialog = dlgAdd(self)
if dialog.exec() == QDialog.Accepted:
row = dialog.getRow()
insort_right(self.__data, row)
self.__updateTable(query(self.__data, self.__key))
self.setWindowTitle(self.unsaved_tip + self.version_str)
@Slot()
def on_actEdit_Remove_triggered(self):
rows = list(set(map(lambda idx: idx.row(), self.ui.table.selectedIndexes())))
for row in rows:
self.__data.remove([self.model.item(row, col).text() for col in range(self.model.columnCount())])
self.model.itemChanged.disconnect(self.__itemChanged)
self.model.removeRows(rows[0], len(rows))
self.model.itemChanged.connect(self.__itemChanged)
self.setWindowTitle(self.unsaved_tip + self.version_str)
def __selectionChanged(self):
self.ui.actEdit_Remove.setEnabled(self.ui.table.selectionModel().hasSelection())
def __itemChanged(self, item: QStandardItem):
i, j, new = item.row(), item.column(), item.text()
if (old := self.__data[i][j]) == new: return
if j == 0 and not QDate.fromString(new, 'yyyy/MM/dd').isValid():
QMessageBox.critical(self, '错误', '日期格式错误。')
self.model.itemChanged.disconnect(self.__itemChanged)
item.setText(old)
self.model.itemChanged.connect(self.__itemChanged)
return
row = self.__data.pop(i)
row[j] = new
insort_right(self.__data, row)
self.__updateTable(query(self.__data, self.__key))
self.setWindowTitle(self.unsaved_tip + self.version_str)
@Slot()
def on_searchEdit_textChanged(self):
self.__key = self.ui.searchEdit.text()
self.__updateTable(query(self.__data, self.__key))
@Slot()
def on_actStat_Show_triggered(self):
if self.__data:
dlgCharts(self.__data, self).exec()
else:
QMessageBox.information(self, '提示', '请添加数据以使用统计功能。')
def closeEvent(self, event):
if not self.windowTitle().startswith(self.unsaved_tip): return
filename = basename(self.__filename) if self.__filename else '新文件'
messageBox = QMessageBox(
parent=self, icon=QMessageBox.Warning, windowTitle='提示',
text=f'是否要保存对 {filename} 的更改?', informativeText='如果不保存,你的更改将丢失。',
standardButtons=QMessageBox.Save | QMessageBox.Discard | QMessageBox.Cancel
)
messageBox.setButtonText(QMessageBox.Save, '保存')
messageBox.setButtonText(QMessageBox.Discard, '不保存')
messageBox.setButtonText(QMessageBox.Cancel, '取消')
reply = messageBox.exec()
if reply == QMessageBox.Save:
self.on_actFile_Save_triggered()
event.accept()
elif reply == QMessageBox.Discard:
event.accept()
else:
event.ignore()
def dragEnterEvent(self, event):
event.accept()
def dropEvent(self, event):
self.__openFile(event.mimeData().text()[8:]) # [8:] is to get rid of 'file:///'
mainform = AccountBookMainWindow()
mainform.show()
sys.exit(app.exec())
完整程序下载地址:
https://download.csdn.net/download/weixin_42756970/86845889
全部0条评论
快来发表一下你的评论吧 !