This commit is contained in:
2025-11-25 20:59:02 +08:00
parent 77b9eb978d
commit d9a02a8670
9 changed files with 573 additions and 86 deletions

View File

@@ -0,0 +1,268 @@
"""
分类管理对话框
提供新增、重命名、删除分类的功能
"""
from typing import Optional
from PyQt5.QtWidgets import (QDialog, QVBoxLayout, QHBoxLayout, QListWidget, QListWidgetItem)
from PyQt5.QtCore import Qt
from qfluentwidgets import (BodyLabel, PushButton, PrimaryPushButton, LineEdit,
InfoBar, InfoBarPosition)
from .tools.finance_manager import FinanceManager
class CategoryManagementDialog(QDialog):
"""分类管理对话框"""
def __init__(self, parent=None, finance_manager: Optional[FinanceManager] = None, account_id: Optional[str] = None):
super().__init__(parent)
self.finance_manager = finance_manager
self.account_id = account_id
self.setWindowTitle("分类管理")
self.setGeometry(100, 100, 500, 400)
self.init_ui()
def init_ui(self):
"""初始化UI"""
main_layout = QVBoxLayout()
# 标签
title_label = BodyLabel("选择分类进行管理:")
main_layout.addWidget(title_label)
# 分类列表
self.category_list = QListWidget()
self.category_list.itemSelectionChanged.connect(self.on_category_selected)
main_layout.addWidget(self.category_list)
# 加载分类
self.load_categories()
# 按钮区域
btn_layout = QHBoxLayout()
btn_layout.addStretch()
# 新增按钮
add_btn = PrimaryPushButton("新增")
add_btn.clicked.connect(self.on_add_category)
btn_layout.addWidget(add_btn)
# 重命名按钮
self.rename_btn = PushButton("重命名")
self.rename_btn.clicked.connect(self.on_rename_category)
self.rename_btn.setEnabled(False)
btn_layout.addWidget(self.rename_btn)
# 删除按钮
self.delete_btn = PushButton("删除")
self.delete_btn.clicked.connect(self.on_delete_category)
self.delete_btn.setEnabled(False)
btn_layout.addWidget(self.delete_btn)
# 关闭按钮
close_btn = PushButton("关闭")
close_btn.clicked.connect(self.accept)
btn_layout.addWidget(close_btn)
main_layout.addLayout(btn_layout)
self.setLayout(main_layout)
def load_categories(self):
"""加载分类列表"""
if not self.finance_manager or not self.account_id:
return
self.category_list.clear()
categories = self.finance_manager.get_categories(self.account_id)
for category in categories:
item = QListWidgetItem(category)
self.category_list.addItem(item)
def on_category_selected(self):
"""分类被选择"""
has_selection = self.category_list.currentItem() is not None
self.rename_btn.setEnabled(has_selection)
self.delete_btn.setEnabled(has_selection)
def on_add_category(self):
"""新增分类"""
# 弹出输入对话框
from PyQt5.QtWidgets import QDialog as QStdDialog
from PyQt5.QtWidgets import QLabel
dialog = QStdDialog(self)
dialog.setWindowTitle("新增分类")
dialog.setGeometry(150, 150, 400, 150)
layout = QVBoxLayout(dialog)
layout.addWidget(BodyLabel("分类名称:"))
input_edit = LineEdit()
input_edit.setPlaceholderText("例如:食品、交通、娱乐等")
layout.addWidget(input_edit)
# 按钮
btn_layout = QHBoxLayout()
btn_layout.addStretch()
def on_create():
category_name = input_edit.text().strip()
if not category_name:
InfoBar.warning(
title="提示",
content="分类名称不能为空",
isClosable=True,
position=InfoBarPosition.TOP,
duration=2000,
parent=self
)
return
if self.finance_manager.add_category(self.account_id, category_name):
InfoBar.success(
title="成功",
content=f"分类 '{category_name}' 创建成功",
isClosable=True,
position=InfoBarPosition.TOP,
duration=2000,
parent=self
)
self.load_categories()
dialog.accept()
else:
InfoBar.warning(
title="提示",
content="分类已存在",
isClosable=True,
position=InfoBarPosition.TOP,
duration=2000,
parent=self
)
cancel_btn = PushButton("取消")
cancel_btn.clicked.connect(dialog.reject)
btn_layout.addWidget(cancel_btn)
create_btn = PrimaryPushButton("创建")
create_btn.clicked.connect(on_create)
btn_layout.addWidget(create_btn)
layout.addLayout(btn_layout)
dialog.exec()
def on_rename_category(self):
"""重命名分类"""
current_item = self.category_list.currentItem()
if not current_item:
return
old_name = current_item.text()
# 弹出输入对话框
from PyQt5.QtWidgets import QDialog as QStdDialog
dialog = QStdDialog(self)
dialog.setWindowTitle("重命名分类")
dialog.setGeometry(150, 150, 400, 150)
layout = QVBoxLayout(dialog)
layout.addWidget(BodyLabel(f"原分类名: {old_name}"))
layout.addWidget(BodyLabel("新分类名:"))
input_edit = LineEdit()
input_edit.setText(old_name)
input_edit.selectAll()
layout.addWidget(input_edit)
# 按钮
btn_layout = QHBoxLayout()
btn_layout.addStretch()
def on_rename():
new_name = input_edit.text().strip()
if not new_name:
InfoBar.warning(
title="提示",
content="分类名称不能为空",
isClosable=True,
position=InfoBarPosition.TOP,
duration=2000,
parent=self
)
return
if new_name == old_name:
dialog.accept()
return
if self.finance_manager.rename_category(self.account_id, old_name, new_name):
InfoBar.success(
title="成功",
content=f"分类已重命名为 '{new_name}'",
isClosable=True,
position=InfoBarPosition.TOP,
duration=2000,
parent=self
)
self.load_categories()
dialog.accept()
else:
InfoBar.warning(
title="提示",
content="重命名失败,可能分类已存在",
isClosable=True,
position=InfoBarPosition.TOP,
duration=2000,
parent=self
)
cancel_btn = PushButton("取消")
cancel_btn.clicked.connect(dialog.reject)
btn_layout.addWidget(cancel_btn)
rename_btn = PrimaryPushButton("重命名")
rename_btn.clicked.connect(on_rename)
btn_layout.addWidget(rename_btn)
layout.addLayout(btn_layout)
dialog.exec()
def on_delete_category(self):
"""删除分类"""
current_item = self.category_list.currentItem()
if not current_item:
return
category_name = current_item.text()
# 确认删除
from PyQt5.QtWidgets import QMessageBox
reply = QMessageBox.question(
self,
"确认删除",
f"确定要删除分类 '{category_name}' 吗?\n\n使用该分类的交易记录分类将被清空。",
QMessageBox.Yes | QMessageBox.No,
QMessageBox.No
)
if reply == QMessageBox.Yes:
if self.finance_manager.delete_category(self.account_id, category_name):
InfoBar.success(
title="成功",
content=f"分类 '{category_name}' 已删除",
isClosable=True,
position=InfoBarPosition.TOP,
duration=2000,
parent=self
)
self.load_categories()
else:
InfoBar.warning(
title="错误",
content="删除分类失败",
isClosable=True,
position=InfoBarPosition.TOP,
duration=2000,
parent=self
)

