From 6a5702cf91c9bc0fb0207624c446b3fbc47153d8 Mon Sep 17 00:00:00 2001 From: samhandsome Date: Mon, 17 Feb 2025 13:23:20 +0800 Subject: [PATCH] =?UTF-8?q?[feature]=20=E7=A7=BB=E9=99=A4=E9=85=8D?= =?UTF-8?q?=E7=BD=AE=E9=80=89=E9=A1=B9=EF=BC=8C=E4=B8=BA=E9=A1=B9=E7=9B=AE?= =?UTF-8?q?=E7=AE=A1=E7=90=86=E5=81=9A=E5=87=86=E5=A4=87?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- data/database.db | Bin 20480 -> 28672 bytes .../__pycache__/db_manager.cpython-313.pyc | Bin 11344 -> 15324 bytes database/__pycache__/models.cpython-313.pyc | Bin 1600 -> 2056 bytes database/database_manager.py | 1 + database/db_manager.py | 105 +++++++++++++++--- database/models.py | 9 ++ widget_file_list.zip | Bin 4321 -> 0 bytes .../audio_filter_widget.cpython-313.pyc | Bin 29897 -> 28223 bytes .../__pycache__/avas_widget.cpython-313.pyc | Bin 14599 -> 17430 bytes .../project_dialog.cpython-313.pyc | Bin 0 -> 3266 bytes widgets/audio_filter_widget.py | 53 +++------ widgets/avas_widget.py | 73 ++++++++++-- widgets/project_dialog.py | 46 ++++++++ 13 files changed, 223 insertions(+), 64 deletions(-) create mode 100644 database/database_manager.py delete mode 100644 widget_file_list.zip create mode 100644 widgets/__pycache__/project_dialog.cpython-313.pyc create mode 100644 widgets/project_dialog.py diff --git a/data/database.db b/data/database.db index 7da05a035eb7126fd662c8460fb6b6f780c60153..770824ae4250650b9d3cad5f4a7902334eb9782d 100644 GIT binary patch delta 549 zcmZozz}WDBae}m<7Xt$WI}pPF>qH%6NiPPyvTk0!UIsSyYzFph{*yeNTv{A&IDc+z z{LIGHRL96JE-lU28a#PA-)9kpf};Ga)Z~)*%oGJrzYte<*C2(-8@WU$FW_bo4{~*L z4RZB!b`6FpE7r(N(VYB{OKkFdel3$GE>?DNSy{&B^peD+oK&b$#W0%9Imp#9#8n~0 z(aFaZX2Rrsd|~Vgp?;o$p{^i<1X5CqlZ!G7N;32F6hd4hLcpd+^Xo})DWCyOeKt06 zXW7Z;`6Yy5cElUPSnQj%xRO~|_8-XSO)%k{D=7G@>lZ5ZWa{q z=C7AxWo6*!H!dtRGB7gLH89dOG*>V%vNATdGBQwNVP%jOHZDmmE`b1KH3n9u=2pfQ z%FL_`lHy2eO5lpj6bwzQj7+VJ%%zyXmXu)1nHd@>FtRd;ib6~P+JvUv$jZow3+(o* z4E$I5Kk@(If6jkWae}m{3m%jxwJM53T)%t Tti_eg!qQ~P$i8_ZU%n#%^5+jT diff --git a/database/__pycache__/db_manager.cpython-313.pyc b/database/__pycache__/db_manager.cpython-313.pyc index d41ab2b39d93f9cfca36dade33de6e588c4ad7fc..e502e4db6c3e0f183003a9c3ddecae262182d8f7 100644 GIT binary patch delta 4987 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 L$Xy|v$Wf?bi3Qk*#Z(s zHM{^Z!R8Bz7z18jjnQnPhA0L!#wXpz#5NmV)E9}E6iU*h`rv$L3u4oMz{&1!X1@7m zzRS$_JNM4yhAk^*vyt%j)vupUrt0P`UOF-#-skZXF9{M^=OePdRf7tIFw^{{7Anl& z3N?=vBn4FW^WjR4!*Ozrj1Wd;!Zb2vf~;Yp>;WH*!yDAoYZx&{jIzx~PKnGZ+xbi* zJJgv=HjbEO%ZM!^%1%|{mR&x=JhGdWL`fHA-fAM1WOr%28ft>5I9Orf_VSj|L@JRP z9gk&VCt^xm(&mM+q{1w4TWp|x@Ib7gPH<>fX#kA69W(^Jx>C(!0wcP+&{o${wHgF&QQ-nS?X%0dnA#ya-F<>-TAy> zBAyvL8A~P^-YQ?lDA+dC(T&YACC%~zOP^6hF7flmt(|nY(tfz%K1P-7nmZ`a?rcS= zlRC^?<-P%-h=XKgdttY)gLcCOUtj>OvUU#Z7SpLXLp9ccGF@zDCY~B+xH~VNNT-vm zj1MJ4!JTeYM4wnE{NnRFck!Wuy66!$?v<;ofga56E*qt_dr|WhgrgkBv>cu^%$>mh zD_2CI+iC=DR11Mf2~0Jcw7RHnNDDOLD^Ka8ddvg1*pGxn^|A&}Ng0MO!r_X5`Qnoj z@e}m`cUXuTBJ>%l=p+MI`HrID%Skk-wt{Y{7%dvY*R-KUi@@)9(;~15uA^K9ecjNw z)V4vyr(EjTRO;GH3Nw{xmVGuTmAHPvnWqF!}ppu&(3g;ijU63SZrG1#n4k-GRF?#`VfMQN=3<- zlHv807-xr3*pILeVE|!22VRHi>FGp<7Zkop_A0`EpZI!hpt3c7Hlbt^smW1JL=%b0 zf-l7riDV|uM%~Do3Rb#WHY)Jixn13$JI95J7amso980zXOLlk87F^K^9?PZvWdl^# z{7}|Qi6yjP4lego;;Xz_e!YCjbzsR|nse2HwRSBuhwF>xApfl?PJZ0{p5}pw@NIEl z74N=}824+k_2FyOQ_I&ROMi%d&5lansxPdS=`ulNYFuba zGP5S==#)BA`2J`c%k*x-WpLhFx5DHW`18U4{=kgC(sCq;*xC N5vWWb5w%l|`~xgc&y)ZF diff --git a/database/__pycache__/models.cpython-313.pyc b/database/__pycache__/models.cpython-313.pyc index 3abf0a6aa89b26a7c8c3ff5b54d48cffb938f2d8..b3eeef57844807887fd0bf51ff8dd9ded0746688 100644 GIT binary patch delta 546 zcmX|7Jxjwt7`{vH(llvGq^;C~sI@97epEz9C&4eMLd5Anq}EUeE7z`~&_Ttei%QbAmuR2mSNQvTXw9r5*gRquAv6tm6D_{zVEv1BBPUA$ diff --git a/database/database_manager.py b/database/database_manager.py new file mode 100644 index 0000000..0519ecb --- /dev/null +++ b/database/database_manager.py @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/database/db_manager.py b/database/db_manager.py index d4ba4cd..1f71d8c 100644 --- a/database/db_manager.py +++ b/database/db_manager.py @@ -2,7 +2,7 @@ import sqlite3 from typing import List, Optional, Tuple from pathlib import Path import os -from .models import FilterData, ParamData, ConfigData +from .models import FilterData, ParamData, ConfigData, ProjectData class DatabaseManager: _instance = None @@ -27,15 +27,50 @@ class DatabaseManager: Path(self.db_path).parent.mkdir(parents=True, exist_ok=True) with sqlite3.connect(self.db_path) as conn: - conn.executescript(""" - CREATE TABLE IF NOT EXISTS configs ( + # 首先创建 projects 表(如果不存在) + conn.execute(""" + CREATE TABLE IF NOT EXISTS projects ( id INTEGER PRIMARY KEY, - name TEXT NOT NULL, - channel_id INTEGER NOT NULL, - created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - UNIQUE(name, channel_id) + name TEXT NOT NULL UNIQUE, + description TEXT, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ); - + """) + + # 检查 configs 表是否存在 + cursor = conn.execute(""" + SELECT name FROM sqlite_master + WHERE type='table' AND name='configs'; + """) + + if cursor.fetchone() is None: + # 如果表不存在,创建新表 + conn.execute(""" + CREATE TABLE configs ( + id INTEGER PRIMARY KEY, + name TEXT NOT NULL, + channel_id INTEGER NOT NULL, + project_id INTEGER, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + UNIQUE(name, channel_id, project_id), + FOREIGN KEY (project_id) REFERENCES projects(id) + ); + """) + else: + # 如果表已存在,检查是否需要添加 project_id 列 + cursor = conn.execute("PRAGMA table_info(configs)") + columns = [row[1] for row in cursor.fetchall()] + + if 'project_id' not in columns: + # 添加 project_id 列 + conn.execute(""" + ALTER TABLE configs + ADD COLUMN project_id INTEGER + REFERENCES projects(id); + """) + + # 其他表的创建保持不变 + conn.executescript(""" CREATE TABLE IF NOT EXISTS params ( id INTEGER PRIMARY KEY, config_id INTEGER, @@ -119,12 +154,14 @@ class DatabaseManager: print(f"load_config filters: {filters}") return params, filters - def save_config(self, name: str, channel_id: int, params: ParamData, filters: List[FilterData]) -> int: + def save_config(self, name: str, channel_id: int, params: ParamData, filters: List[FilterData], project_id: Optional[int] = None) -> int: with self.get_connection() as conn: # 检查是否存在相同的配置 cursor = conn.execute( - "SELECT id FROM configs WHERE name = ? AND channel_id = ?", - (name, channel_id) + """SELECT id FROM configs + WHERE name = ? AND channel_id = ? AND + (project_id = ? OR (project_id IS NULL AND ? IS NULL))""", + (name, channel_id, project_id, project_id) ) existing_config = cursor.fetchone() @@ -154,10 +191,10 @@ class DatabaseManager: return config_id else: - # 创建新配置(原有的插入逻辑) + # 创建新配置 cursor = conn.execute( - "INSERT INTO configs (name, channel_id) VALUES (?, ?)", - (name, channel_id) + "INSERT INTO configs (name, channel_id, project_id) VALUES (?, ?, ?)", + (name, channel_id, project_id) ) config_id = cursor.lastrowid @@ -245,3 +282,43 @@ class DatabaseManager: # 保存默认配置 self.save_config("Default Config", 1, default_params, default_filters) + + def add_project(self, name: str, description: str) -> int: + with self.get_connection() as conn: + cursor = conn.execute( + "INSERT INTO projects (name, description) VALUES (?, ?)", + (name, description) + ) + conn.commit() + return cursor.lastrowid + + def get_all_projects(self) -> List[ProjectData]: + with self.get_connection() as conn: + cursor = conn.execute( + "SELECT id, name, description, created_at FROM projects" + ) + return [ProjectData(*row) for row in cursor.fetchall()] + + def update_project_name(self, project_id: int, new_name: str) -> bool: + """更新项目名称""" + try: + with self.get_connection() as conn: + cursor = conn.execute( + "UPDATE projects SET name = ? WHERE id = ?", + (new_name, project_id) + ) + conn.commit() + return cursor.rowcount > 0 + except sqlite3.IntegrityError: + # 项目名已存在 + return False + + def get_project_configs(self, project_id: int) -> List[ConfigData]: + """获取项目关联的所有配置""" + with self.get_connection() as conn: + cursor = conn.execute( + """SELECT id, name, channel_id, created_at, project_id + FROM configs WHERE project_id = ?""", + (project_id,) + ) + return [ConfigData(*row) for row in cursor.fetchall()] diff --git a/database/models.py b/database/models.py index 8ceb411..cb8e8ab 100644 --- a/database/models.py +++ b/database/models.py @@ -30,3 +30,12 @@ class ConfigData: name: str channel_id: int created_at: str + project_id: Optional[int] = None + +@dataclass +class ProjectData: + id: Optional[int] + name: str + description: str + created_at: str = None + configs: List[ConfigData] = None diff --git a/widget_file_list.zip b/widget_file_list.zip deleted file mode 100644 index 7a252b0b7845625004983cfd719d790a5899e2d9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4321 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 LmVZlBirN1Kl7ClE delta 5833 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 DyTM*y delta 1955 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 EFU2B>WB>pF literal 0 HcmV?d00001 diff --git a/widgets/audio_filter_widget.py b/widgets/audio_filter_widget.py index 0abd8f6..4ce89ff 100644 --- a/widgets/audio_filter_widget.py +++ b/widgets/audio_filter_widget.py @@ -3,12 +3,13 @@ from PySide6.QtCore import Qt from widgets.Ui_widget import Ui_Widget from database.db_manager import DatabaseManager from database.models import FilterData, ParamData, ConfigData -from typing import List +from typing import List, Optional class AudioFilterModel: def __init__(self, db_manager: DatabaseManager): self.db_manager = db_manager self.current_config_id: Optional[int] = None + self.current_project_id: Optional[int] = None # 添加当前项目ID self.current_channel_id: int = 1 # 添加当前通道ID self.filters: List[FilterData] = [] self.params: ParamData = ParamData( @@ -38,7 +39,13 @@ class AudioFilterModel: for filter_data in self.filters: print(f"save_config filter_data.channel_id: {filter_data.channel_id}") filter_data.channel_id = self.current_channel_id - return self.db_manager.save_config(name, self.current_channel_id, self.params, self.filters) + return self.db_manager.save_config( + name, + self.current_channel_id, + self.params, + self.filters, + self.current_project_id # 添加项目ID + ) def update_filter(self, index: int, **kwargs): if 0 <= index < len(self.filters): @@ -60,6 +67,10 @@ class AudioFilterModel: if self.current_config_id: self.db_manager.update_params(self.current_config_id, self.params) + def set_project(self, project_id: Optional[int]): + """设置当前项目""" + self.current_project_id = project_id + class FilterTypeDialog(QDialog): def __init__(self, parent=None): super().__init__(parent) @@ -254,15 +265,7 @@ class AudioFilterWidget(QWidget): self.model = AudioFilterModel(self.db_manager) self.model.current_channel_id = channel_id # 设置当前通道ID - # 加载该通道的所有配置 - configs = self.db_manager.get_all_configs() - if configs: - # 获取当前通道的所有配置 - channel_configs = [config for config in configs if config.channel_id == channel_id] - if channel_configs: - # 可以让用户选择要加载哪个配置 - self.show_config_selection_dialog(channel_configs) - + # 移除配置选择相关代码 self.controller = AudioFilterController(self.model, self) self.update_view() @@ -445,31 +448,3 @@ class AudioFilterWidget(QWidget): except ValueError: print("输入的值无效,请输入数字") - def show_config_selection_dialog(self, configs: List[ConfigData]): - dialog = QDialog(self) - dialog.setWindowTitle("选择配置") - layout = QVBoxLayout() - - combo = QComboBox() - for config in configs: - combo.addItem(f"{config.name} (创建于: {config.created_at})", config.id) - - layout.addWidget(QLabel("请选择要加载的配置:")) - layout.addWidget(combo) - - buttons = QHBoxLayout() - ok_button = QPushButton("确定") - cancel_button = QPushButton("取消") - - ok_button.clicked.connect(dialog.accept) - cancel_button.clicked.connect(dialog.reject) - - buttons.addWidget(ok_button) - buttons.addWidget(cancel_button) - layout.addLayout(buttons) - - dialog.setLayout(layout) - - if dialog.exec() == QDialog.Accepted: - selected_config_id = combo.currentData() - self.model.load_config(selected_config_id) diff --git a/widgets/avas_widget.py b/widgets/avas_widget.py index 223f712..dc74783 100644 --- a/widgets/avas_widget.py +++ b/widgets/avas_widget.py @@ -1,10 +1,13 @@ import sys from PySide6.QtWidgets import (QApplication, QWidget, QPushButton, QGridLayout, QVBoxLayout, QHBoxLayout, QCheckBox, QComboBox, - QLabel, QSlider, QFrame, QGroupBox, QListView) + QLabel, QSlider, QFrame, QGroupBox, QListView, QListWidgetItem, QMessageBox) from PySide6.QtCore import Qt, QSize -from PySide6.QtGui import QIcon +from PySide6.QtGui import QIcon, QStandardItemModel, QStandardItem from widgets.audio_filter_widget import AudioFilterWidget +from widgets.project_dialog import AddProjectDialog +import sqlite3 +from database.db_manager import DatabaseManager # 添加这行导入 class ChannelButton(QWidget): def __init__(self, channel_num): @@ -120,9 +123,13 @@ class ChannelButton(QWidget): class AVAS_WIDGET(QWidget): def __init__(self): super().__init__() + # 初始化数据库管理器 + self.db_manager = DatabaseManager() + self.setWindowTitle("AVAS") self.is_panel_visible = True # 添加状态标记 self.setup_ui() + self.setup_connections() def setup_ui(self): # 创建主水平布局 @@ -241,9 +248,9 @@ class AVAS_WIDGET(QWidget): # 添加工具栏按钮 toolbar = QHBoxLayout() - add_btn = QPushButton("添加") - delete_btn = QPushButton("删除") - refresh_btn = QPushButton("刷新") + self.add_btn = QPushButton("添加") + self.delete_btn = QPushButton("删除") + self.refresh_btn = QPushButton("刷新") # 设置按钮样式 button_style = """ @@ -262,13 +269,13 @@ class AVAS_WIDGET(QWidget): background-color: #303030; } """ - add_btn.setStyleSheet(button_style) - delete_btn.setStyleSheet(button_style) - refresh_btn.setStyleSheet(button_style) + self.add_btn.setStyleSheet(button_style) + self.delete_btn.setStyleSheet(button_style) + self.refresh_btn.setStyleSheet(button_style) - toolbar.addWidget(add_btn) - toolbar.addWidget(delete_btn) - toolbar.addWidget(refresh_btn) + toolbar.addWidget(self.add_btn) + toolbar.addWidget(self.delete_btn) + toolbar.addWidget(self.refresh_btn) # 组装面板 layout.addLayout(toolbar) @@ -339,3 +346,47 @@ class AVAS_WIDGET(QWidget): frame.setLayout(layout) return frame + def setup_connections(self): + # 假设添加按钮的变量名是 add_btn + self.add_btn.clicked.connect(self.add_project) + + def add_project(self): + dialog = AddProjectDialog(self) + if dialog.exec(): + name, description = dialog.get_project_data() + + # 验证输入 + if not name: + QMessageBox.warning(self, "警告", "项目名称不能为空!") + return + + try: + # 保存到数据库 + project_id = self.db_manager.add_project(name, description) + + # 更新项目列表 + self.update_project_list() + + QMessageBox.information(self, "成功", "项目添加成功!") + + except sqlite3.IntegrityError: + QMessageBox.warning(self, "警告", "项目名称已存在!") + except Exception as e: + QMessageBox.critical(self, "错误", f"添加项目失败:{str(e)}") + + def update_project_list(self): + """更新项目列表显示""" + # 创建新的模型 + model = QStandardItemModel() + self.project_list.setModel(model) + + # 获取所有项目 + projects = self.db_manager.get_all_projects() + + # 创建项目列表项 + for project in projects: + item = QStandardItem(project.name) + item.setData(project.id, Qt.UserRole) # 存储项目ID + item.setToolTip(project.description) # 显示项目描述为提示 + model.appendRow(item) + diff --git a/widgets/project_dialog.py b/widgets/project_dialog.py new file mode 100644 index 0000000..51ec510 --- /dev/null +++ b/widgets/project_dialog.py @@ -0,0 +1,46 @@ +from PySide6.QtWidgets import QDialog, QVBoxLayout, QHBoxLayout, QLabel, QLineEdit, QTextEdit, QPushButton, QMessageBox +from PySide6.QtCore import Qt +from typing import Tuple + +class AddProjectDialog(QDialog): + def __init__(self, parent=None): + super().__init__(parent) + self.setup_ui() + + def setup_ui(self): + self.setWindowTitle("添加项目") + layout = QVBoxLayout() + + # 项目名称输入 + self.name_label = QLabel("项目名称:") + self.name_edit = QLineEdit() + self.name_edit.setPlaceholderText("请输入项目名称") + + # 项目描述输入 + self.desc_label = QLabel("项目描述:") + self.desc_edit = QTextEdit() + self.desc_edit.setPlaceholderText("请输入项目描述") + + # 按钮 + self.btn_layout = QHBoxLayout() + self.ok_btn = QPushButton("确定") + self.cancel_btn = QPushButton("取消") + + self.btn_layout.addWidget(self.ok_btn) + self.btn_layout.addWidget(self.cancel_btn) + + # 添加到主布局 + layout.addWidget(self.name_label) + layout.addWidget(self.name_edit) + layout.addWidget(self.desc_label) + layout.addWidget(self.desc_edit) + layout.addLayout(self.btn_layout) + + self.setLayout(layout) + + # 连接信号 + self.ok_btn.clicked.connect(self.accept) + self.cancel_btn.clicked.connect(self.reject) + + def get_project_data(self) -> Tuple[str, str]: + return self.name_edit.text().strip(), self.desc_edit.toPlainText().strip() \ No newline at end of file