增加了查看入库秤上传图片的GUI界面。
This commit is contained in:
@@ -0,0 +1,469 @@
|
||||
"""
|
||||
中间图片预览面板
|
||||
"""
|
||||
import customtkinter as ctk
|
||||
from tkinter import messagebox
|
||||
from typing import List, Dict, Callable, Optional
|
||||
import threading
|
||||
import sys
|
||||
import os
|
||||
|
||||
# 添加父目录到路径
|
||||
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||
from config import GRID_COLUMNS, THUMBNAIL_SIZE, CARD_PADDING, ITEMS_PER_PAGE
|
||||
from utils.image_loader import load_image_from_url, create_placeholder_image
|
||||
|
||||
|
||||
class ImageCard(ctk.CTkFrame):
|
||||
"""图片卡片组件"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
parent,
|
||||
data: Dict,
|
||||
is_downloaded: bool,
|
||||
on_check_changed: Callable,
|
||||
on_preview: Callable,
|
||||
**kwargs
|
||||
):
|
||||
super().__init__(parent, **kwargs)
|
||||
|
||||
self.data = data
|
||||
self.is_downloaded = is_downloaded
|
||||
self.on_check_changed = on_check_changed
|
||||
self.on_preview = on_preview
|
||||
|
||||
# 勾选状态
|
||||
self.check_var = ctk.BooleanVar(value=False)
|
||||
|
||||
self._create_widgets()
|
||||
self._load_thumbnail()
|
||||
|
||||
def _create_widgets(self):
|
||||
"""创建卡片组件"""
|
||||
# 勾选框
|
||||
self.checkbox = ctk.CTkCheckBox(
|
||||
self,
|
||||
text="",
|
||||
variable=self.check_var,
|
||||
command=self._on_check,
|
||||
width=20
|
||||
)
|
||||
self.checkbox.pack(pady=(5, 0), padx=5, anchor="w")
|
||||
|
||||
# 图片容器
|
||||
self.image_label = ctk.CTkLabel(
|
||||
self,
|
||||
text="加载中...",
|
||||
width=THUMBNAIL_SIZE,
|
||||
height=THUMBNAIL_SIZE
|
||||
)
|
||||
self.image_label.pack(pady=5, padx=5)
|
||||
self.image_label.bind("<Button-1>", lambda e: self.on_preview(self.data))
|
||||
|
||||
# 物品名称(加粗)
|
||||
name_label = ctk.CTkLabel(
|
||||
self,
|
||||
text=self.data['goods_name'],
|
||||
font=("Arial", 11, "bold"),
|
||||
wraplength=THUMBNAIL_SIZE - 10
|
||||
)
|
||||
name_label.pack(pady=(0, 2), padx=5)
|
||||
|
||||
# 采集时间
|
||||
time_label = ctk.CTkLabel(
|
||||
self,
|
||||
text=self.data['create_time'],
|
||||
font=("Arial", 9),
|
||||
text_color="gray"
|
||||
)
|
||||
time_label.pack(pady=(0, 2), padx=5)
|
||||
|
||||
# 设备编码
|
||||
if self.data['equipment_code']:
|
||||
equipment_label = ctk.CTkLabel(
|
||||
self,
|
||||
text=f"设备: {self.data['equipment_code']}",
|
||||
font=("Arial", 9),
|
||||
text_color="gray"
|
||||
)
|
||||
equipment_label.pack(pady=(0, 2), padx=5)
|
||||
|
||||
# 已下载标记
|
||||
if self.is_downloaded:
|
||||
downloaded_label = ctk.CTkLabel(
|
||||
self,
|
||||
text="✅ 已下载",
|
||||
font=("Arial", 10),
|
||||
text_color="green"
|
||||
)
|
||||
downloaded_label.pack(pady=(0, 5), padx=5)
|
||||
|
||||
# 查看大图按钮
|
||||
preview_btn = ctk.CTkButton(
|
||||
self,
|
||||
text="查看大图",
|
||||
command=lambda: self.on_preview(self.data),
|
||||
width=100,
|
||||
height=25,
|
||||
font=("Arial", 10)
|
||||
)
|
||||
preview_btn.pack(pady=(0, 5), padx=5)
|
||||
|
||||
def _load_thumbnail(self):
|
||||
"""异步加载缩略图"""
|
||||
def load():
|
||||
image = load_image_from_url(
|
||||
self.data['image_url'],
|
||||
size=(THUMBNAIL_SIZE, THUMBNAIL_SIZE)
|
||||
)
|
||||
|
||||
if image:
|
||||
self.image_label.configure(image=image, text="")
|
||||
self.image_label.image = image
|
||||
else:
|
||||
placeholder = create_placeholder_image(
|
||||
size=(THUMBNAIL_SIZE, THUMBNAIL_SIZE)
|
||||
)
|
||||
self.image_label.configure(image=placeholder, text="")
|
||||
self.image_label.image = placeholder
|
||||
|
||||
threading.Thread(target=load, daemon=True).start()
|
||||
|
||||
def _on_check(self):
|
||||
"""勾选框改变"""
|
||||
self.on_check_changed(self.data['id'], self.check_var.get())
|
||||
|
||||
def is_checked(self) -> bool:
|
||||
"""是否被勾选"""
|
||||
return self.check_var.get()
|
||||
|
||||
def set_checked(self, checked: bool):
|
||||
"""设置勾选状态"""
|
||||
self.check_var.set(checked)
|
||||
|
||||
|
||||
class PreviewPanel(ctk.CTkFrame):
|
||||
"""中间图片预览面板"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
parent,
|
||||
sqlite_manager,
|
||||
on_download_callback: Callable,
|
||||
**kwargs
|
||||
):
|
||||
super().__init__(parent, **kwargs)
|
||||
|
||||
self.sqlite_manager = sqlite_manager
|
||||
self.on_download_callback = on_download_callback
|
||||
|
||||
# 数据
|
||||
self.current_data = []
|
||||
self.total_count = 0
|
||||
self.current_page = 1
|
||||
self.selected_ids = set()
|
||||
|
||||
self._create_widgets()
|
||||
|
||||
def _create_widgets(self):
|
||||
"""创建UI组件"""
|
||||
# 标题栏
|
||||
header_frame = ctk.CTkFrame(self)
|
||||
header_frame.pack(fill="x", padx=15, pady=(15, 10))
|
||||
|
||||
title_label = ctk.CTkLabel(
|
||||
header_frame,
|
||||
text="图片预览",
|
||||
font=("Arial", 16, "bold")
|
||||
)
|
||||
title_label.pack(side="left", padx=10)
|
||||
|
||||
# 统计信息
|
||||
self.count_label = ctk.CTkLabel(
|
||||
header_frame,
|
||||
text="共 0 张 | 已选 0 张",
|
||||
font=("Arial", 12),
|
||||
text_color="gray"
|
||||
)
|
||||
self.count_label.pack(side="left", padx=20)
|
||||
|
||||
# 图片网格(可滚动)
|
||||
self.scroll_frame = ctk.CTkScrollableFrame(self)
|
||||
self.scroll_frame.pack(fill="both", expand=True, padx=15, pady=(0, 10))
|
||||
|
||||
# 网格容器
|
||||
self.grid_frame = ctk.CTkFrame(self.scroll_frame)
|
||||
self.grid_frame.pack(fill="both", expand=True)
|
||||
|
||||
# 底部操作栏
|
||||
bottom_frame = ctk.CTkFrame(self)
|
||||
bottom_frame.pack(fill="x", padx=15, pady=(0, 15))
|
||||
|
||||
# 左侧按钮
|
||||
left_buttons = ctk.CTkFrame(bottom_frame)
|
||||
left_buttons.pack(side="left", padx=10, pady=10)
|
||||
|
||||
self.select_all_btn = ctk.CTkButton(
|
||||
left_buttons,
|
||||
text="全选",
|
||||
command=self._select_all,
|
||||
width=80,
|
||||
height=30
|
||||
)
|
||||
self.select_all_btn.pack(side="left", padx=5)
|
||||
|
||||
self.deselect_all_btn = ctk.CTkButton(
|
||||
left_buttons,
|
||||
text="取消全选",
|
||||
command=self._deselect_all,
|
||||
width=80,
|
||||
height=30,
|
||||
fg_color="gray",
|
||||
hover_color="darkgray"
|
||||
)
|
||||
self.deselect_all_btn.pack(side="left", padx=5)
|
||||
|
||||
self.download_btn = ctk.CTkButton(
|
||||
left_buttons,
|
||||
text="批量下载",
|
||||
command=self._on_download,
|
||||
width=100,
|
||||
height=30,
|
||||
fg_color="green",
|
||||
hover_color="darkgreen"
|
||||
)
|
||||
self.download_btn.pack(side="left", padx=5)
|
||||
|
||||
# 分页控件
|
||||
page_frame = ctk.CTkFrame(bottom_frame)
|
||||
page_frame.pack(side="right", padx=10, pady=10)
|
||||
|
||||
self.prev_btn = ctk.CTkButton(
|
||||
page_frame,
|
||||
text="上一页",
|
||||
command=self._prev_page,
|
||||
width=80,
|
||||
height=30
|
||||
)
|
||||
self.prev_btn.pack(side="left", padx=5)
|
||||
|
||||
self.page_label = ctk.CTkLabel(
|
||||
page_frame,
|
||||
text="第 1 页",
|
||||
font=("Arial", 11)
|
||||
)
|
||||
self.page_label.pack(side="left", padx=10)
|
||||
|
||||
self.next_btn = ctk.CTkButton(
|
||||
page_frame,
|
||||
text="下一页",
|
||||
command=self._next_page,
|
||||
width=80,
|
||||
height=30
|
||||
)
|
||||
self.next_btn.pack(side="left", padx=5)
|
||||
|
||||
def load_data(self, data_list: List[Dict], total_count: int, page: int = 1):
|
||||
"""加载图片数据"""
|
||||
self.current_data = data_list
|
||||
self.total_count = total_count
|
||||
self.current_page = page
|
||||
self.selected_ids.clear()
|
||||
|
||||
self._update_grid()
|
||||
self._update_stats()
|
||||
self._update_pagination()
|
||||
|
||||
def _update_grid(self):
|
||||
"""更新图片网格"""
|
||||
# 清空现有卡片
|
||||
for widget in self.grid_frame.winfo_children():
|
||||
widget.destroy()
|
||||
|
||||
if not self.current_data:
|
||||
no_data_label = ctk.CTkLabel(
|
||||
self.grid_frame,
|
||||
text="暂无数据",
|
||||
font=("Arial", 14),
|
||||
text_color="gray"
|
||||
)
|
||||
no_data_label.grid(row=0, column=0, pady=50, padx=50)
|
||||
return
|
||||
|
||||
# 创建网格卡片
|
||||
for i, data in enumerate(self.current_data):
|
||||
row = i // GRID_COLUMNS
|
||||
col = i % GRID_COLUMNS
|
||||
|
||||
# 检查是否已下载
|
||||
is_downloaded = self.sqlite_manager.is_downloaded(
|
||||
data['datasource'],
|
||||
data['image_url']
|
||||
)
|
||||
|
||||
card = ImageCard(
|
||||
self.grid_frame,
|
||||
data=data,
|
||||
is_downloaded=is_downloaded,
|
||||
on_check_changed=self._on_check_changed,
|
||||
on_preview=self._on_preview
|
||||
)
|
||||
card.grid(
|
||||
row=row,
|
||||
column=col,
|
||||
padx=CARD_PADDING,
|
||||
pady=CARD_PADDING,
|
||||
sticky="nsew"
|
||||
)
|
||||
|
||||
# 配置网格列权重
|
||||
for col in range(GRID_COLUMNS):
|
||||
self.grid_frame.grid_columnconfigure(col, weight=1)
|
||||
|
||||
def _update_stats(self):
|
||||
"""更新统计信息"""
|
||||
selected_count = len(self.selected_ids)
|
||||
self.count_label.configure(
|
||||
text=f"共 {self.total_count} 张 | 已选 {selected_count} 张"
|
||||
)
|
||||
|
||||
def _update_pagination(self):
|
||||
"""更新分页控件"""
|
||||
total_pages = (self.total_count + ITEMS_PER_PAGE - 1) // ITEMS_PER_PAGE
|
||||
if total_pages == 0:
|
||||
total_pages = 1
|
||||
|
||||
self.page_label.configure(text=f"第 {self.current_page}/{total_pages} 页")
|
||||
|
||||
# 更新按钮状态
|
||||
self.prev_btn.configure(state="normal" if self.current_page > 1 else "disabled")
|
||||
self.next_btn.configure(state="normal" if self.current_page < total_pages else "disabled")
|
||||
|
||||
def _on_check_changed(self, item_id: int, checked: bool):
|
||||
"""勾选框改变"""
|
||||
if checked:
|
||||
self.selected_ids.add(item_id)
|
||||
else:
|
||||
self.selected_ids.discard(item_id)
|
||||
|
||||
self._update_stats()
|
||||
|
||||
def _select_all(self):
|
||||
"""全选"""
|
||||
for widget in self.grid_frame.winfo_children():
|
||||
if isinstance(widget, ImageCard):
|
||||
widget.set_checked(True)
|
||||
self.selected_ids.add(widget.data['id'])
|
||||
|
||||
self._update_stats()
|
||||
|
||||
def _deselect_all(self):
|
||||
"""取消全选"""
|
||||
for widget in self.grid_frame.winfo_children():
|
||||
if isinstance(widget, ImageCard):
|
||||
widget.set_checked(False)
|
||||
|
||||
self.selected_ids.clear()
|
||||
self._update_stats()
|
||||
|
||||
def _on_download(self):
|
||||
"""批量下载"""
|
||||
if not self.selected_ids:
|
||||
messagebox.showwarning("提示", "请先选择要下载的图片")
|
||||
return
|
||||
|
||||
# 获取选中的图片数据
|
||||
selected_data = [
|
||||
data for data in self.current_data
|
||||
if data['id'] in self.selected_ids
|
||||
]
|
||||
|
||||
# 调用下载回调
|
||||
if self.on_download_callback:
|
||||
self.on_download_callback(selected_data)
|
||||
|
||||
def _on_preview(self, data: Dict):
|
||||
"""查看大图"""
|
||||
# 创建预览窗口
|
||||
preview_window = ctk.CTkToplevel(self)
|
||||
preview_window.title(f"预览 - {data['goods_name']}")
|
||||
preview_window.geometry("800x700")
|
||||
|
||||
# 窗口属性
|
||||
preview_window.transient(self)
|
||||
preview_window.grab_set()
|
||||
preview_window.lift()
|
||||
preview_window.focus_set()
|
||||
|
||||
# 居中显示
|
||||
preview_window.update_idletasks()
|
||||
x = (preview_window.winfo_screenwidth() // 2) - 400
|
||||
y = (preview_window.winfo_screenheight() // 2) - 350
|
||||
preview_window.geometry(f"800x700+{x}+{y}")
|
||||
|
||||
# 信息框架
|
||||
info_frame = ctk.CTkFrame(preview_window)
|
||||
info_frame.pack(fill="x", padx=20, pady=(20, 10))
|
||||
|
||||
info_text = f"""
|
||||
物品名称: {data['goods_name']}
|
||||
采集时间: {data['create_time']}
|
||||
设备编码: {data['equipment_code']}
|
||||
数据源: {data['datasource']}
|
||||
""".strip()
|
||||
|
||||
info_label = ctk.CTkLabel(
|
||||
info_frame,
|
||||
text=info_text,
|
||||
font=("Arial", 12),
|
||||
justify="left"
|
||||
)
|
||||
info_label.pack(pady=10, padx=10)
|
||||
|
||||
# 图片容器
|
||||
image_container = ctk.CTkLabel(
|
||||
preview_window,
|
||||
text="加载中...",
|
||||
width=760,
|
||||
height=550
|
||||
)
|
||||
image_container.pack(pady=10, padx=20)
|
||||
|
||||
# 异步加载大图
|
||||
def load_large_image():
|
||||
image = load_image_from_url(data['image_url'], size=(760, 550))
|
||||
if image:
|
||||
image_container.configure(image=image, text="")
|
||||
image_container.image = image
|
||||
else:
|
||||
image_container.configure(text="图片加载失败")
|
||||
|
||||
threading.Thread(target=load_large_image, daemon=True).start()
|
||||
|
||||
def _prev_page(self):
|
||||
"""上一页"""
|
||||
if self.current_page > 1:
|
||||
# 需要重新查询数据(在主应用中处理)
|
||||
pass
|
||||
|
||||
def _next_page(self):
|
||||
"""下一页"""
|
||||
total_pages = (self.total_count + ITEMS_PER_PAGE - 1) // ITEMS_PER_PAGE
|
||||
if self.current_page < total_pages:
|
||||
# 需要重新查询数据(在主应用中处理)
|
||||
pass
|
||||
|
||||
def get_current_page(self) -> int:
|
||||
"""获取当前页码"""
|
||||
return self.current_page
|
||||
|
||||
def clear(self):
|
||||
"""清空数据"""
|
||||
self.current_data = []
|
||||
self.total_count = 0
|
||||
self.current_page = 1
|
||||
self.selected_ids.clear()
|
||||
self._update_grid()
|
||||
self._update_stats()
|
||||
self._update_pagination()
|
||||
Reference in New Issue
Block a user