From b3f94d90342aa5adc797ee9085e22cd69e941f5b Mon Sep 17 00:00:00 2001 From: houhuan Date: Tue, 21 Jul 2026 10:34:13 +0800 Subject: [PATCH] Fix: AttributeError in OCRProcessor and refactor all relative imports to absolute --- app/core/ocr/table_ocr.py | 18 ++++++---- app/services/batch_service.py | 4 +-- app/services/order_service.py | 8 ++--- app/ui/action_handlers.py | 22 ++++++------ app/ui/file_operations.py | 6 ++-- app/ui/main_window.py | 22 ++++++------ app/ui/ui_widgets.py | 2 +- config.ini | 2 +- config/config.ini | 2 +- scripts/manual_sync.py | 63 ----------------------------------- 10 files changed, 44 insertions(+), 105 deletions(-) delete mode 100644 scripts/manual_sync.py diff --git a/app/core/ocr/table_ocr.py b/app/core/ocr/table_ocr.py index 789d851..de1b396 100644 --- a/app/core/ocr/table_ocr.py +++ b/app/core/ocr/table_ocr.py @@ -21,7 +21,7 @@ from app.core.utils.file_utils import ( save_json ) from app.config.settings import ConfigManager -from .baidu_ocr import BaiduOCRClient +from app.core.ocr.baidu_ocr import BaiduOCRClient logger = get_logger(__name__) @@ -112,14 +112,13 @@ class OCRProcessor: """ self.config = config or ConfigManager() self.ocr_client = None - self._ensure_ocr_client() # 修复ConfigParser对象没有get_path方法的问题 try: # 获取输入和输出目录 - self.input_folder = config.get_path('Paths', 'input_folder', fallback='data/input', create=True) if hasattr(config, 'get_path') else os.path.abspath('data/input') - self.output_folder = config.get_path('Paths', 'output_folder', fallback='data/output', create=True) if hasattr(config, 'get_path') else os.path.abspath('data/output') - self.temp_folder = config.get_path('Paths', 'temp_folder', fallback='data/temp', create=True) if hasattr(config, 'get_path') else os.path.abspath('data/temp') + self.input_folder = self.config.get_path('Paths', 'input_folder', fallback='data/input', create=True) if hasattr(self.config, 'get_path') else os.path.abspath('data/input') + self.output_folder = self.config.get_path('Paths', 'output_folder', fallback='data/output', create=True) if hasattr(self.config, 'get_path') else os.path.abspath('data/output') + self.temp_folder = self.config.get_path('Paths', 'temp_folder', fallback='data/temp', create=True) if hasattr(self.config, 'get_path') else os.path.abspath('data/temp') # 确保目录存在 os.makedirs(self.input_folder, exist_ok=True) @@ -127,13 +126,13 @@ class OCRProcessor: os.makedirs(self.temp_folder, exist_ok=True) # 获取文件类型列表 - allowed_extensions_str = config.get('File', 'allowed_extensions', fallback='.jpg,.jpeg,.png,.bmp') + allowed_extensions_str = self.config.get('File', 'allowed_extensions', fallback='.jpg,.jpeg,.png,.bmp') self.file_types = [ext.strip() for ext in allowed_extensions_str.split(',') if ext.strip()] if not self.file_types: self.file_types = ['.jpg', '.jpeg', '.png', '.bmp', '.gif', '.tif', '.tiff'] # 初始化OCR客户端 - self.ocr_client = BaiduOCRClient(self.config) + self._ensure_ocr_client() # 记录实际路径 logger.info(f"使用输入目录: {os.path.abspath(self.input_folder)}") @@ -153,6 +152,11 @@ class OCRProcessor: logger.error(f"初始化OCRProcessor失败: {e}") raise + def _ensure_ocr_client(self): + """确保OCR客户端已初始化""" + if self.ocr_client is None: + self.ocr_client = BaiduOCRClient(self.config) + def _load_processed_files(self) -> Dict[str, str]: """ 加载已处理的文件记录 diff --git a/app/services/batch_service.py b/app/services/batch_service.py index 0f9cdee..c37366f 100644 --- a/app/services/batch_service.py +++ b/app/services/batch_service.py @@ -13,8 +13,8 @@ from typing import Any, Callable, Dict, List, Optional from app.config.settings import ConfigManager from app.core.utils.log_utils import get_logger -from .ocr_service import OCRService -from .order_service import OrderService +from app.services.ocr_service import OCRService +from app.services.order_service import OrderService logger = get_logger(__name__) diff --git a/app/services/order_service.py b/app/services/order_service.py index 4c32a71..69e3233 100644 --- a/app/services/order_service.py +++ b/app/services/order_service.py @@ -90,7 +90,7 @@ class OrderService: # 检查是否需要特殊的供应商预处理(如杨碧月) try: - from .special_suppliers_service import SpecialSuppliersService + from app.services.special_suppliers_service import SpecialSuppliersService special_service = SpecialSuppliersService(self.config) # 尝试识别并预处理(注意:这里不再传入 progress_cb 避免无限递归或重复进度条, @@ -138,7 +138,7 @@ class OrderService: is_tobacco = df_str.apply(lambda x: x.str.contains('专卖证号|510109104938')).any().any() if is_tobacco: logger.info("识别到烟草公司订单,执行专用预处理...") - from .tobacco_service import TobaccoService + from app.services.tobacco_service import TobaccoService tobacco_svc = TobaccoService(self.config) return tobacco_svc.preprocess_tobacco_order(file_path) @@ -147,7 +147,7 @@ class OrderService: is_rongcheng = df_str.apply(lambda x: x.str.contains('RCDH')).any().any() if is_rongcheng: logger.info("识别到蓉城易购订单,执行专用预处理...") - from .special_suppliers_service import SpecialSuppliersService + from app.services.special_suppliers_service import SpecialSuppliersService special_svc = SpecialSuppliersService(self.config) return special_svc.preprocess_rongcheng_yigou(file_path) @@ -164,7 +164,7 @@ class OrderService: # 检查该列是否有“杨碧月” if df_head[handler_col].astype(str).str.contains('杨碧月').any(): logger.info("识别到杨碧月订单,执行专用预处理...") - from .special_suppliers_service import SpecialSuppliersService + from app.services.special_suppliers_service import SpecialSuppliersService special_svc = SpecialSuppliersService(self.config) return special_svc.process_yang_biyue_only(file_path) diff --git a/app/ui/action_handlers.py b/app/ui/action_handlers.py index da27d22..c8a3f9d 100644 --- a/app/ui/action_handlers.py +++ b/app/ui/action_handlers.py @@ -16,15 +16,13 @@ from app.services.ocr_service import OCRService from app.services.order_service import OrderService from app.core.utils.log_utils import get_logger -from .logging_ui import add_to_log, init_gui_logger, dispose_gui_logger, GUILogHandler -from .ui_widgets import ProgressReporter -from .error_utils import show_error_dialog, get_error_suggestion - -logger = get_logger(__name__) -from .result_previews import show_ocr_result_preview, show_excel_result_preview, show_merge_result_preview -from .user_settings import add_recent_file -from .command_runner import get_running_task, set_running_task -from .file_operations import select_file, select_excel_file, validate_unit_price_against_item_data +from app.ui.logging_ui import add_to_log, init_gui_logger, dispose_gui_logger, GUILogHandler +from app.ui.ui_widgets import ProgressReporter +from app.ui.error_utils import show_error_dialog, get_error_suggestion +from app.ui.result_previews import show_ocr_result_preview, show_excel_result_preview, show_merge_result_preview +from app.ui.user_settings import add_recent_file +from app.ui.command_runner import get_running_task, set_running_task +from app.ui.file_operations import select_file, select_excel_file, validate_unit_price_against_item_data def _get_missing_barcodes_callback(log_widget): @@ -249,9 +247,9 @@ def batch_process_all_inputs(log_widget, status_bar): init_gui_logger(log_widget) - from ..services.batch_service import BatchService - from ..config.settings import ConfigManager - from .memory_editor import show_memory_editor # noqa: F401 触发 import 顺序 + from app.services.batch_service import BatchService + from app.config.settings import ConfigManager + from app.ui.memory_editor import show_memory_editor # noqa: F401 触发 import 顺序 cfg = ConfigManager() svc = BatchService(cfg, missing_barcodes_cb=_get_missing_barcodes_callback(log_widget)) diff --git a/app/ui/file_operations.py b/app/ui/file_operations.py index 436b161..384e750 100644 --- a/app/ui/file_operations.py +++ b/app/ui/file_operations.py @@ -7,8 +7,8 @@ import json import tkinter as tk from tkinter import messagebox, filedialog, scrolledtext -from .logging_ui import add_to_log -from .ui_widgets import center_window +from app.ui.logging_ui import add_to_log +from app.ui.ui_widgets import center_window from app.config.settings import ConfigManager @@ -59,7 +59,7 @@ def ensure_directories(): def clean_cache(log_widget): """清除处理缓存""" - from .command_runner import set_running_task + from app.ui.command_runner import set_running_task try: config = ConfigManager() processed_record = config.get_path('Paths', 'processed_record', fallback='data/processed_files.json') diff --git a/app/ui/main_window.py b/app/ui/main_window.py index b4cc8e6..dc9d25d 100644 --- a/app/ui/main_window.py +++ b/app/ui/main_window.py @@ -11,29 +11,29 @@ from tkinter import messagebox, filedialog, scrolledtext from app.config.settings import ConfigManager from app.core.utils.log_utils import set_log_level -from .theme import THEMES, get_theme_mode, set_theme_mode, create_modern_button, create_card_frame -from .logging_ui import add_to_log, poll_log_queue -from .ui_widgets import StatusBar, ToolTip -from .user_settings import ( +from app.ui.theme import THEMES, get_theme_mode, set_theme_mode, create_modern_button, create_card_frame +from app.ui.logging_ui import add_to_log, poll_log_queue +from app.ui.ui_widgets import StatusBar, ToolTip +from app.ui.user_settings import ( load_user_settings, save_user_settings, refresh_recent_list_widget, _extract_path_from_recent_item, clear_recent_files, RECENT_LIST_WIDGET, ) -from .file_operations import ( +from app.ui.file_operations import ( ensure_directories, open_result_directory, clean_cache, clean_data_files, clean_result_files, ) -from .action_handlers import ( +from app.ui.action_handlers import ( process_single_image_with_status, run_pipeline_directly, batch_ocr_with_status, batch_process_orders_with_status, merge_orders_with_status, process_excel_file_with_status, process_dropped_file, batch_process_all_inputs, ) -from .memory_editor import show_memory_editor -from .config_dialog import show_config_dialog -from .barcode_editor import edit_barcode_mappings -from .shortcuts import bind_keyboard_shortcuts +from app.ui.memory_editor import show_memory_editor +from app.ui.config_dialog import show_config_dialog +from app.ui.barcode_editor import edit_barcode_mappings +from app.ui.shortcuts import bind_keyboard_shortcuts from app.core.utils.dialog_utils import show_cloud_sync_dialog -from .db_viewer import show_db_viewer +from app.ui.db_viewer import show_db_viewer def _init_window(): diff --git a/app/ui/ui_widgets.py b/app/ui/ui_widgets.py index caa5b58..1eeefcb 100644 --- a/app/ui/ui_widgets.py +++ b/app/ui/ui_widgets.py @@ -5,7 +5,7 @@ import tkinter as tk from tkinter import ttk -from .theme import THEMES, get_theme_mode +from app.ui.theme import THEMES, get_theme_mode class StatusBar(tk.Frame): diff --git a/config.ini b/config.ini index d263cba..1d71854 100644 --- a/config.ini +++ b/config.ini @@ -41,7 +41,7 @@ version = 2026.07.20.2105 base_url = https://gitea.94kan.cn owner = houhuan repo = yixuan-sync-data -token = 50b61e43a141d606ae2529cd1755bc666d800e08 +token = 149f3f64edad2a3a25d94a054ea699f3d28f7294 [WebAuth] username = admin diff --git a/config/config.ini b/config/config.ini index 06de377..ba5fa67 100644 --- a/config/config.ini +++ b/config/config.ini @@ -38,5 +38,5 @@ item_data = 商品资料.xlsx base_url = https://gitea.94kan.cn owner = houhuan repo = yixuan-sync-data -token = 50b61e43a141d606ae2529cd1755bc666d800e08 +token = 149f3f64edad2a3a25d94a054ea699f3d28f7294 diff --git a/scripts/manual_sync.py b/scripts/manual_sync.py deleted file mode 100644 index 1986af4..0000000 --- a/scripts/manual_sync.py +++ /dev/null @@ -1,63 +0,0 @@ - -import os -import sys -import json -from pathlib import Path - -# Add project root to path -sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))) - -from app.core.utils.cloud_sync import GiteaSync -from app.config.settings import ConfigManager - -def sync_all(): - config = ConfigManager() - sync = GiteaSync.from_config(config) - if not sync: - print("Error: Gitea configuration missing in config.ini") - sys.exit(1) - - # Sync files defined in dialog_utils.py (re-implementing the logic here) - SYNC_FILES = [ - {"name": "条码映射", "remote": "barcode_mappings.json", "local": "config/barcode_mappings.json", "type": "json"}, - {"name": "供应商配置", "remote": "suppliers_config.json", "local": "config/suppliers_config.json", "type": "json"}, - {"name": "商品资料", "remote": "templates/商品资料.xlsx", "local": "templates/商品资料.xlsx", "type": "binary"}, - {"name": "采购单模板", "remote": "templates/银豹-采购单模板.xls", "local": "templates/银豹-采购单模板.xls", "type": "binary"}, - {"name": "商品记忆库 (DB)", "remote": "product_cache.db", "local": "data/product_cache.db", "type": "binary"}, - ] - - print(f"Starting sync to {sync.base_url}/{sync.owner}/{sync.repo}...") - - success_count = 0 - for entry in SYNC_FILES: - local_path = entry["local"] - remote_path = entry["remote"] - name = entry["name"] - - if not os.path.exists(local_path): - print(f"Skipping {name}: Local file not found at {local_path}") - continue - - print(f"Pushing {name} ({local_path}) -> {remote_path}...") - try: - if entry["type"] == "json": - with open(local_path, "r", encoding="utf-8") as f: - data = json.load(f) - # Get current SHA to update - sha = sync.file_exists(remote_path) - result = sync.push_json(remote_path, data, f"Sync: {name}", sha=sha) - else: - result = sync.push_binary(remote_path, local_path, f"Sync: {name}") - - if result: - print(f"Successfully synced {name}") - success_count += 1 - else: - print(f"Failed to sync {name}") - except Exception as e: - print(f"Error syncing {name}: {e}") - - print(f"\nSync finished. {success_count}/{len(SYNC_FILES)} files synced.") - -if __name__ == "__main__": - sync_all()