feat: add column sorting functionality to material validation table

Add sorting capability for "选择" and "材料名称" columns in the CheckboxTreeview:
- Click column header to cycle through: asc (↑) → desc (↓) → unsort
- Sort "选择" column by checkbox state (checked/unchecked)
- Sort "材料名称" column alphabetically
- Preserve checkbox states during sorting using move() instead of delete+insert
- Separate event handlers for cell clicks and heading clicks

Implementation details:
- Added 6 new helper methods to CheckboxTreeview class
- Store original headings to properly display sort arrows
- Use identify_region() to distinguish between cell and heading clicks
- Column index conversion (#1/#2) to column identifiers

The sorting is a pure frontend feature with no database changes.
Available to all users without permission restrictions.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
Misaka_Company
2026-02-24 15:45:55 +08:00
parent 4fcb29f488
commit 0644bdfb11
4 changed files with 820 additions and 0 deletions

View File

@@ -45,14 +45,26 @@ class CheckboxTreeview(ttk.Treeview):
self.checkbox_column = "选择"
self.on_checkbox_change = on_checkbox_change # checkbox 状态改变回调
# 排序状态
self.sort_column = None # 当前排序列的列标识符
self.sort_direction = None # 'asc', 'desc', 或 None
self.sortable_columns = ["选择", "材料名称"] # 可排序的列白名单
self.original_headings = {} # 存储原始列标题文本(不含箭头)
# 存储原始列标题(延迟执行以确保标题已设置)
self.after(100, self._store_original_headings)
# 绑定点击事件
self.bind("<Button-1>", self._on_click)
# 绑定表头点击事件
self.bind("<ButtonRelease-1>", self._on_heading_click)
def _on_click(self, event):
"""处理点击事件,切换 checkbox 状态"""
# 获取点击位置对应的 item 和 column
region = self.identify_region(event.x, event.y)
# 仅处理单元格点击,不处理表头点击
if region == "cell":
column = self.identify_column(event.x)
item = self.identify_row(event.y)
@@ -127,6 +139,115 @@ class CheckboxTreeview(ttk.Treeview):
del self.checkboxes[item]
super().delete(*items)
def _store_original_headings(self):
"""存储原始列标题文本(不含箭头)"""
for col in self['columns']:
self.original_headings[col] = self.heading(col, 'text')
def _get_column_id_from_column_index(self, column_index):
"""将列索引 ('#1', '#2') 转换为列标识符
Args:
column_index: 列索引字符串,如 '#1', '#2'
Returns:
列标识符,如 '选择', '材料名称'
"""
index = int(column_index[1:]) - 1
columns = self['columns']
if 0 <= index < len(columns):
return columns[index]
return None
def _on_heading_click(self, event):
"""处理表头点击事件,触发排序"""
region = self.identify_region(event.x, event.y)
if region == "heading":
column = self.identify_column(event.x)
column_id = self._get_column_id_from_column_index(column)
# 仅对可排序列进行排序
if column_id in self.sortable_columns:
self._toggle_sort(column_id)
def _toggle_sort(self, column_id):
"""切换指定列的排序状态
Args:
column_id: 列标识符(如 '选择', '材料名称'
"""
# 确定新的排序方向
if self.sort_column == column_id:
# 同一列asc -> desc -> None
if self.sort_direction == 'asc':
new_direction = 'desc'
elif self.sort_direction == 'desc':
new_direction = None
else:
new_direction = 'asc'
else:
# 不同列:从升序开始
new_direction = 'asc'
# 应用排序
if new_direction:
self._sort_by_column(column_id, new_direction)
self.sort_column = column_id
self.sort_direction = new_direction
else:
# 清除排序状态
self.sort_column = None
self.sort_direction = None
# 更新表头显示
self._update_heading_display()
def _sort_by_column(self, column_id, direction):
"""按指定列和方向排序
Args:
column_id: 列标识符
direction: 'asc''desc'
"""
# 收集所有项目及其数据和复选框状态
items_data = []
for item in self.get_children():
values = self.item(item, "values")
checkbox_state = self.checkboxes.get(item, False)
items_data.append({
'item_id': item,
'values': values,
'checked': checkbox_state
})
# 根据列和方向排序
if column_id == "选择":
# 按复选框状态排序(选中在前,未选中在后)
items_data.sort(key=lambda x: x['checked'], reverse=(direction == 'desc'))
elif column_id == "材料名称":
# 按材料名称排序
items_data.sort(
key=lambda x: str(x['values'][1]) if len(x['values']) > 1 else "",
reverse=(direction == 'desc')
)
# 重新排列项目顺序(使用 detach 和 move 保留项目ID和状态
for item_data in items_data:
self.move(item_data['item_id'], '', 'end')
def _update_heading_display(self):
"""更新列标题显示(添加/移除排序箭头)"""
for col in self['columns']:
original = self.original_headings.get(col, col)
if col == self.sort_column:
# 添加排序箭头
arrow = "" if self.sort_direction == 'asc' else ""
self.heading(col, text=original + arrow)
else:
# 移除箭头,显示原始标题
self.heading(col, text=original)
class MaterialValidationTab(ttk.Frame):
"""物料校验标签页"""