feat: add Production ID input widget and UI configuration
- Add ProductionIdInput widget with placeholder and multi-line support - Add UIConfig class for font family, font size, and input width settings - Refactor data extraction tab to use horizontal PanedWindow layout - Left panel: Production ID text input (draggable width) - Right panel: control panel and log output - Share Production IDs between data extraction and material validation tabs - Add UI settings group in settings page (font selection, size, input width) - For User users: automatically use shared Production IDs, simplified UI - Apply font settings to input and log widgets - Use sashpos() to set initial pane width correctly Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -269,6 +269,34 @@ class ValidationConfig:
|
|||||||
return errors
|
return errors
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class UIConfig:
|
||||||
|
"""用户界面配置"""
|
||||||
|
|
||||||
|
font_family: str = "Microsoft YaHei UI"
|
||||||
|
font_size: int = 10
|
||||||
|
production_id_input_width: int = 20
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_env(cls) -> "UIConfig":
|
||||||
|
"""从环境变量创建配置"""
|
||||||
|
from config.env_loader import get_env, get_env_int
|
||||||
|
return cls(
|
||||||
|
font_family=get_env("UI_FONT_FAMILY", "Microsoft YaHei UI"),
|
||||||
|
font_size=get_env_int("UI_FONT_SIZE", 10),
|
||||||
|
production_id_input_width=get_env_int("UI_PRODUCTION_ID_INPUT_WIDTH", 20),
|
||||||
|
)
|
||||||
|
|
||||||
|
def validate(self) -> list[str]:
|
||||||
|
"""验证配置,返回错误列表"""
|
||||||
|
errors = []
|
||||||
|
if self.font_size < 8 or self.font_size > 24:
|
||||||
|
errors.append("字号必须在 8-24 之间")
|
||||||
|
if self.production_id_input_width < 10 or self.production_id_input_width > 100:
|
||||||
|
errors.append("输入框宽度必须在 10-100 之间")
|
||||||
|
return errors
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class AppConfig:
|
class AppConfig:
|
||||||
"""应用总配置"""
|
"""应用总配置"""
|
||||||
@@ -278,6 +306,7 @@ class AppConfig:
|
|||||||
paths: PathConfig
|
paths: PathConfig
|
||||||
extraction: ExtractionConfig
|
extraction: ExtractionConfig
|
||||||
validation: ValidationConfig
|
validation: ValidationConfig
|
||||||
|
ui: UIConfig
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_env(cls) -> "AppConfig":
|
def from_env(cls) -> "AppConfig":
|
||||||
@@ -288,6 +317,7 @@ class AppConfig:
|
|||||||
paths=PathConfig.from_env(),
|
paths=PathConfig.from_env(),
|
||||||
extraction=ExtractionConfig.from_env(),
|
extraction=ExtractionConfig.from_env(),
|
||||||
validation=ValidationConfig.from_env(),
|
validation=ValidationConfig.from_env(),
|
||||||
|
ui=UIConfig.from_env(),
|
||||||
)
|
)
|
||||||
|
|
||||||
def validate(self) -> list[str]:
|
def validate(self) -> list[str]:
|
||||||
@@ -298,6 +328,7 @@ class AppConfig:
|
|||||||
errors.extend(self.paths.validate())
|
errors.extend(self.paths.validate())
|
||||||
errors.extend(self.extraction.validate())
|
errors.extend(self.extraction.validate())
|
||||||
errors.extend(self.validation.validate())
|
errors.extend(self.validation.validate())
|
||||||
|
errors.extend(self.ui.validate())
|
||||||
return errors
|
return errors
|
||||||
|
|
||||||
def to_dict(self) -> dict:
|
def to_dict(self) -> dict:
|
||||||
@@ -348,4 +379,9 @@ class AppConfig:
|
|||||||
"default_manager": self.validation.default_manager,
|
"default_manager": self.validation.default_manager,
|
||||||
"match_mode": self.validation.match_mode,
|
"match_mode": self.validation.match_mode,
|
||||||
},
|
},
|
||||||
|
"ui": {
|
||||||
|
"font_family": self.ui.font_family,
|
||||||
|
"font_size": self.ui.font_size,
|
||||||
|
"production_id_input_width": self.ui.production_id_input_width,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ import tkinter as tk
|
|||||||
from tkinter import ttk, filedialog, messagebox
|
from tkinter import ttk, filedialog, messagebox
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from contextlib import redirect_stdout
|
from contextlib import redirect_stdout
|
||||||
from gui.widgets import FileSelector, LogText
|
from gui.widgets import FileSelector, LogText, ProductionIdInput
|
||||||
from gui.config_manager import ConfigManager
|
from gui.config_manager import ConfigManager
|
||||||
from gui.progress import ProgressInfo, ProgressCalculator
|
from gui.progress import ProgressInfo, ProgressCalculator
|
||||||
from gui.utils import RealtimeOutput
|
from gui.utils import RealtimeOutput
|
||||||
@@ -23,16 +23,18 @@ from gui.utils import RealtimeOutput
|
|||||||
class DataExtractionTab(ttk.Frame):
|
class DataExtractionTab(ttk.Frame):
|
||||||
"""数据提取标签页"""
|
"""数据提取标签页"""
|
||||||
|
|
||||||
def __init__(self, parent, config: ConfigManager):
|
def __init__(self, parent, config: ConfigManager, main_window=None):
|
||||||
"""
|
"""
|
||||||
初始化数据提取标签页
|
初始化数据提取标签页
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
parent: 父容器
|
parent: 父容器
|
||||||
config: 配置管理器
|
config: 配置管理器
|
||||||
|
main_window: 主窗口引用,用于共享 Production ID 数据
|
||||||
"""
|
"""
|
||||||
super().__init__(parent)
|
super().__init__(parent)
|
||||||
self.config = config
|
self.config = config
|
||||||
|
self.main_window = main_window
|
||||||
self.extracting = False
|
self.extracting = False
|
||||||
self.extractor = None
|
self.extractor = None
|
||||||
self.extraction_thread = None
|
self.extraction_thread = None
|
||||||
@@ -44,6 +46,9 @@ class DataExtractionTab(ttk.Frame):
|
|||||||
|
|
||||||
self.create_widgets()
|
self.create_widgets()
|
||||||
|
|
||||||
|
# 应用字体设置
|
||||||
|
self._apply_ui_config()
|
||||||
|
|
||||||
# 稍后显示就绪消息
|
# 稍后显示就绪消息
|
||||||
try:
|
try:
|
||||||
self.log_text.info("数据提取标签页已就绪")
|
self.log_text.info("数据提取标签页已就绪")
|
||||||
@@ -52,9 +57,50 @@ class DataExtractionTab(ttk.Frame):
|
|||||||
|
|
||||||
def create_widgets(self):
|
def create_widgets(self):
|
||||||
"""创建界面组件"""
|
"""创建界面组件"""
|
||||||
# 主容器 - 使用 PanedWindow 分割上下部分
|
# 主容器 - 使用水平 PanedWindow 分割左右部分
|
||||||
main_paned = ttk.PanedWindow(self, orient=tk.VERTICAL)
|
horizontal_paned = ttk.PanedWindow(self, orient=tk.HORIZONTAL)
|
||||||
main_paned.pack(fill=tk.BOTH, expand=True, padx=10, pady=10)
|
horizontal_paned.pack(fill=tk.BOTH, expand=True, padx=10, pady=10)
|
||||||
|
|
||||||
|
# 左侧:Production ID 输入面板
|
||||||
|
left_panel = ttk.Frame(horizontal_paned)
|
||||||
|
horizontal_paned.add(left_panel, weight=0)
|
||||||
|
|
||||||
|
# 右侧:主面板(控制面板 + 日志)
|
||||||
|
right_panel = ttk.Frame(horizontal_paned)
|
||||||
|
horizontal_paned.add(right_panel, weight=1)
|
||||||
|
|
||||||
|
self._create_left_panel(left_panel)
|
||||||
|
self._create_right_panel(right_panel)
|
||||||
|
|
||||||
|
# 保存 PanedWindow 引用,后续用于设置分隔条位置
|
||||||
|
self.horizontal_paned = horizontal_paned
|
||||||
|
|
||||||
|
# 设置默认宽度(使用 after 确保在渲染后设置)
|
||||||
|
input_width = self.config.get("ui.production_id_input_width", 20)
|
||||||
|
# 字符宽度约 8 像素
|
||||||
|
self.after(100, lambda: self._set_pane_width(input_width * 8))
|
||||||
|
|
||||||
|
def _create_left_panel(self, parent):
|
||||||
|
"""创建左侧 Production ID 输入面板"""
|
||||||
|
# 创建带标题的框架
|
||||||
|
input_group = ttk.LabelFrame(parent, text="Production ID", padding=10)
|
||||||
|
input_group.pack(fill=tk.BOTH, expand=True)
|
||||||
|
|
||||||
|
# 创建 Production ID 输入控件
|
||||||
|
self.production_id_input = ProductionIdInput(
|
||||||
|
input_group,
|
||||||
|
placeholder="每行输入一个 Production ID\n\n示例:\n26B848\n26B849"
|
||||||
|
)
|
||||||
|
self.production_id_input.pack(fill=tk.BOTH, expand=True)
|
||||||
|
|
||||||
|
# 绑定变化事件:当文本框失去焦点时更新共享 Production ID
|
||||||
|
self.production_id_input.text_widget.bind("<FocusOut>", self._on_production_ids_changed)
|
||||||
|
|
||||||
|
def _create_right_panel(self, parent):
|
||||||
|
"""创建右侧主面板"""
|
||||||
|
# 主容器 - 使用垂直 PanedWindow 分割上下部分
|
||||||
|
main_paned = ttk.PanedWindow(parent, orient=tk.VERTICAL)
|
||||||
|
main_paned.pack(fill=tk.BOTH, expand=True)
|
||||||
|
|
||||||
# 上部:控制面板
|
# 上部:控制面板
|
||||||
control_frame = ttk.Frame(main_paned)
|
control_frame = ttk.Frame(main_paned)
|
||||||
@@ -69,24 +115,6 @@ class DataExtractionTab(ttk.Frame):
|
|||||||
|
|
||||||
def _create_control_panel(self, parent):
|
def _create_control_panel(self, parent):
|
||||||
"""创建控制面板"""
|
"""创建控制面板"""
|
||||||
# 输入文件选择
|
|
||||||
input_group = ttk.LabelFrame(parent, text="输入文件", padding=10)
|
|
||||||
input_group.pack(fill=tk.X, pady=5)
|
|
||||||
|
|
||||||
self.input_file_selector = FileSelector(
|
|
||||||
input_group,
|
|
||||||
label_text="ProductionID 文件:",
|
|
||||||
file_type="file",
|
|
||||||
file_types=[("文本文件", "*.txt"), ("所有文件", "*.*")],
|
|
||||||
initial_dir="D:/python/playwrite/",
|
|
||||||
)
|
|
||||||
self.input_file_selector.pack(fill=tk.X)
|
|
||||||
|
|
||||||
# 设置默认文件
|
|
||||||
default_input = self.config.get("paths.production_id_file", "ProductionID.txt")
|
|
||||||
if os.path.exists(default_input):
|
|
||||||
self.input_file_selector.set(default_input)
|
|
||||||
|
|
||||||
# 输出文件选择
|
# 输出文件选择
|
||||||
output_group = ttk.LabelFrame(parent, text="输出文件", padding=10)
|
output_group = ttk.LabelFrame(parent, text="输出文件", padding=10)
|
||||||
output_group.pack(fill=tk.X, pady=5)
|
output_group.pack(fill=tk.X, pady=5)
|
||||||
@@ -147,20 +175,47 @@ class DataExtractionTab(ttk.Frame):
|
|||||||
self.log_text = LogText(parent, height=15, readonly=True)
|
self.log_text = LogText(parent, height=15, readonly=True)
|
||||||
self.log_text.pack(fill=tk.BOTH, expand=True)
|
self.log_text.pack(fill=tk.BOTH, expand=True)
|
||||||
|
|
||||||
|
def _apply_ui_config(self):
|
||||||
|
"""应用 UI 配置(字体等)"""
|
||||||
|
try:
|
||||||
|
font_family = self.config.get("ui.font_family", "Microsoft YaHei UI")
|
||||||
|
font_size = self.config.get("ui.font_size", 10)
|
||||||
|
|
||||||
|
# 应用到 Production ID 输入控件
|
||||||
|
self.production_id_input.apply_font(font_family, font_size)
|
||||||
|
|
||||||
|
# 应用到日志控件(如果支持)
|
||||||
|
if hasattr(self.log_text, 'apply_font'):
|
||||||
|
self.log_text.apply_font(font_family, font_size)
|
||||||
|
except Exception as e:
|
||||||
|
# 如果应用字体失败,不影响主流程
|
||||||
|
pass
|
||||||
|
|
||||||
|
def _set_pane_width(self, width: int):
|
||||||
|
"""设置左侧 pane 的宽度
|
||||||
|
|
||||||
|
Args:
|
||||||
|
width: 宽度(像素)
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
# 使用 sashpos 方法设置分隔条位置
|
||||||
|
# 参数 0 表示第一个分隔条(索引从 0 开始)
|
||||||
|
self.horizontal_paned.sashpos(0, width)
|
||||||
|
except Exception as e:
|
||||||
|
# 如果设置失败,不影响主流程
|
||||||
|
pass
|
||||||
|
|
||||||
def start_extraction(self):
|
def start_extraction(self):
|
||||||
"""开始数据提取"""
|
"""开始数据提取"""
|
||||||
# 验证输入
|
# 获取 Production ID 列表
|
||||||
input_file = self.input_file_selector.get()
|
production_ids = self.production_id_input.get()
|
||||||
|
|
||||||
|
if not production_ids:
|
||||||
|
messagebox.showerror("错误", "请输入至少一个 Production ID")
|
||||||
|
return
|
||||||
|
|
||||||
output_file = self.output_file_selector.get()
|
output_file = self.output_file_selector.get()
|
||||||
|
|
||||||
if not input_file:
|
|
||||||
messagebox.showerror("错误", "请选择 ProductionID 输入文件")
|
|
||||||
return
|
|
||||||
|
|
||||||
if not os.path.exists(input_file):
|
|
||||||
messagebox.showerror("错误", f"输入文件不存在:{input_file}")
|
|
||||||
return
|
|
||||||
|
|
||||||
if not output_file:
|
if not output_file:
|
||||||
messagebox.showerror("错误", "请指定输出文件路径")
|
messagebox.showerror("错误", "请指定输出文件路径")
|
||||||
return
|
return
|
||||||
@@ -177,11 +232,11 @@ class DataExtractionTab(ttk.Frame):
|
|||||||
self.progress_bar["value"] = 0
|
self.progress_bar["value"] = 0
|
||||||
self.status_label.config(text="正在登录...")
|
self.status_label.config(text="正在登录...")
|
||||||
self.log_text.clear()
|
self.log_text.clear()
|
||||||
self.log_text.info("开始数据提取...")
|
self.log_text.info(f"开始数据提取... ({len(production_ids)} 个 Production ID)")
|
||||||
|
|
||||||
# 在后台线程中执行提取
|
# 在后台线程中执行提取
|
||||||
self.extraction_thread = threading.Thread(
|
self.extraction_thread = threading.Thread(
|
||||||
target=self._extraction_worker, args=(input_file, output_file), daemon=True
|
target=self._extraction_worker, args=(production_ids, output_file), daemon=True
|
||||||
)
|
)
|
||||||
self.extraction_thread.start()
|
self.extraction_thread.start()
|
||||||
|
|
||||||
@@ -192,8 +247,16 @@ class DataExtractionTab(ttk.Frame):
|
|||||||
self.log_text.warning("正在停止提取...")
|
self.log_text.warning("正在停止提取...")
|
||||||
self.status_label.config(text="正在停止...")
|
self.status_label.config(text="正在停止...")
|
||||||
|
|
||||||
def _extraction_worker(self, input_file: str, output_file: str):
|
def _extraction_worker(self, production_ids: list[str], output_file: str):
|
||||||
"""提取工作线程"""
|
"""提取工作线程"""
|
||||||
|
import tempfile
|
||||||
|
|
||||||
|
try:
|
||||||
|
# 创建临时文件保存 Production ID 列表
|
||||||
|
with tempfile.NamedTemporaryFile(mode='w', suffix='.txt', delete=False, encoding='utf-8') as f:
|
||||||
|
temp_file = f.name
|
||||||
|
f.write('\n'.join(production_ids))
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# 导入提取器(延迟导入以避免启动时加载 Playwright)
|
# 导入提取器(延迟导入以避免启动时加载 Playwright)
|
||||||
from utils.离散备料计划维护数据提取 import DiscreteMaterialPlanExtractor
|
from utils.离散备料计划维护数据提取 import DiscreteMaterialPlanExtractor
|
||||||
@@ -224,7 +287,7 @@ class DataExtractionTab(ttk.Frame):
|
|||||||
# 重定向 stdout 并执行提取(带进度回调)
|
# 重定向 stdout 并执行提取(带进度回调)
|
||||||
with redirect_stdout(realtime_output):
|
with redirect_stdout(realtime_output):
|
||||||
result = self.extractor.extract(
|
result = self.extractor.extract(
|
||||||
production_id_file=input_file,
|
production_id_file=temp_file,
|
||||||
output_file=output_file,
|
output_file=output_file,
|
||||||
progress_callback=progress_callback,
|
progress_callback=progress_callback,
|
||||||
)
|
)
|
||||||
@@ -236,6 +299,13 @@ class DataExtractionTab(ttk.Frame):
|
|||||||
else:
|
else:
|
||||||
self._update_log("提取失败", "ERROR")
|
self._update_log("提取失败", "ERROR")
|
||||||
|
|
||||||
|
finally:
|
||||||
|
# 删除临时文件
|
||||||
|
try:
|
||||||
|
os.unlink(temp_file)
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self._update_log(f"提取过程中发生错误:{str(e)}", "ERROR")
|
self._update_log(f"提取过程中发生错误:{str(e)}", "ERROR")
|
||||||
finally:
|
finally:
|
||||||
@@ -287,3 +357,15 @@ class DataExtractionTab(ttk.Frame):
|
|||||||
self.log_text.error(message)
|
self.log_text.error(message)
|
||||||
|
|
||||||
self.after(0, update)
|
self.after(0, update)
|
||||||
|
|
||||||
|
def _on_production_ids_changed(self, event=None):
|
||||||
|
"""Production ID 变化时的回调"""
|
||||||
|
if self.main_window:
|
||||||
|
production_ids = self.production_id_input.get()
|
||||||
|
self.main_window.update_shared_production_ids(production_ids)
|
||||||
|
|
||||||
|
def reload_config(self):
|
||||||
|
"""配置更新后重新应用 UI 设置"""
|
||||||
|
self._apply_ui_config()
|
||||||
|
# 通知主窗口当前的 Production ID
|
||||||
|
self._on_production_ids_changed()
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ class MainWindow:
|
|||||||
self.root = root
|
self.root = root
|
||||||
self.config = ConfigManager()
|
self.config = ConfigManager()
|
||||||
self.session_manager = session_manager
|
self.session_manager = session_manager
|
||||||
|
self.shared_production_ids = [] # 共享的 Production ID 列表
|
||||||
|
|
||||||
# 设置窗口属性(包含用户信息)
|
# 设置窗口属性(包含用户信息)
|
||||||
user_type_display = "管理员" if session_manager.is_admin() else "用户"
|
user_type_display = "管理员" if session_manager.is_admin() else "用户"
|
||||||
@@ -49,6 +50,18 @@ class MainWindow:
|
|||||||
# 居中窗口
|
# 居中窗口
|
||||||
self._center_window()
|
self._center_window()
|
||||||
|
|
||||||
|
def get_shared_production_ids(self) -> list:
|
||||||
|
"""获取共享的 Production ID 列表"""
|
||||||
|
return self.shared_production_ids.copy()
|
||||||
|
|
||||||
|
def update_shared_production_ids(self, production_ids: list):
|
||||||
|
"""更新共享的 Production ID 列表"""
|
||||||
|
self.shared_production_ids = production_ids
|
||||||
|
# 通知物料校验标签页 Production ID 已更新
|
||||||
|
if hasattr(self, 'validation_tab'):
|
||||||
|
if hasattr(self.validation_tab, 'on_production_ids_updated'):
|
||||||
|
self.validation_tab.on_production_ids_updated(production_ids)
|
||||||
|
|
||||||
def create_menu(self):
|
def create_menu(self):
|
||||||
"""创建菜单栏"""
|
"""创建菜单栏"""
|
||||||
menubar = tk.Menu(self.root)
|
menubar = tk.Menu(self.root)
|
||||||
@@ -70,18 +83,31 @@ class MainWindow:
|
|||||||
self.notebook = ttk.Notebook(self.root)
|
self.notebook = ttk.Notebook(self.root)
|
||||||
self.notebook.pack(fill=tk.BOTH, expand=True, padx=5, pady=5)
|
self.notebook.pack(fill=tk.BOTH, expand=True, padx=5, pady=5)
|
||||||
|
|
||||||
# 数据提取标签页
|
# 数据提取标签页(传递 self 以支持共享 Production ID)
|
||||||
self.extraction_tab = DataExtractionTab(self.notebook, self.config)
|
self.extraction_tab = DataExtractionTab(self.notebook, self.config, self)
|
||||||
self.notebook.add(self.extraction_tab, text="数据提取")
|
self.notebook.add(self.extraction_tab, text="数据提取")
|
||||||
|
|
||||||
# 物料校验标签页(传入 session_manager)
|
# 物料校验标签页(传入 session_manager 和 main_window)
|
||||||
self.validation_tab = MaterialValidationTab(self.notebook, self.config, self.session_manager)
|
self.validation_tab = MaterialValidationTab(self.notebook, self.config, self.session_manager, self)
|
||||||
self.notebook.add(self.validation_tab, text="物料校验")
|
self.notebook.add(self.validation_tab, text="物料校验")
|
||||||
|
|
||||||
# 设置标签页(传入 session_manager)
|
# 设置标签页(传入 session_manager)
|
||||||
self.settings_tab = SettingsTab(self.notebook, self.config, self.session_manager)
|
self.settings_tab = SettingsTab(self.notebook, self.config, self.session_manager)
|
||||||
self.notebook.add(self.settings_tab, text="设置")
|
self.notebook.add(self.settings_tab, text="设置")
|
||||||
|
|
||||||
|
# 初始化:如果数据提取页面已有 Production ID,通知物料校验页面
|
||||||
|
self._initialize_shared_production_ids()
|
||||||
|
|
||||||
|
def _initialize_shared_production_ids(self):
|
||||||
|
"""初始化共享的 Production ID(从数据提取页面获取)"""
|
||||||
|
try:
|
||||||
|
if hasattr(self.extraction_tab, 'production_id_input'):
|
||||||
|
production_ids = self.extraction_tab.production_id_input.get()
|
||||||
|
if production_ids:
|
||||||
|
self.update_shared_production_ids(production_ids)
|
||||||
|
except Exception:
|
||||||
|
pass # 如果获取失败,忽略错误
|
||||||
|
|
||||||
def create_status_bar(self):
|
def create_status_bar(self):
|
||||||
"""创建状态栏"""
|
"""创建状态栏"""
|
||||||
self.status_bar = ttk.Frame(self.root, relief=tk.SUNKEN)
|
self.status_bar = ttk.Frame(self.root, relief=tk.SUNKEN)
|
||||||
@@ -144,3 +170,17 @@ class MainWindow:
|
|||||||
"• 数据库持久化 - 将提取的数据自动保存到数据库\n\n"
|
"• 数据库持久化 - 将提取的数据自动保存到数据库\n\n"
|
||||||
"基于 Playwright 和 Python 开发",
|
"基于 Playwright 和 Python 开发",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def reload_config(self):
|
||||||
|
"""配置更新后重新加载配置到各个标签页"""
|
||||||
|
# 重新加载配置
|
||||||
|
self.config.reload()
|
||||||
|
|
||||||
|
# 通知各个标签页重新加载配置
|
||||||
|
if hasattr(self.extraction_tab, 'reload_config'):
|
||||||
|
self.extraction_tab.reload_config()
|
||||||
|
if hasattr(self.validation_tab, 'reload_config'):
|
||||||
|
self.validation_tab.reload_config()
|
||||||
|
|
||||||
|
# 更新状态栏
|
||||||
|
self.config_status.set("配置已重新加载")
|
||||||
|
|||||||
@@ -131,7 +131,7 @@ class CheckboxTreeview(ttk.Treeview):
|
|||||||
class MaterialValidationTab(ttk.Frame):
|
class MaterialValidationTab(ttk.Frame):
|
||||||
"""物料校验标签页"""
|
"""物料校验标签页"""
|
||||||
|
|
||||||
def __init__(self, parent, config: ConfigManager, session_manager):
|
def __init__(self, parent, config: ConfigManager, session_manager, main_window=None):
|
||||||
"""
|
"""
|
||||||
初始化物料校验标签页
|
初始化物料校验标签页
|
||||||
|
|
||||||
@@ -139,13 +139,16 @@ class MaterialValidationTab(ttk.Frame):
|
|||||||
parent: 父容器
|
parent: 父容器
|
||||||
config: 配置管理器
|
config: 配置管理器
|
||||||
session_manager: SessionManager 实例,用于权限控制
|
session_manager: SessionManager 实例,用于权限控制
|
||||||
|
main_window: 主窗口引用,用于获取共享的 Production ID
|
||||||
"""
|
"""
|
||||||
super().__init__(parent)
|
super().__init__(parent)
|
||||||
self.config = config
|
self.config = config
|
||||||
self.session_manager = session_manager
|
self.session_manager = session_manager
|
||||||
|
self.main_window = main_window
|
||||||
self.validating = False
|
self.validating = False
|
||||||
self.validation_results = None
|
self.validation_results = None
|
||||||
self.material_records_cache = None # 缓存完整的物料记录
|
self.material_records_cache = None # 缓存完整的物料记录
|
||||||
|
self.shared_production_ids = [] # 共享的 Production ID 列表
|
||||||
|
|
||||||
# 负责人筛选相关
|
# 负责人筛选相关
|
||||||
self.managers: List[str] = [] # 可用负责人列表
|
self.managers: List[str] = [] # 可用负责人列表
|
||||||
@@ -244,15 +247,73 @@ class MaterialValidationTab(ttk.Frame):
|
|||||||
|
|
||||||
# 数据库过滤模式 - 需要 ProductionID 文件
|
# 数据库过滤模式 - 需要 ProductionID 文件
|
||||||
self.db_filtered_frame = ttk.Frame(file_group)
|
self.db_filtered_frame = ttk.Frame(file_group)
|
||||||
|
|
||||||
|
# 判断用户类型
|
||||||
|
is_user_only = not self.session_manager.is_admin()
|
||||||
|
|
||||||
|
if is_user_only:
|
||||||
|
# ========== User 模式:直接使用共享的 Production ID ==========
|
||||||
|
# 自动设置为使用共享数据源
|
||||||
|
self.production_id_source_var = tk.StringVar(value="shared")
|
||||||
|
|
||||||
|
# 创建共享 Production ID 显示标签(但不显示任何内容,只是用于更新)
|
||||||
|
self.shared_ids_info_label = ttk.Label(
|
||||||
|
self.db_filtered_frame,
|
||||||
|
text="",
|
||||||
|
foreground="gray"
|
||||||
|
)
|
||||||
|
# 不 pack,保持隐藏状态
|
||||||
|
|
||||||
|
else:
|
||||||
|
# ========== Admin 模式:可选择文件或共享数据 ==========
|
||||||
|
# 共享 Production ID 提示标签
|
||||||
|
self.shared_ids_label = ttk.Label(
|
||||||
|
self.db_filtered_frame,
|
||||||
|
text="💡 数据提取页面输入了 Production ID,可直接使用",
|
||||||
|
foreground="green",
|
||||||
|
font=("", 9)
|
||||||
|
)
|
||||||
|
# 初始时不显示,等待 Production ID 更新
|
||||||
|
|
||||||
|
# Production ID 数据源选择
|
||||||
|
self.production_id_source_var = tk.StringVar(value="file")
|
||||||
|
source_frame = ttk.Frame(self.db_filtered_frame)
|
||||||
|
source_frame.pack(fill=tk.X, pady=5)
|
||||||
|
|
||||||
|
ttk.Radiobutton(
|
||||||
|
source_frame,
|
||||||
|
text="使用文件:",
|
||||||
|
variable=self.production_id_source_var,
|
||||||
|
value="file",
|
||||||
|
command=self._on_production_id_source_changed
|
||||||
|
).pack(side=tk.LEFT)
|
||||||
|
|
||||||
|
ttk.Radiobutton(
|
||||||
|
source_frame,
|
||||||
|
text="使用数据提取页面的 Production ID:",
|
||||||
|
variable=self.production_id_source_var,
|
||||||
|
value="shared",
|
||||||
|
command=self._on_production_id_source_changed
|
||||||
|
).pack(side=tk.LEFT, padx=10)
|
||||||
|
|
||||||
|
# 文件选择器
|
||||||
self.db_filtered_production_id_selector = FileSelector(
|
self.db_filtered_production_id_selector = FileSelector(
|
||||||
self.db_filtered_frame,
|
self.db_filtered_frame,
|
||||||
label_text="ProductionID 文件:",
|
label_text="",
|
||||||
file_type="file",
|
file_type="file",
|
||||||
file_types=[("文本文件", "*.txt"), ("所有文件", "*.*")],
|
file_types=[("文本文件", "*.txt"), ("所有文件", "*.*")],
|
||||||
initial_dir="D:/python/playwrite/",
|
initial_dir="D:/python/playwrite/",
|
||||||
)
|
)
|
||||||
self.db_filtered_production_id_selector.pack(fill=tk.X)
|
self.db_filtered_production_id_selector.pack(fill=tk.X)
|
||||||
|
|
||||||
|
# 共享 Production ID 显示标签
|
||||||
|
self.shared_ids_info_label = ttk.Label(
|
||||||
|
self.db_filtered_frame,
|
||||||
|
text="",
|
||||||
|
foreground="blue"
|
||||||
|
)
|
||||||
|
# 初始时不显示
|
||||||
|
|
||||||
# 输出文件(对于所有用户都可见)
|
# 输出文件(对于所有用户都可见)
|
||||||
self.output_file_selector = FileSelector(
|
self.output_file_selector = FileSelector(
|
||||||
file_group,
|
file_group,
|
||||||
@@ -396,6 +457,56 @@ class MaterialValidationTab(ttk.Frame):
|
|||||||
elif mode == "database_filtered":
|
elif mode == "database_filtered":
|
||||||
self.db_filtered_frame.pack(fill=tk.X, pady=(0, 5))
|
self.db_filtered_frame.pack(fill=tk.X, pady=(0, 5))
|
||||||
|
|
||||||
|
def _on_production_id_source_changed(self):
|
||||||
|
"""Production ID 数据源切换回调(仅 Admin 模式)"""
|
||||||
|
# User 模式下没有文件选择器,直接返回
|
||||||
|
if not self.session_manager.is_admin():
|
||||||
|
return
|
||||||
|
|
||||||
|
source = self.production_id_source_var.get()
|
||||||
|
if source == "shared":
|
||||||
|
# 使用共享 Production ID
|
||||||
|
self.db_filtered_production_id_selector.pack_forget()
|
||||||
|
self.shared_ids_info_label.pack(anchor="w", pady=(0, 5))
|
||||||
|
else:
|
||||||
|
# 使用文件
|
||||||
|
self.shared_ids_info_label.pack_forget()
|
||||||
|
self.db_filtered_production_id_selector.pack(fill=tk.X)
|
||||||
|
|
||||||
|
def on_production_ids_updated(self, production_ids: list):
|
||||||
|
"""当数据提取页面的 Production ID 更新时调用"""
|
||||||
|
self.shared_production_ids = production_ids
|
||||||
|
|
||||||
|
is_user_only = not self.session_manager.is_admin()
|
||||||
|
|
||||||
|
# User 模式:静默更新,不显示任何提示
|
||||||
|
if is_user_only:
|
||||||
|
return
|
||||||
|
|
||||||
|
# Admin 模式:显示提示信息
|
||||||
|
if production_ids:
|
||||||
|
count = len(production_ids)
|
||||||
|
preview = ", ".join(production_ids[:3])
|
||||||
|
if count > 3:
|
||||||
|
preview += f" ... (共 {count} 个)"
|
||||||
|
self.shared_ids_info_label.config(text=f"📋 {preview}")
|
||||||
|
# 显示提示标签
|
||||||
|
if hasattr(self, 'shared_ids_label') and not self.shared_ids_label.winfo_ismapped():
|
||||||
|
self.shared_ids_label.pack(anchor="w", pady=(0, 5))
|
||||||
|
else:
|
||||||
|
self.shared_ids_info_label.config(text="")
|
||||||
|
# 隐藏提示标签
|
||||||
|
if hasattr(self, 'shared_ids_label'):
|
||||||
|
if self.shared_ids_label.winfo_ismapped():
|
||||||
|
self.shared_ids_label.pack_forget()
|
||||||
|
|
||||||
|
def reload_config(self):
|
||||||
|
"""配置更新后重新加载"""
|
||||||
|
# 从主窗口获取最新的 Production ID
|
||||||
|
if self.main_window:
|
||||||
|
production_ids = self.main_window.get_shared_production_ids()
|
||||||
|
self.on_production_ids_updated(production_ids)
|
||||||
|
|
||||||
def _create_manager_filter_area(self, parent):
|
def _create_manager_filter_area(self, parent):
|
||||||
"""创建负责人筛选区域"""
|
"""创建负责人筛选区域"""
|
||||||
# PERMISSION CHECK: 非管理员用户不显示筛选区域
|
# PERMISSION CHECK: 非管理员用户不显示筛选区域
|
||||||
@@ -716,12 +827,34 @@ class MaterialValidationTab(ttk.Frame):
|
|||||||
mode = self.source_mode.get()
|
mode = self.source_mode.get()
|
||||||
input_file = None
|
input_file = None
|
||||||
production_id_file = None
|
production_id_file = None
|
||||||
|
production_ids_list = None # 新增:用于传递共享的 Production ID 列表
|
||||||
|
|
||||||
|
is_user_only = not self.session_manager.is_admin()
|
||||||
|
|
||||||
# 根据模式验证输入文件
|
# 根据模式验证输入文件
|
||||||
if mode == "database_full":
|
if mode == "database_full":
|
||||||
# 无需输入文件
|
# 无需输入文件
|
||||||
pass
|
pass
|
||||||
elif mode == "database_filtered":
|
elif mode == "database_filtered":
|
||||||
|
if is_user_only:
|
||||||
|
# User 模式:自动使用共享的 Production ID
|
||||||
|
production_ids_list = self.shared_production_ids
|
||||||
|
if not production_ids_list:
|
||||||
|
messagebox.showerror("错误", "没有可用的共享 Production ID\n请在数据提取页面输入 Production ID 后再试")
|
||||||
|
return
|
||||||
|
self.log_text.info(f"使用数据提取页面的 Production ID({len(production_ids_list)} 个)")
|
||||||
|
else:
|
||||||
|
# Admin 模式:检查数据源选择
|
||||||
|
source = self.production_id_source_var.get()
|
||||||
|
if source == "shared":
|
||||||
|
# 使用共享的 Production ID
|
||||||
|
production_ids_list = self.shared_production_ids
|
||||||
|
if not production_ids_list:
|
||||||
|
messagebox.showerror("错误", "没有可用的共享 Production ID\n请在数据提取页面输入 Production ID 后再试")
|
||||||
|
return
|
||||||
|
self.log_text.info(f"使用共享的 Production ID({len(production_ids_list)} 个)")
|
||||||
|
else:
|
||||||
|
# 使用文件
|
||||||
production_id_file = self.db_filtered_production_id_selector.get()
|
production_id_file = self.db_filtered_production_id_selector.get()
|
||||||
if not production_id_file:
|
if not production_id_file:
|
||||||
messagebox.showerror("错误", "请选择 ProductionID 文件")
|
messagebox.showerror("错误", "请选择 ProductionID 文件")
|
||||||
@@ -758,16 +891,27 @@ class MaterialValidationTab(ttk.Frame):
|
|||||||
# 在后台线程中执行校验
|
# 在后台线程中执行校验
|
||||||
validation_thread = threading.Thread(
|
validation_thread = threading.Thread(
|
||||||
target=self._validation_worker_enhanced,
|
target=self._validation_worker_enhanced,
|
||||||
args=(mode, input_file, production_id_file, output_file),
|
args=(mode, input_file, production_id_file, output_file, production_ids_list),
|
||||||
daemon=True,
|
daemon=True,
|
||||||
)
|
)
|
||||||
validation_thread.start()
|
validation_thread.start()
|
||||||
|
|
||||||
def _validation_worker_enhanced(
|
def _validation_worker_enhanced(
|
||||||
self, mode: str, input_file: str, production_id_file: str, output_file: str
|
self, mode: str, input_file: str, production_id_file: str, output_file: str, production_ids_list: list = None
|
||||||
):
|
):
|
||||||
"""增强的校验工作线程(使用完整记录模式)"""
|
"""增强的校验工作线程(使用完整记录模式)"""
|
||||||
|
import tempfile
|
||||||
|
temp_production_id_file = None
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
# 如果提供了共享的 Production ID 列表,创建临时文件
|
||||||
|
if production_ids_list:
|
||||||
|
with tempfile.NamedTemporaryFile(mode='w', suffix='.txt', delete=False, encoding='utf-8') as f:
|
||||||
|
temp_production_id_file = f.name
|
||||||
|
f.write('\n'.join(production_ids_list))
|
||||||
|
production_id_file = temp_production_id_file
|
||||||
|
self._update_log(f"使用共享的 Production ID 列表({len(production_ids_list)} 个)", "INFO")
|
||||||
|
|
||||||
# 导入校验器
|
# 导入校验器
|
||||||
from utils.material_status_validator import MaterialStatusValidator
|
from utils.material_status_validator import MaterialStatusValidator
|
||||||
|
|
||||||
@@ -820,6 +964,12 @@ class MaterialValidationTab(ttk.Frame):
|
|||||||
import traceback
|
import traceback
|
||||||
self._update_log(traceback.format_exc(), "ERROR")
|
self._update_log(traceback.format_exc(), "ERROR")
|
||||||
finally:
|
finally:
|
||||||
|
# 清理临时文件
|
||||||
|
if temp_production_id_file and os.path.exists(temp_production_id_file):
|
||||||
|
try:
|
||||||
|
os.unlink(temp_production_id_file)
|
||||||
|
except:
|
||||||
|
pass
|
||||||
# 更新 UI 状态
|
# 更新 UI 状态
|
||||||
self.after(0, self._validation_complete)
|
self.after(0, self._validation_complete)
|
||||||
|
|
||||||
|
|||||||
@@ -57,10 +57,11 @@ class SettingsTab(ttk.Frame):
|
|||||||
self._create_paths_group(scrollable_frame)
|
self._create_paths_group(scrollable_frame)
|
||||||
self._create_extraction_group(scrollable_frame)
|
self._create_extraction_group(scrollable_frame)
|
||||||
self._create_validation_group(scrollable_frame)
|
self._create_validation_group(scrollable_frame)
|
||||||
|
self._create_ui_group(scrollable_frame)
|
||||||
|
|
||||||
# 按钮区域 - 根据用户类型显示不同按钮
|
# 按钮区域 - 根据用户类型显示不同按钮
|
||||||
button_frame = ttk.Frame(scrollable_frame)
|
button_frame = ttk.Frame(scrollable_frame)
|
||||||
button_frame.grid(row=6, column=0, columnspan=2, pady=20, sticky="ew")
|
button_frame.grid(row=7, column=0, columnspan=2, pady=20, sticky="ew")
|
||||||
|
|
||||||
if is_user_only:
|
if is_user_only:
|
||||||
# 普通用户只显示测试按钮
|
# 普通用户只显示测试按钮
|
||||||
@@ -331,6 +332,39 @@ class SettingsTab(ttk.Frame):
|
|||||||
|
|
||||||
group.columnconfigure(1, weight=1)
|
group.columnconfigure(1, weight=1)
|
||||||
|
|
||||||
|
def _create_ui_group(self, parent):
|
||||||
|
"""创建 UI 配置组"""
|
||||||
|
group = ttk.LabelFrame(parent, text="界面设置", padding=10)
|
||||||
|
group.grid(row=5, column=0, columnspan=2, pady=10, padx=10, sticky="ew")
|
||||||
|
|
||||||
|
# 字体选择
|
||||||
|
ttk.Label(group, text="字体:").grid(row=0, column=0, sticky="w", pady=5)
|
||||||
|
self.ui_font_family_var = tk.StringVar()
|
||||||
|
font_combo = ttk.Combobox(
|
||||||
|
group,
|
||||||
|
textvariable=self.ui_font_family_var,
|
||||||
|
values=["Microsoft YaHei UI", "SimSun", "KaiTi", "FangSong", "Arial", "Segoe UI"],
|
||||||
|
state="readonly",
|
||||||
|
width=30,
|
||||||
|
)
|
||||||
|
font_combo.grid(row=0, column=1, sticky="w", pady=5)
|
||||||
|
|
||||||
|
# 字号选择
|
||||||
|
ttk.Label(group, text="字号:").grid(row=1, column=0, sticky="w", pady=5)
|
||||||
|
self.ui_font_size_var = tk.IntVar(value=10)
|
||||||
|
ttk.Spinbox(
|
||||||
|
group, from_=8, to=24, textvariable=self.ui_font_size_var, width=10
|
||||||
|
).grid(row=1, column=1, sticky="w", pady=5)
|
||||||
|
|
||||||
|
# Production ID 输入框宽度
|
||||||
|
ttk.Label(group, text="输入框宽度(字符):").grid(row=2, column=0, sticky="w", pady=5)
|
||||||
|
self.ui_input_width_var = tk.IntVar(value=20)
|
||||||
|
ttk.Spinbox(
|
||||||
|
group, from_=10, to=100, textvariable=self.ui_input_width_var, width=10
|
||||||
|
).grid(row=2, column=1, sticky="w", pady=5)
|
||||||
|
|
||||||
|
group.columnconfigure(1, weight=1)
|
||||||
|
|
||||||
def load_settings(self):
|
def load_settings(self):
|
||||||
"""从配置加载设置到界面"""
|
"""从配置加载设置到界面"""
|
||||||
# 判断是否为仅测试用户模式
|
# 判断是否为仅测试用户模式
|
||||||
@@ -391,6 +425,11 @@ class SettingsTab(ttk.Frame):
|
|||||||
self.validation_enable_crud_var.set(self.config.get("validation.enable_crud_operations", False))
|
self.validation_enable_crud_var.set(self.config.get("validation.enable_crud_operations", False))
|
||||||
self.validation_default_manager_var.set(self.config.get("validation.default_manager", ""))
|
self.validation_default_manager_var.set(self.config.get("validation.default_manager", ""))
|
||||||
|
|
||||||
|
# UI 设置
|
||||||
|
self.ui_font_family_var.set(self.config.get("ui.font_family", "Microsoft YaHei UI"))
|
||||||
|
self.ui_font_size_var.set(self.config.get("ui.font_size", 10))
|
||||||
|
self.ui_input_width_var.set(self.config.get("ui.production_id_input_width", 20))
|
||||||
|
|
||||||
def save_settings(self):
|
def save_settings(self):
|
||||||
"""保存界面设置到配置"""
|
"""保存界面设置到配置"""
|
||||||
# ERP 设置
|
# ERP 设置
|
||||||
@@ -439,12 +478,31 @@ class SettingsTab(ttk.Frame):
|
|||||||
self.config.set("validation.enable_crud_operations", self.validation_enable_crud_var.get())
|
self.config.set("validation.enable_crud_operations", self.validation_enable_crud_var.get())
|
||||||
self.config.set("validation.default_manager", self.validation_default_manager_var.get())
|
self.config.set("validation.default_manager", self.validation_default_manager_var.get())
|
||||||
|
|
||||||
|
# UI 设置
|
||||||
|
self.config.set("ui.font_family", self.ui_font_family_var.get())
|
||||||
|
self.config.set("ui.font_size", self.ui_font_size_var.get())
|
||||||
|
self.config.set("ui.production_id_input_width", self.ui_input_width_var.get())
|
||||||
|
|
||||||
# 保存到文件
|
# 保存到文件
|
||||||
if self.config.save():
|
if self.config.save():
|
||||||
messagebox.showinfo("成功", "设置已保存")
|
messagebox.showinfo("成功", "设置已保存")
|
||||||
|
# 通知其他标签页重新加载配置
|
||||||
|
self._notify_config_reload()
|
||||||
else:
|
else:
|
||||||
messagebox.showerror("错误", "保存设置失败")
|
messagebox.showerror("错误", "保存设置失败")
|
||||||
|
|
||||||
|
def _notify_config_reload(self):
|
||||||
|
"""通知其他标签页配置已更新"""
|
||||||
|
# 尝试通知主窗口重新加载配置
|
||||||
|
try:
|
||||||
|
# 获取主窗口
|
||||||
|
main_window = self.winfo_toplevel()
|
||||||
|
# 调用主窗口的 reload_config 方法(如果存在)
|
||||||
|
if hasattr(main_window, 'reload_config'):
|
||||||
|
main_window.reload_config()
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
def test_db_connection(self):
|
def test_db_connection(self):
|
||||||
"""测试数据库连接"""
|
"""测试数据库连接"""
|
||||||
# 从配置读取而不是从 UI 变量(支持 User 类型用户)
|
# 从配置读取而不是从 UI 变量(支持 User 类型用户)
|
||||||
|
|||||||
@@ -6,5 +6,6 @@ GUI 自定义组件模块
|
|||||||
|
|
||||||
from .file_selector import FileSelector
|
from .file_selector import FileSelector
|
||||||
from .log_text import LogText
|
from .log_text import LogText
|
||||||
|
from .production_id_input import ProductionIdInput
|
||||||
|
|
||||||
__all__ = ['FileSelector', 'LogText']
|
__all__ = ['FileSelector', 'LogText', 'ProductionIdInput']
|
||||||
|
|||||||
@@ -178,3 +178,9 @@ class LogText(tk.Frame):
|
|||||||
def grid(self, **kwargs):
|
def grid(self, **kwargs):
|
||||||
"""Grid 布局"""
|
"""Grid 布局"""
|
||||||
super().grid(**kwargs)
|
super().grid(**kwargs)
|
||||||
|
|
||||||
|
def apply_font(self, font_family: str, font_size: int):
|
||||||
|
"""应用字体设置"""
|
||||||
|
from tkinter import font as tk_font
|
||||||
|
font_spec = tk_font.Font(family=font_family, size=font_size)
|
||||||
|
self.text.configure(font=font_spec)
|
||||||
|
|||||||
92
gui/widgets/production_id_input.py
Normal file
92
gui/widgets/production_id_input.py
Normal file
@@ -0,0 +1,92 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""
|
||||||
|
Production ID 输入控件
|
||||||
|
|
||||||
|
多行文本输入框,用于输入 Production ID 列表。
|
||||||
|
"""
|
||||||
|
|
||||||
|
import tkinter as tk
|
||||||
|
from tkinter import ttk, font
|
||||||
|
|
||||||
|
|
||||||
|
class ProductionIdInput(ttk.Frame):
|
||||||
|
"""Production ID 输入控件"""
|
||||||
|
|
||||||
|
def __init__(self, parent, placeholder="每行输入一个 Production ID", **kwargs):
|
||||||
|
super().__init__(parent, **kwargs)
|
||||||
|
self.placeholder = placeholder
|
||||||
|
|
||||||
|
# 创建文本框和滚动条
|
||||||
|
self.text_widget = tk.Text(self, wrap=tk.WORD, padx=5, pady=5)
|
||||||
|
self.scrollbar = ttk.Scrollbar(self, orient=tk.VERTICAL, command=self.text_widget.yview)
|
||||||
|
self.text_widget.configure(yscrollcommand=self.scrollbar.set)
|
||||||
|
|
||||||
|
# 布局
|
||||||
|
self.text_widget.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
|
||||||
|
self.scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
|
||||||
|
|
||||||
|
# 绑定事件
|
||||||
|
self.text_widget.bind("<FocusIn>", self._on_focus_in)
|
||||||
|
self.text_widget.bind("<FocusOut>", self._on_focus_out)
|
||||||
|
|
||||||
|
# 显示占位符
|
||||||
|
self._show_placeholder()
|
||||||
|
|
||||||
|
def _on_focus_in(self, event):
|
||||||
|
"""获得焦点时隐藏占位符"""
|
||||||
|
if self.text_widget.get("1.0", "end-1c") == self.placeholder:
|
||||||
|
self.text_widget.delete("1.0", tk.END)
|
||||||
|
|
||||||
|
def _on_focus_out(self, event):
|
||||||
|
"""失去焦点时显示占位符"""
|
||||||
|
content = self.text_widget.get("1.0", "end-1c")
|
||||||
|
if not content:
|
||||||
|
self._show_placeholder()
|
||||||
|
|
||||||
|
def _show_placeholder(self):
|
||||||
|
"""显示占位符"""
|
||||||
|
self.text_widget.delete("1.0", tk.END)
|
||||||
|
self.text_widget.insert("1.0", self.placeholder)
|
||||||
|
self.text_widget.configure(foreground="gray")
|
||||||
|
|
||||||
|
def _hide_placeholder(self):
|
||||||
|
"""隐藏占位符"""
|
||||||
|
if self.text_widget.get("1.0", "end-1c") == self.placeholder:
|
||||||
|
self.text_widget.delete("1.0", tk.END)
|
||||||
|
self.text_widget.configure(foreground="black")
|
||||||
|
|
||||||
|
def get(self) -> list[str]:
|
||||||
|
"""获取 Production ID 列表"""
|
||||||
|
self._hide_placeholder()
|
||||||
|
content = self.text_widget.get("1.0", "end-1c").strip()
|
||||||
|
return [line.strip() for line in content.split("\n") if line.strip()]
|
||||||
|
|
||||||
|
def set(self, production_ids: list[str]):
|
||||||
|
"""设置 Production ID 列表"""
|
||||||
|
self.text_widget.delete("1.0", tk.END)
|
||||||
|
if production_ids:
|
||||||
|
self.text_widget.insert("1.0", "\n".join(production_ids))
|
||||||
|
self.text_widget.configure(foreground="black")
|
||||||
|
else:
|
||||||
|
self._show_placeholder()
|
||||||
|
|
||||||
|
def clear(self):
|
||||||
|
"""清空内容"""
|
||||||
|
self.text_widget.delete("1.0", tk.END)
|
||||||
|
self._show_placeholder()
|
||||||
|
|
||||||
|
def append(self, production_ids: list[str]):
|
||||||
|
"""追加 Production ID 列表"""
|
||||||
|
self._hide_placeholder()
|
||||||
|
if production_ids:
|
||||||
|
current_content = self.text_widget.get("1.0", "end-1c")
|
||||||
|
if current_content.strip():
|
||||||
|
self.text_widget.insert(tk.END, "\n" + "\n".join(production_ids))
|
||||||
|
else:
|
||||||
|
self.text_widget.insert("1.0", "\n".join(production_ids))
|
||||||
|
|
||||||
|
def apply_font(self, font_family: str, font_size: int):
|
||||||
|
"""应用字体设置"""
|
||||||
|
font_spec = font.Font(family=font_family, size=font_size)
|
||||||
|
self.text_widget.configure(font=font_spec)
|
||||||
Reference in New Issue
Block a user