增加了查看入库秤上传图片的GUI界面。

This commit is contained in:
2025-11-27 10:46:53 +08:00
parent c74ac621a6
commit 1bedc16bd8
16 changed files with 2703 additions and 0 deletions
+8
View File
@@ -0,0 +1,8 @@
"""
UI模块
"""
from .filter_panel import FilterPanel
from .preview_panel import PreviewPanel
from .statistics_panel import StatisticsPanel
__all__ = ['FilterPanel', 'PreviewPanel', 'StatisticsPanel']
+278
View File
@@ -0,0 +1,278 @@
"""
左侧筛选面板
"""
import customtkinter as ctk
from datetime import datetime, timedelta
from typing import Callable, Optional
import sys
import os
# 添加父目录到路径
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
from config import TIME_SHORTCUTS
class FilterPanel(ctk.CTkFrame):
"""左侧筛选面板"""
def __init__(
self,
parent,
on_query_callback: Callable,
on_datasource_change_callback: Optional[Callable] = None,
**kwargs
):
"""
初始化筛选面板
Args:
parent: 父窗口
on_query_callback: 查询回调函数
on_datasource_change_callback: 数据源切换回调函数
"""
super().__init__(parent, **kwargs)
self.on_query_callback = on_query_callback
self.on_datasource_change_callback = on_datasource_change_callback
# 创建UI组件
self._create_widgets()
def _create_widgets(self):
"""创建UI组件"""
# 标题
title_label = ctk.CTkLabel(
self,
text="筛选条件",
font=("Arial", 16, "bold")
)
title_label.pack(pady=(15, 10), padx=15)
# ==================== 数据源选择 ====================
datasource_frame = ctk.CTkFrame(self)
datasource_frame.pack(fill="x", padx=15, pady=(0, 10))
datasource_label = ctk.CTkLabel(
datasource_frame,
text="数据源:",
font=("Arial", 12, "bold")
)
datasource_label.pack(pady=(10, 5), padx=10, anchor="w")
# 数据源单选按钮
self.datasource_var = ctk.StringVar(value="菜品")
datasource_options = ["菜品", "食材", "半加工"]
for option in datasource_options:
radio_btn = ctk.CTkRadioButton(
datasource_frame,
text=option,
variable=self.datasource_var,
value=option,
command=self._on_datasource_changed
)
radio_btn.pack(pady=3, padx=20, anchor="w")
# ==================== 时间范围 ====================
time_frame = ctk.CTkFrame(self)
time_frame.pack(fill="x", padx=15, pady=(0, 10))
time_label = ctk.CTkLabel(
time_frame,
text="时间范围:",
font=("Arial", 12, "bold")
)
time_label.pack(pady=(10, 5), padx=10, anchor="w")
# 快捷时间按钮
shortcuts_frame = ctk.CTkFrame(time_frame)
shortcuts_frame.pack(fill="x", padx=10, pady=(0, 5))
for text, days in TIME_SHORTCUTS.items():
btn = ctk.CTkButton(
shortcuts_frame,
text=text,
command=lambda d=days: self._set_time_shortcut(d),
width=60,
height=25,
font=("Arial", 10)
)
btn.pack(side="left", padx=2, pady=5)
# 开始日期
start_date_label = ctk.CTkLabel(
time_frame,
text="开始日期:",
font=("Arial", 11)
)
start_date_label.pack(pady=(5, 2), padx=10, anchor="w")
self.start_date_entry = ctk.CTkEntry(
time_frame,
placeholder_text="YYYY-MM-DD",
width=200
)
self.start_date_entry.pack(pady=(0, 5), padx=10)
# 结束日期
end_date_label = ctk.CTkLabel(
time_frame,
text="结束日期:",
font=("Arial", 11)
)
end_date_label.pack(pady=(5, 2), padx=10, anchor="w")
self.end_date_entry = ctk.CTkEntry(
time_frame,
placeholder_text="YYYY-MM-DD",
width=200
)
self.end_date_entry.pack(pady=(0, 10), padx=10)
# ==================== 物品筛选 ====================
goods_frame = ctk.CTkFrame(self)
goods_frame.pack(fill="x", padx=15, pady=(0, 10))
goods_label = ctk.CTkLabel(
goods_frame,
text="物品筛选:",
font=("Arial", 12, "bold")
)
goods_label.pack(pady=(10, 5), padx=10, anchor="w")
self.goods_search_entry = ctk.CTkEntry(
goods_frame,
placeholder_text="输入物品名称搜索",
width=200
)
self.goods_search_entry.pack(pady=(0, 10), padx=10)
# ==================== 设备编码 ====================
equipment_frame = ctk.CTkFrame(self)
equipment_frame.pack(fill="x", padx=15, pady=(0, 10))
equipment_label = ctk.CTkLabel(
equipment_frame,
text="设备编码:",
font=("Arial", 12, "bold")
)
equipment_label.pack(pady=(10, 5), padx=10, anchor="w")
self.equipment_entry = ctk.CTkEntry(
equipment_frame,
placeholder_text="可选,精确匹配",
width=200
)
self.equipment_entry.pack(pady=(0, 10), padx=10)
# ==================== 按钮区域 ====================
button_frame = ctk.CTkFrame(self)
button_frame.pack(fill="x", padx=15, pady=(10, 15))
# 查询按钮
self.query_button = ctk.CTkButton(
button_frame,
text="查询",
command=self._on_query,
height=35,
fg_color="green",
hover_color="darkgreen"
)
self.query_button.pack(fill="x", padx=10, pady=(10, 5))
# 重置按钮
self.reset_button = ctk.CTkButton(
button_frame,
text="重置",
command=self._on_reset,
height=35,
fg_color="gray",
hover_color="darkgray"
)
self.reset_button.pack(fill="x", padx=10, pady=(5, 10))
def _set_time_shortcut(self, days: int):
"""设置时间快捷选项"""
end_date = datetime.now()
if days == 0:
# 今日
start_date = end_date.replace(hour=0, minute=0, second=0, microsecond=0)
else:
# 最近N天
start_date = end_date - timedelta(days=days)
self.start_date_entry.delete(0, 'end')
self.start_date_entry.insert(0, start_date.strftime('%Y-%m-%d'))
self.end_date_entry.delete(0, 'end')
self.end_date_entry.insert(0, end_date.strftime('%Y-%m-%d'))
def _on_datasource_changed(self):
"""数据源改变时的回调"""
if self.on_datasource_change_callback:
self.on_datasource_change_callback(self.datasource_var.get())
def _on_query(self):
"""查询按钮点击"""
# 获取筛选条件
filters = self.get_filters()
# 调用回调函数
if self.on_query_callback:
self.on_query_callback(filters)
def _on_reset(self):
"""重置按钮点击"""
# 清空所有输入
self.start_date_entry.delete(0, 'end')
self.end_date_entry.delete(0, 'end')
self.goods_search_entry.delete(0, 'end')
self.equipment_entry.delete(0, 'end')
# 重置数据源为菜品
self.datasource_var.set("菜品")
def get_filters(self) -> dict:
"""获取当前筛选条件"""
filters = {
'datasource': self.datasource_var.get(),
'start_date': None,
'end_date': None,
'goods_name': None,
'equipment_code': None
}
# 解析开始日期
start_date_str = self.start_date_entry.get().strip()
if start_date_str:
try:
filters['start_date'] = datetime.strptime(start_date_str, '%Y-%m-%d')
except ValueError:
pass
# 解析结束日期
end_date_str = self.end_date_entry.get().strip()
if end_date_str:
try:
filters['end_date'] = datetime.strptime(end_date_str, '%Y-%m-%d')
except ValueError:
pass
# 物品名称
goods_name = self.goods_search_entry.get().strip()
if goods_name:
filters['goods_name'] = goods_name
# 设备编码
equipment_code = self.equipment_entry.get().strip()
if equipment_code:
filters['equipment_code'] = equipment_code
return filters
def set_loading_state(self, loading: bool):
"""设置加载状态"""
if loading:
self.query_button.configure(state="disabled", text="查询中...")
else:
self.query_button.configure(state="normal", text="查询")
+469
View File
@@ -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()
+296
View File
@@ -0,0 +1,296 @@
"""
右侧统计面板
"""
import customtkinter as ctk
from typing import Dict, Callable, Optional
class StatisticsPanel(ctk.CTkFrame):
"""右侧统计面板"""
def __init__(
self,
parent,
on_refresh_callback: Callable,
on_download_history_callback: Callable,
on_export_callback: Callable,
on_settings_callback: Callable,
**kwargs
):
super().__init__(parent, **kwargs)
self.on_refresh_callback = on_refresh_callback
self.on_download_history_callback = on_download_history_callback
self.on_export_callback = on_export_callback
self.on_settings_callback = on_settings_callback
self._create_widgets()
def _create_widgets(self):
"""创建UI组件"""
# 标题
title_label = ctk.CTkLabel(
self,
text="统计信息",
font=("Arial", 16, "bold")
)
title_label.pack(pady=(15, 10), padx=15)
# ==================== 当前数据源信息 ====================
datasource_frame = ctk.CTkFrame(self)
datasource_frame.pack(fill="x", padx=15, pady=(0, 10))
datasource_title = ctk.CTkLabel(
datasource_frame,
text="当前数据源",
font=("Arial", 12, "bold")
)
datasource_title.pack(pady=(10, 5), padx=10)
self.datasource_label = ctk.CTkLabel(
datasource_frame,
text="菜品",
font=("Arial", 14, "bold"),
text_color="blue"
)
self.datasource_label.pack(pady=(0, 10), padx=10)
# ==================== 数据统计 ====================
stats_frame = ctk.CTkFrame(self)
stats_frame.pack(fill="x", padx=15, pady=(0, 10))
stats_title = ctk.CTkLabel(
stats_frame,
text="数据统计",
font=("Arial", 12, "bold")
)
stats_title.pack(pady=(10, 5), padx=10)
# 查询结果
self.query_result_label = ctk.CTkLabel(
stats_frame,
text="查询结果: 0 张",
font=("Arial", 11),
anchor="w"
)
self.query_result_label.pack(fill="x", pady=2, padx=15)
# 已选择
self.selected_label = ctk.CTkLabel(
stats_frame,
text="已选择: 0 张",
font=("Arial", 11),
anchor="w"
)
self.selected_label.pack(fill="x", pady=2, padx=15)
# 分隔线
separator1 = ctk.CTkFrame(stats_frame, height=2)
separator1.pack(fill="x", pady=10, padx=15)
# ==================== 时间分布 ====================
time_frame = ctk.CTkFrame(self)
time_frame.pack(fill="x", padx=15, pady=(0, 10))
time_title = ctk.CTkLabel(
time_frame,
text="时间分布",
font=("Arial", 12, "bold")
)
time_title.pack(pady=(10, 5), padx=10)
# 今日
self.today_label = ctk.CTkLabel(
time_frame,
text="今日: 0 张",
font=("Arial", 11),
anchor="w"
)
self.today_label.pack(fill="x", pady=2, padx=15)
# 本周
self.week_label = ctk.CTkLabel(
time_frame,
text="本周: 0 张",
font=("Arial", 11),
anchor="w"
)
self.week_label.pack(fill="x", pady=2, padx=15)
# 本月
self.month_label = ctk.CTkLabel(
time_frame,
text="本月: 0 张",
font=("Arial", 11),
anchor="w"
)
self.month_label.pack(fill="x", pady=2, padx=15)
# 分隔线
separator2 = ctk.CTkFrame(time_frame, height=2)
separator2.pack(fill="x", pady=10, padx=15)
# ==================== TOP 10物品排行 ====================
top_frame = ctk.CTkFrame(self)
top_frame.pack(fill="both", expand=True, padx=15, pady=(0, 10))
top_title = ctk.CTkLabel(
top_frame,
text="TOP 10 物品排行",
font=("Arial", 12, "bold")
)
top_title.pack(pady=(10, 5), padx=10)
# 可滚动的排行榜
self.top_scroll = ctk.CTkScrollableFrame(top_frame, height=200)
self.top_scroll.pack(fill="both", expand=True, padx=10, pady=(0, 10))
# ==================== 快捷操作 ====================
actions_frame = ctk.CTkFrame(self)
actions_frame.pack(fill="x", padx=15, pady=(0, 15))
actions_title = ctk.CTkLabel(
actions_frame,
text="快捷操作",
font=("Arial", 12, "bold")
)
actions_title.pack(pady=(10, 5), padx=10)
# 刷新按钮
refresh_btn = ctk.CTkButton(
actions_frame,
text="🔄 刷新",
command=self.on_refresh_callback,
height=30
)
refresh_btn.pack(fill="x", padx=10, pady=2)
# 下载历史按钮
history_btn = ctk.CTkButton(
actions_frame,
text="📜 下载历史",
command=self.on_download_history_callback,
height=30,
fg_color="purple",
hover_color="darkviolet"
)
history_btn.pack(fill="x", padx=10, pady=2)
# 导出按钮
export_btn = ctk.CTkButton(
actions_frame,
text="📊 导出数据",
command=self.on_export_callback,
height=30,
fg_color="orange",
hover_color="darkorange"
)
export_btn.pack(fill="x", padx=10, pady=2)
# 设置按钮
settings_btn = ctk.CTkButton(
actions_frame,
text="⚙️ 设置",
command=self.on_settings_callback,
height=30,
fg_color="gray",
hover_color="darkgray"
)
settings_btn.pack(fill="x", padx=10, pady=(2, 10))
def update_datasource(self, datasource: str):
"""更新当前数据源显示"""
self.datasource_label.configure(text=datasource)
def update_statistics(self, stats: Dict):
"""
更新统计信息
Args:
stats: 统计数据字典,包含:
- total_count: 总数量
- today_count: 今日数量
- week_count: 本周数量
- month_count: 本月数量
- top_items: TOP 10列表 [{'name': str, 'count': int}, ...]
"""
# 查询结果
total_count = stats.get('total_count', 0)
self.query_result_label.configure(text=f"查询结果: {total_count}")
# 时间分布
today_count = stats.get('today_count', 0)
self.today_label.configure(text=f"今日: {today_count}")
week_count = stats.get('week_count', 0)
self.week_label.configure(text=f"本周: {week_count}")
month_count = stats.get('month_count', 0)
self.month_label.configure(text=f"本月: {month_count}")
# TOP 10排行
self._update_top_items(stats.get('top_items', []))
def _update_top_items(self, top_items: list):
"""更新TOP 10排行榜"""
# 清空现有内容
for widget in self.top_scroll.winfo_children():
widget.destroy()
if not top_items:
no_data_label = ctk.CTkLabel(
self.top_scroll,
text="暂无数据",
font=("Arial", 11),
text_color="gray"
)
no_data_label.pack(pady=10)
return
# 显示TOP 10
for i, item in enumerate(top_items, 1):
item_frame = ctk.CTkFrame(self.top_scroll)
item_frame.pack(fill="x", pady=2, padx=5)
# 排名
rank_label = ctk.CTkLabel(
item_frame,
text=f"{i}.",
font=("Arial", 11, "bold"),
width=30
)
rank_label.pack(side="left", padx=(5, 2))
# 物品名称
name_label = ctk.CTkLabel(
item_frame,
text=item['name'],
font=("Arial", 10),
anchor="w"
)
name_label.pack(side="left", fill="x", expand=True, padx=5)
# 数量
count_label = ctk.CTkLabel(
item_frame,
text=f"{item['count']}",
font=("Arial", 10),
text_color="blue"
)
count_label.pack(side="right", padx=5)
def update_selected_count(self, count: int):
"""更新已选择数量"""
self.selected_label.configure(text=f"已选择: {count}")
def clear_statistics(self):
"""清空统计信息"""
self.query_result_label.configure(text="查询结果: 0 张")
self.selected_label.configure(text="已选择: 0 张")
self.today_label.configure(text="今日: 0 张")
self.week_label.configure(text="本周: 0 张")
self.month_label.configure(text="本月: 0 张")
# 清空TOP 10
for widget in self.top_scroll.winfo_children():
widget.destroy()