View File

@@ -21,6 +21,7 @@ import json
import os
from .tools.finance_manager import FinanceManager, TransactionType, Transaction, Account
from .category_management_dialog import CategoryManagementDialog
class CreateTransactionDialog(QDialog):
@@ -502,11 +503,11 @@ class FinanceInterface(QWidget):
title_layout = QHBoxLayout()
title_layout.addWidget(SubtitleLabel("交易记录"))
# 新建分类按钮
new_category_btn = PushButton("新建分类")
new_category_btn.setFixedWidth(90)
new_category_btn.clicked.connect(self.on_create_category_clicked)
title_layout.addWidget(new_category_btn)
# 管理分类按钮
manage_category_btn = PushButton("管理分类")
manage_category_btn.setFixedWidth(90)
manage_category_btn.clicked.connect(self.on_manage_category_clicked)
title_layout.addWidget(manage_category_btn)
title_layout.addStretch()
@@ -1224,8 +1225,8 @@ class FinanceInterface(QWidget):
if trans_id:
self.view_record(trans_id)
def on_create_category_clicked(self):
"""新建分类按钮点击"""
def on_manage_category_clicked(self):
"""分类管理按钮点击"""
account_id = self.get_current_account_id()
if not account_id:
InfoBar.warning(
@@ -1238,71 +1239,27 @@ class FinanceInterface(QWidget):
)
return
# 创建自定义对话框
from PyQt5.QtWidgets import QDialog as QStdDialog, QLabel
# 打开分类管理对话框
dialog = CategoryManagementDialog(self, self.finance_manager, account_id)
if dialog.exec():
# 刷新查询页面的分类下拉框
self.refresh_query_category_dropdown()
def refresh_query_category_dropdown(self):
"""刷新查询页面的分类下拉框"""
account_id = self.get_current_account_id()
if not account_id or not hasattr(self, 'query_category'):
return
category_dialog = QStdDialog(self)
category_dialog.setWindowTitle("新建分类")
category_dialog.setGeometry(100, 100, 400, 150)
current_text = self.query_category.currentText()
self.query_category.clear()
self.query_category.addItem("全部")
layout = QVBoxLayout(category_dialog)
layout.addWidget(BodyLabel("分类名称:"))
categories = self.finance_manager.get_categories(account_id)
for category in categories:
self.query_category.addItem(category)
input_edit = LineEdit()
input_edit.setPlaceholderText("例如:食品、交通、娱乐等")
layout.addWidget(input_edit)
# 按钮
btn_layout = QHBoxLayout()
btn_layout.addStretch()
def on_create():
category_name = input_edit.text().strip()
if not category_name:
InfoBar.warning(
title="提示",
content="分类名称不能为空",
isClosable=True,
position=InfoBarPosition.TOP,
duration=2000,
parent=self
)
return
if self.finance_manager.add_category(account_id, category_name):
InfoBar.success(
title="成功",
content=f"分类 '{category_name}' 创建成功",
isClosable=True,
position=InfoBarPosition.TOP,
duration=2000,
parent=self
)
# 更新查询页面的分类下拉框
if hasattr(self, 'query_category'):
# 检查是否已经存在
if self.query_category.findText(category_name) < 0:
self.query_category.addItem(category_name)
category_dialog.accept()
else:
InfoBar.warning(
title="提示",
content="分类已存在",
isClosable=True,
position=InfoBarPosition.TOP,
duration=2000,
parent=self
)
cancel_btn = PushButton("取消")
cancel_btn.clicked.connect(category_dialog.reject)
btn_layout.addWidget(cancel_btn)
create_btn = PrimaryPushButton("创建")
create_btn.clicked.connect(on_create)
btn_layout.addWidget(create_btn)
layout.addLayout(btn_layout)
category_dialog.exec()
# 恢复之前的选择
index = self.query_category.findText(current_text)
if index >= 0:
self.query_category.setCurrentIndex(index)

