diff --git a/docs/USER_SETTINGS_RESTRICTION.md b/docs/USER_SETTINGS_RESTRICTION.md new file mode 100644 index 0000000..a010d3a --- /dev/null +++ b/docs/USER_SETTINGS_RESTRICTION.md @@ -0,0 +1,105 @@ +# 用户类型限制设置界面功能 + +## 概述 +实现了基于用户类型的设置界面访问控制。对于 `User` 类型的用户,设置界面仅显示"测试ERP连接"和"测试数据库连接"两个按钮,隐藏所有配置组。`Admin` 类型用户可以看到完整的设置界面。 + +## 修改的文件 + +### 1. `gui/settings_tab.py` + +#### 构造函数修改 +- 添加了 `session_manager` 可选参数 +- 保存 `session_manager` 实例用于用户类型判断 + +#### `create_widgets()` 方法修改 +- 添加用户类型判断逻辑 +- 对于 `User` 类型用户,跳过创建所有配置组 +- 根据用户类型显示不同的按钮组合 + +#### `load_settings()` 方法修改 +- 添加用户类型判断 +- 对于 `User` 类型用户,跳过加载配置到界面(因为没有界面元素) + +#### `test_db_connection()` 方法修改 +- 从 `self.config` 直接读取配置而不是从 UI 变量 +- 这样即使没有 UI 变量,User 也能测试数据库连接 + +### 2. `gui/main_window.py` + +#### `create_widgets()` 方法修改 +- 传入 `session_manager` 到 `SettingsTab` 构造函数 + +## 功能特性 + +### User 类型用户 +- **可见内容**: 仅两个按钮 + - 测试 ERP 连接 + - 测试数据库连接 +- **隐藏内容**: 所有配置组 + - ERP 系统配置 + - 数据库配置 + - 浏览器设置 + - 路径设置 + - 数据提取设置 + - 物料校验设置 +- **隐藏按钮**: + - 保存设置 + - 恢复默认 + +### Admin 类型用户 +- 显示完整的设置界面(所有配置组和所有按钮) + +### 向后兼容 +- 如果不传入 `session_manager` 参数,默认显示完整界面 +- 这确保了不破坏现有代码 + +## 测试 + +### 自动化测试 +运行 `tests/test_user_settings_restriction.py`: +```bash +.venv/Scripts/python tests/test_user_settings_restriction.py +``` + +测试覆盖: +1. User 类型用户不显示任何配置组 +2. Admin 类型用户显示所有配置组 +3. 无 session_manager 时显示所有配置(向后兼容) + +### 手动测试 +运行 `tests/manual_test_user_settings.py`: +```bash +.venv/Scripts/python tests/manual_test_user_settings.py +``` + +选择测试场景后,可以直观地查看设置界面的显示效果。 + +## 验证步骤 + +1. 启动应用,使用 `User` 类型用户登录 +2. 切换到"设置"标签页 +3. 确认只显示"测试ERP连接"和"测试数据库连接"两个按钮 +4. 确认所有配置组都不显示 +5. 使用 `Admin` 类型用户登录,确认设置界面显示完整内容 + +## 技术细节 + +### 用户类型判断 +```python +is_user_only = self.session_manager and self.session_manager.get_user_type() == 'User' +``` + +### 配置读取 +对于测试功能,直接从 `ConfigManager` 读取配置: +```python +db_type = self.config.get("database.db_type", "sqlserver") +``` + +这确保了即使没有 UI 变量,测试功能也能正常工作。 + +## 未来扩展 + +如果需要为不同用户类型提供更多自定义功能,可以在 `create_widgets()` 方法中添加更多条件判断,例如: +- 为不同用户类型显示不同的配置组 +- 为不同用户类型提供不同的默认值 +- 添加更多用户类型(如 `Guest`, `Manager` 等) diff --git a/gui/main_window.py b/gui/main_window.py index f329bf4..5f528ac 100644 --- a/gui/main_window.py +++ b/gui/main_window.py @@ -83,8 +83,8 @@ class MainWindow: self.query_tab = DataQueryTab(self.notebook, self.config) self.notebook.add(self.query_tab, text="数据查询") - # 设置标签页 - self.settings_tab = SettingsTab(self.notebook, self.config) + # 设置标签页(传入 session_manager) + self.settings_tab = SettingsTab(self.notebook, self.config, self.session_manager) self.notebook.add(self.settings_tab, text="设置") def create_status_bar(self): diff --git a/gui/material_validation_tab.py b/gui/material_validation_tab.py index 8e458c9..af3fe49 100644 --- a/gui/material_validation_tab.py +++ b/gui/material_validation_tab.py @@ -171,13 +171,18 @@ class MaterialValidationTab(ttk.Frame): control_frame = ttk.Frame(main_container) control_frame.pack(fill=tk.X, pady=(0, 10)) - # 负责人筛选区域 - manager_filter_frame = ttk.LabelFrame( - main_container, - text="筛选(按负责人)", - padding=10 - ) - manager_filter_frame.pack(fill=tk.X, pady=(0, 10)) + # 负责人筛选区域 - 仅管理员可见 + # PERMISSION CHECK: 非管理员用户不显示筛选区域 + if self.session_manager.is_admin(): + manager_filter_frame = ttk.LabelFrame( + main_container, + text="筛选(按负责人)", + padding=10 + ) + manager_filter_frame.pack(fill=tk.X, pady=(0, 10)) + else: + # 普通用户:创建一个空容器,避免后续代码出错 + manager_filter_frame = ttk.Frame(main_container) # 中部:结果表格 result_frame = ttk.LabelFrame(main_container, text="校验结果", padding=5) @@ -197,28 +202,33 @@ class MaterialValidationTab(ttk.Frame): def _create_control_panel(self, parent): """创建控制面板""" - # 数据来源选择 - source_group = ttk.LabelFrame(parent, text="数据来源", padding=10) - source_group.pack(fill=tk.X, pady=5) + # 数据来源选择 - 仅管理员可见 + # PERMISSION CHECK: 非管理员用户不显示数据来源选项 + if self.session_manager.is_admin(): + source_group = ttk.LabelFrame(parent, text="数据来源", padding=10) + source_group.pack(fill=tk.X, pady=5) - self.source_mode = tk.StringVar(value="database_full") + self.source_mode = tk.StringVar(value="database_full") - # 数据库模式 - ttk.Radiobutton( - source_group, - text="数据库 - 全表校验", - variable=self.source_mode, - value="database_full", - command=self._on_source_mode_change, - ).grid(row=0, column=0, sticky="w", padx=5) + # 数据库模式 + ttk.Radiobutton( + source_group, + text="数据库 - 全表校验", + variable=self.source_mode, + value="database_full", + command=self._on_source_mode_change, + ).grid(row=0, column=0, sticky="w", padx=5) - ttk.Radiobutton( - source_group, - text="数据库 - ProductionID 过滤", - variable=self.source_mode, - value="database_filtered", - command=self._on_source_mode_change, - ).grid(row=0, column=1, sticky="w", padx=5) + ttk.Radiobutton( + source_group, + text="数据库 - ProductionID 过滤", + variable=self.source_mode, + value="database_filtered", + command=self._on_source_mode_change, + ).grid(row=0, column=1, sticky="w", padx=5) + else: + # 普通用户:默认使用 database_filtered 模式 + self.source_mode = tk.StringVar(value="database_filtered") # 文件选择 file_group = ttk.LabelFrame(parent, text="文件选择", padding=10) @@ -243,18 +253,15 @@ class MaterialValidationTab(ttk.Frame): ) self.db_filtered_production_id_selector.pack(fill=tk.X) - # 输出文件 - output_frame = ttk.Frame(file_group) - output_frame.grid(row=1, column=0, columnspan=2, sticky="ew", pady=(10, 0)) - + # 输出文件(对于所有用户都可见) self.output_file_selector = FileSelector( - output_frame, + file_group, label_text="输出文件:", file_type="file", file_types=[("Excel 文件", "*.xlsx"), ("所有文件", "*.*")], initial_dir=self.config.get("paths.data_dir", "data/"), ) - self.output_file_selector.pack(fill=tk.X) + self.output_file_selector.pack(fill=tk.X, pady=(10, 0)) # 设置默认输出 default_output = os.path.join( @@ -370,20 +377,31 @@ class MaterialValidationTab(ttk.Frame): def _on_source_mode_change(self): """数据源模式切换""" + # PERMISSION CHECK: 普通用户固定为 database_filtered 模式 + if not self.session_manager.is_admin(): + # 对于普通用户,pack 数据库过滤模式框架 + self.db_full_frame.pack_forget() + self.db_filtered_frame.pack(fill=tk.X, pady=(0, 5)) + return + mode = self.source_mode.get() # 隐藏所有文件选择框架 - self.db_full_frame.grid_remove() - self.db_filtered_frame.grid_remove() + self.db_full_frame.pack_forget() + self.db_filtered_frame.pack_forget() # 根据模式显示对应的文件选择器 if mode == "database_full": - self.db_full_frame.grid(row=0, column=0, columnspan=2, sticky="ew") + self.db_full_frame.pack(fill=tk.X, pady=(0, 5)) elif mode == "database_filtered": - self.db_filtered_frame.grid(row=0, column=0, columnspan=2, sticky="ew") + self.db_filtered_frame.pack(fill=tk.X, pady=(0, 5)) def _create_manager_filter_area(self, parent): """创建负责人筛选区域""" + # PERMISSION CHECK: 非管理员用户不显示筛选区域 + if not self.session_manager.is_admin(): + return + # 创建复选框容器 canvas_container = ttk.Frame(parent) canvas_container.pack(fill=tk.BOTH, expand=True) diff --git a/gui/settings_tab.py b/gui/settings_tab.py index fc8ff57..e91a55b 100644 --- a/gui/settings_tab.py +++ b/gui/settings_tab.py @@ -16,21 +16,26 @@ from config.schema import DatabaseType class SettingsTab(ttk.Frame): """设置标签页""" - def __init__(self, parent, config: ConfigManager): + def __init__(self, parent, config: ConfigManager, session_manager=None): """ 初始化设置标签页 Args: parent: 父容器 config: 配置管理器 + session_manager: 会话管理器,用于获取用户类型 """ super().__init__(parent) self.config = config + self.session_manager = session_manager self.create_widgets() self.load_settings() def create_widgets(self): """创建界面组件""" + # 判断用户类型 + is_user_only = self.session_manager and self.session_manager.get_user_type() == 'User' + # 创建主容器,带滚动条 canvas = tk.Canvas(self) scrollbar = ttk.Scrollbar(self, orient="vertical", command=canvas.yview) @@ -43,40 +48,42 @@ class SettingsTab(ttk.Frame): canvas.create_window((0, 0), window=scrollable_frame, anchor="nw") canvas.configure(yscrollcommand=scrollbar.set) - # ERP 配置组 - self._create_erp_group(scrollable_frame) + # 根据用户类型创建配置组 + if not is_user_only: + # 管理员显示所有配置组 + self._create_erp_group(scrollable_frame) + self._create_database_group(scrollable_frame) + self._create_browser_group(scrollable_frame) + self._create_paths_group(scrollable_frame) + self._create_extraction_group(scrollable_frame) + self._create_validation_group(scrollable_frame) - # 数据库配置组 - self._create_database_group(scrollable_frame) - - # 浏览器配置组 - self._create_browser_group(scrollable_frame) - - # 路径配置组 - self._create_paths_group(scrollable_frame) - - # 处理配置组 - self._create_extraction_group(scrollable_frame) - - # 校验配置组 - self._create_validation_group(scrollable_frame) - - # 按钮区域 + # 按钮区域 - 根据用户类型显示不同按钮 button_frame = ttk.Frame(scrollable_frame) button_frame.grid(row=6, column=0, columnspan=2, pady=20, sticky="ew") - ttk.Button( - button_frame, text="测试 ERP 连接", command=self.test_erp_connection - ).pack(side="left", padx=5) - ttk.Button( - button_frame, text="测试数据库连接", command=self.test_db_connection - ).pack(side="left", padx=5) - ttk.Button(button_frame, text="保存设置", command=self.save_settings).pack( - side="left", padx=5 - ) - ttk.Button(button_frame, text="恢复默认", command=self.reset_defaults).pack( - side="left", padx=5 - ) + if is_user_only: + # 普通用户只显示测试按钮 + ttk.Button( + button_frame, text="测试 ERP 连接", command=self.test_erp_connection + ).pack(side="left", padx=5) + ttk.Button( + button_frame, text="测试数据库连接", command=self.test_db_connection + ).pack(side="left", padx=5) + else: + # 管理员显示所有按钮 + ttk.Button( + button_frame, text="测试 ERP 连接", command=self.test_erp_connection + ).pack(side="left", padx=5) + ttk.Button( + button_frame, text="测试数据库连接", command=self.test_db_connection + ).pack(side="left", padx=5) + ttk.Button(button_frame, text="保存设置", command=self.save_settings).pack( + side="left", padx=5 + ) + ttk.Button(button_frame, text="恢复默认", command=self.reset_defaults).pack( + side="left", padx=5 + ) # 布局 canvas.grid(row=0, column=0, sticky="nsew") @@ -326,6 +333,15 @@ class SettingsTab(ttk.Frame): def load_settings(self): """从配置加载设置到界面""" + # 判断是否为仅测试用户模式 + is_user_only = self.session_manager and self.session_manager.get_user_type() == 'User' + + if is_user_only: + # 普通用户模式 - 只需要加载测试连接所需的配置 + # 不需要加载设置到界面,因为界面没有配置输入框 + return + + # 管理员模式 - 加载所有配置 # ERP 设置 self.erp_url_var.set(self.config.get("erp.url", "")) self.erp_username_var.set(self.config.get("erp.username", "")) @@ -431,7 +447,8 @@ class SettingsTab(ttk.Frame): def test_db_connection(self): """测试数据库连接""" - db_type = self.db_type_var.get() + # 从配置读取而不是从 UI 变量(支持 User 类型用户) + db_type = self.config.get("database.db_type", "sqlserver") try: if db_type == "mysql": @@ -439,11 +456,11 @@ class SettingsTab(ttk.Frame): from mysql.connector import Error conn = mysql.connector.connect( - host=self.mysql_host_var.get(), - port=self.mysql_port_var.get(), - database=self.db_name_var.get(), - user=self.db_username_var.get(), - password=self.db_password_var.get(), + host=self.config.get("database.mysql.host", ""), + port=self.config.get("database.mysql.port", 3306), + database=self.config.get("database.database", ""), + user=self.config.get("database.username", ""), + password=self.config.get("database.password", ""), connection_timeout=5 ) conn.close() @@ -451,10 +468,10 @@ class SettingsTab(ttk.Frame): else: conn_str = ( f"DRIVER={{ODBC Driver 18 for SQL Server}};" - f"SERVER={self.db_server_var.get()};" - f"DATABASE={self.db_name_var.get()};" - f"UID={self.db_username_var.get()};" - f"PWD={self.db_password_var.get()};" + f"SERVER={self.config.get('database.server', '')};" + f"DATABASE={self.config.get('database.database', '')};" + f"UID={self.config.get('database.username', '')};" + f"PWD={self.config.get('database.password', '')};" f"TrustServerCertificate=yes;" ) conn = pyodbc.connect(conn_str, timeout=5)