View File

@@ -576,6 +576,45 @@ class FinanceManager:
return True
return False
def rename_category(self, account_id: str, old_name: str, new_name: str) -> bool:
"""重命名交易分类"""
account = self.accounts.get(account_id)
if not account:
return False
if old_name not in account.categories:
return False
if new_name in account.categories:
return False # 新分类名已存在
# 重命名分类
idx = account.categories.index(old_name)
account.categories[idx] = new_name
# 更新所有使用旧分类的交易
account_dir = self._get_account_dir(account_id)
for trans_dir in account_dir.iterdir():
if not trans_dir.is_dir() or trans_dir.name in ['invoice', 'payment', 'purchase']:
continue
data_file = trans_dir / 'data.json'
if data_file.exists():
try:
with open(data_file, 'r', encoding='utf-8') as f:
transaction_data = json.load(f)
if transaction_data.get('category') == old_name:
transaction_data['category'] = new_name
with open(data_file, 'w', encoding='utf-8') as f:
json.dump(transaction_data, f, ensure_ascii=False, indent=2)
except Exception as e:
print(f"更新交易分类出错: {e}")
account.updated_at = datetime.now().isoformat()
self._save_account_metadata(account)
return True
def delete_category(self, account_id: str, category: str) -> bool:
"""删除交易分类"""
account = self.accounts.get(account_id)
@@ -584,6 +623,26 @@ class FinanceManager:
if category in account.categories:
account.categories.remove(category)
# 清除所有使用此分类的交易的分类字段
account_dir = self._get_account_dir(account_id)
for trans_dir in account_dir.iterdir():
if not trans_dir.is_dir() or trans_dir.name in ['invoice', 'payment', 'purchase']:
continue
data_file = trans_dir / 'data.json'
if data_file.exists():
try:
with open(data_file, 'r', encoding='utf-8') as f:
transaction_data = json.load(f)
if transaction_data.get('category') == category:
transaction_data['category'] = ""
with open(data_file, 'w', encoding='utf-8') as f:
json.dump(transaction_data, f, ensure_ascii=False, indent=2)
except Exception as e:
print(f"清除交易分类出错: {e}")
account.updated_at = datetime.now().isoformat()
self._save_account_metadata(account)
return True