This commit is contained in:
root
2026-02-10 02:54:13 +08:00
parent 4a59323398
commit 64bfd85368
22 changed files with 2572 additions and 422 deletions

View File

@@ -8,6 +8,237 @@ import logging
logger = logging.getLogger(__name__)
class DependencyCheckDialog:
"""依赖检查对话框 - 显示系统依赖状态"""
def __init__(self, parent):
self.parent = parent
self.result = None
self.dialog = tk.Toplevel(parent)
self.dialog.title("系统依赖检查")
self.dialog.geometry("650x550")
self.dialog.minsize(600, 400)
self.dialog.transient(parent)
self.dialog.grab_set()
self._center_window()
self._create_widgets()
def _center_window(self):
"""居中窗口"""
self.dialog.update_idletasks()
width = 650
height = 550
x = (self.dialog.winfo_screenwidth() // 2) - (width // 2)
y = (self.dialog.winfo_screenheight() // 2) - (height // 2)
self.dialog.geometry(f'{width}x{height}+{x}+{y}')
def _create_widgets(self):
"""创建界面"""
from dependency_checker import get_dependency_checker
checker = get_dependency_checker()
summary = checker.get_summary()
# 主框架
main_frame = ttk.Frame(self.dialog, padding="15")
main_frame.pack(fill=tk.BOTH, expand=True)
# 标题
ttk.Label(main_frame, text="系统依赖状态",
font=('Arial', 14, 'bold')).pack(anchor=tk.W, pady=(0, 10))
# 摘要信息
summary_frame = ttk.LabelFrame(main_frame, text="摘要", padding="10")
summary_frame.pack(fill=tk.X, pady=(0, 10))
ttk.Label(summary_frame,
text=f"操作系统: {summary['os_type'].upper()}").pack(anchor=tk.W)
ttk.Label(summary_frame,
text=f"总依赖项: {summary['total']} | 已安装: {summary['complete']} | 缺失: {summary['required_missing']}",
foreground='green' if summary['required_missing'] == 0 else 'red').pack(anchor=tk.W)
if summary['required_missing'] > 0:
ttk.Label(summary_frame,
text=f"⚠ 有 {summary['required_missing']} 个必需依赖未安装,部分功能可能不可用",
foreground='red').pack(anchor=tk.W, pady=(5, 0))
# 创建 notebook 标签页
notebook = ttk.Notebook(main_frame)
notebook.pack(fill=tk.BOTH, expand=True, pady=(0, 10))
# 文件系统页
fs_frame = ttk.Frame(notebook, padding="10")
notebook.add(fs_frame, text="文件系统")
self._create_category_frame(fs_frame, summary['categories']['filesystem'])
# 分区/LVM/RAID 页
storage_frame = ttk.Frame(notebook, padding="10")
notebook.add(storage_frame, text="分区/LVM/RAID")
self._create_category_frame(storage_frame, {**summary['categories']['partition'],
**summary['categories']['lvm'],
**summary['categories']['raid']})
# 其他工具页
other_frame = ttk.Frame(notebook, padding="10")
notebook.add(other_frame, text="其他工具")
self._create_category_frame(other_frame, summary['categories']['other'])
# 安装指南页
if summary['install_commands']:
install_frame = ttk.Frame(notebook, padding="10")
notebook.add(install_frame, text="安装指南")
self._create_install_frame(install_frame, summary)
# 底部按钮
btn_frame = ttk.Frame(main_frame)
btn_frame.pack(fill=tk.X, pady=5)
ttk.Button(btn_frame, text="刷新",
command=self._refresh).pack(side=tk.LEFT, padx=5)
ttk.Button(btn_frame, text="关闭",
command=self._on_close).pack(side=tk.RIGHT, padx=5)
def _create_category_frame(self, parent, items):
"""创建分类框架"""
canvas = tk.Canvas(parent)
scrollbar = ttk.Scrollbar(parent, orient="vertical", command=canvas.yview)
scrollable_frame = ttk.Frame(canvas)
scrollable_frame.bind(
"<Configure>",
lambda e: canvas.configure(scrollregion=canvas.bbox("all"))
)
canvas.create_window((0, 0), window=scrollable_frame, anchor="nw", width=580)
canvas.configure(yscrollcommand=scrollbar.set)
canvas.pack(side="left", fill="both", expand=True)
scrollbar.pack(side="right", fill="y")
for key, status in items.items():
self._create_item_frame(scrollable_frame, status)
def _create_item_frame(self, parent, status):
"""创建单个依赖项框架"""
frame = ttk.Frame(parent)
frame.pack(fill=tk.X, pady=3)
is_ok = status['is_complete']
is_optional = status['optional']
# 状态图标
if is_ok:
icon = ""
color = "green"
elif is_optional:
icon = ""
color = "orange"
else:
icon = ""
color = "red"
# 标题行
header_frame = ttk.Frame(frame)
header_frame.pack(fill=tk.X)
ttk.Label(header_frame, text=icon, foreground=color,
font=('Arial', 10, 'bold')).pack(side=tk.LEFT)
ttk.Label(header_frame, text=status['name'],
font=('Arial', 9, 'bold')).pack(side=tk.LEFT, padx=5)
if is_optional:
ttk.Label(header_frame, text="(可选)",
foreground="gray").pack(side=tk.LEFT)
# 详情
detail_frame = ttk.Frame(frame)
detail_frame.pack(fill=tk.X, padx=(20, 0))
if is_ok:
ttk.Label(detail_frame, text=f"已安装命令: {', '.join(status['installed'])}",
foreground="green").pack(anchor=tk.W)
else:
if status['missing']:
ttk.Label(detail_frame, text=f"缺失命令: {', '.join(status['missing'])}",
foreground="red").pack(anchor=tk.W)
ttk.Label(detail_frame, text=f"安装包: {status['package']}",
foreground="blue").pack(anchor=tk.W)
ttk.Separator(frame, orient='horizontal').pack(fill=tk.X, pady=5)
def _create_install_frame(self, parent, summary):
"""创建安装指南框架"""
# 安装命令
cmd_frame = ttk.LabelFrame(parent, text="一键安装命令", padding="10")
cmd_frame.pack(fill=tk.X, pady=(0, 10))
os_type = summary['os_type']
if os_type == 'centos':
cmd = "sudo yum install -y " + " ".join(summary['install_commands'].keys())
elif os_type == 'arch':
cmd = "sudo pacman -Sy --needed " + " ".join(summary['install_commands'].keys())
elif os_type == 'ubuntu':
cmd = "sudo apt-get install -y " + " ".join(summary['install_commands'].keys())
else:
cmd = "# 请根据您的发行版安装以下包: " + ", ".join(summary['install_commands'].keys())
text_widget = tk.Text(cmd_frame, height=3, wrap=tk.WORD,
font=('Consolas', 10))
text_widget.pack(fill=tk.X)
text_widget.insert('1.0', cmd)
text_widget.config(state='disabled')
# 包详情
detail_frame = ttk.LabelFrame(parent, text="包详情", padding="10")
detail_frame.pack(fill=tk.BOTH, expand=True)
canvas = tk.Canvas(detail_frame)
scrollbar = ttk.Scrollbar(detail_frame, orient="vertical", command=canvas.yview)
scrollable_frame = ttk.Frame(canvas)
scrollable_frame.bind(
"<Configure>",
lambda e: canvas.configure(scrollregion=canvas.bbox("all"))
)
canvas.create_window((0, 0), window=scrollable_frame, anchor="nw", width=550)
canvas.configure(yscrollcommand=scrollbar.set)
canvas.pack(side="left", fill="both", expand=True)
scrollbar.pack(side="right", fill="y")
for pkg, names in summary['install_commands'].items():
ttk.Label(scrollable_frame, text=f"{pkg}",
font=('Arial', 9, 'bold')).pack(anchor=tk.W)
ttk.Label(scrollable_frame,
text=f" 支持: {', '.join(names)}",
foreground="gray").pack(anchor=tk.W, padx=(15, 0))
def _refresh(self):
"""刷新检查"""
from dependency_checker import get_dependency_checker
# 清除缓存
checker = get_dependency_checker()
checker._cache = {}
# 重新创建界面
for widget in self.dialog.winfo_children():
widget.destroy()
self._create_widgets()
def _on_close(self):
"""关闭对话框"""
self.dialog.destroy()
def wait_for_result(self):
"""等待对话框关闭"""
self.dialog.wait_window()
class EnhancedFormatDialog:
"""增强格式化对话框 - 显示详细警告信息"""
@@ -19,7 +250,7 @@ class EnhancedFormatDialog:
self.dialog = tk.Toplevel(parent)
self.dialog.title(f"格式化确认 - {device_path}")
self.dialog.geometry("500x575")
self.dialog.geometry("500x500")
self.dialog.minsize(480, 450)
self.dialog.transient(parent)
self.dialog.grab_set()
@@ -28,14 +259,17 @@ class EnhancedFormatDialog:
self._create_widgets()
def _center_window(self):
"""居中窗口"""
self.dialog.update_idletasks()
width = self.dialog.winfo_width()
height = self.dialog.winfo_height()
width = 500
height = 500
x = (self.dialog.winfo_screenwidth() // 2) - (width // 2)
y = (self.dialog.winfo_screenheight() // 2) - (height // 2)
self.dialog.geometry(f"{width}x{height}+{x}+{y}")
self.dialog.geometry(f'{width}x{height}+{x}+{y}')
def _create_widgets(self):
"""创建界面元素"""
# 主框架
main_frame = ttk.Frame(self.dialog, padding="15")
main_frame.pack(fill=tk.BOTH, expand=True)
@@ -43,58 +277,45 @@ class EnhancedFormatDialog:
warning_frame = ttk.Frame(main_frame)
warning_frame.pack(fill=tk.X, pady=(0, 15))
warning_label = ttk.Label(
warning_frame,
text="⚠ 警告:此操作将永久删除所有数据!",
font=("Arial", 12, "bold"),
foreground="red"
)
warning_label.pack()
ttk.Label(warning_frame, text="⚠️ 警告",
font=('Arial', 16, 'bold'),
foreground='red').pack(anchor=tk.W)
# 设备信息区域
# 设备信息
info_frame = ttk.LabelFrame(main_frame, text="设备信息", padding="10")
info_frame.pack(fill=tk.X, pady=(0, 15))
# 显示详细信息
infos = [
("设备路径", self.device_path),
("设备名称", self.device_info.get('name', 'N/A')),
("文件系统", self.device_info.get('fstype', 'N/A')),
("大小", self.device_info.get('size', 'N/A')),
("UUID", self.device_info.get('uuid', 'N/A')),
("挂载点", self.device_info.get('mountpoint', '未挂载')),
]
ttk.Label(info_frame,
text=f"设备路径: {self.device_path}",
font=('Arial', 10, 'bold')).pack(anchor=tk.W, pady=2)
ttk.Label(info_frame,
text=f"设备类型: {self.device_info.get('type', 'Unknown')}",
font=('Arial', 9)).pack(anchor=tk.W, pady=2)
for i, (label, value) in enumerate(infos):
ttk.Label(info_frame, text=f"{label}:", font=("Arial", 10, "bold")).grid(
row=i, column=0, sticky=tk.W, padx=5, pady=3
)
val_label = ttk.Label(info_frame, text=str(value))
val_label.grid(row=i, column=1, sticky=tk.W, padx=5, pady=3)
# 显示文件系统信息
fstype = self.device_info.get('fstype', '')
if fstype:
ttk.Label(info_frame,
text=f"当前文件系统: {fstype}",
font=('Arial', 9)).pack(anchor=tk.W, pady=2)
# 如果有挂载点,高亮显示
if label == "挂载点" and value and value != '未挂载':
val_label.configure(foreground="orange", font=("Arial", 9, "bold"))
size = self.device_info.get('size', 'Unknown')
ttk.Label(info_frame,
text=f"设备大小: {size}",
font=('Arial', 9)).pack(anchor=tk.W, pady=2)
# 数据风险提示
risk_frame = ttk.LabelFrame(main_frame, text="数据风险提示", padding="10")
risk_frame.pack(fill=tk.X, pady=(0, 15))
# 危险警告
danger_frame = ttk.Frame(main_frame)
danger_frame.pack(fill=tk.X, pady=(0, 15))
risks = []
if self.device_info.get('fstype'):
risks.append(f"• 此分区包含 {self.device_info['fstype']} 文件系统")
if self.device_info.get('uuid'):
risks.append(f"• 分区有 UUID: {self.device_info['uuid'][:20]}...")
if self.device_info.get('mountpoint'):
risks.append(f"• 分区当前挂载在: {self.device_info['mountpoint']}")
ttk.Label(danger_frame,
text="此操作将永久删除设备上的所有数据!",
font=('Arial', 10, 'bold'),
foreground='red').pack(anchor=tk.W, pady=5)
if not risks:
risks.append("• 无法获取分区详细信息")
for risk in risks:
ttk.Label(risk_frame, text=risk, foreground="red").pack(
anchor=tk.W, pady=2
)
ttk.Label(danger_frame,
text="• 所有数据将无法恢复\n• 请确保已备份重要数据",
foreground='red').pack(anchor=tk.W, pady=5)
# 文件系统选择
fs_frame = ttk.LabelFrame(main_frame, text="选择新文件系统", padding="10")
@@ -107,19 +328,6 @@ class EnhancedFormatDialog:
ttk.Radiobutton(fs_frame, text=fs, variable=self.fs_var,
value=fs).pack(side=tk.LEFT, padx=10)
# 确认输入
confirm_frame = ttk.Frame(main_frame)
confirm_frame.pack(fill=tk.X, pady=(0, 15))
ttk.Label(confirm_frame,
text=f"请输入设备名 '{self.device_info.get('name', '确认')}' 以继续:",
foreground="red").pack(anchor=tk.W, pady=5)
self.confirm_var = tk.StringVar()
self.confirm_entry = ttk.Entry(confirm_frame, textvariable=self.confirm_var, width=20)
self.confirm_entry.pack(anchor=tk.W, pady=5)
self.confirm_entry.focus()
# 按钮
btn_frame = ttk.Frame(main_frame)
btn_frame.pack(fill=tk.X, pady=10)
@@ -130,12 +338,6 @@ class EnhancedFormatDialog:
command=self._on_confirm).pack(side=tk.RIGHT, padx=5)
def _on_confirm(self):
expected = self.device_info.get('name', '确认')
if self.confirm_var.get() != expected:
messagebox.showerror("错误",
f"输入错误!请输入 '{expected}' 以确认格式化操作。")
return
self.result = self.fs_var.get()
self.dialog.destroy()
@@ -158,9 +360,9 @@ class EnhancedRaidDeleteDialog:
self.result = False
self.dialog = tk.Toplevel(parent)
self.dialog.title(f"删除 RAID 阵列确认 - {array_path}")
self.dialog.geometry("550x750")
self.dialog.minsize(520, 750)
self.dialog.title(f"删除 RAID 阵列 - {array_path}")
self.dialog.geometry("500x500")
self.dialog.minsize(450, 400)
self.dialog.transient(parent)
self.dialog.grab_set()
@@ -168,96 +370,62 @@ class EnhancedRaidDeleteDialog:
self._create_widgets()
def _center_window(self):
"""居中窗口"""
self.dialog.update_idletasks()
width = self.dialog.winfo_width()
height = self.dialog.winfo_height()
width = 500
height = 500
x = (self.dialog.winfo_screenwidth() // 2) - (width // 2)
y = (self.dialog.winfo_screenheight() // 2) - (height // 2)
self.dialog.geometry(f"{width}x{height}+{x}+{y}")
self.dialog.geometry(f'{width}x{height}+{x}+{y}')
def _create_widgets(self):
"""创建界面元素"""
# 主框架
main_frame = ttk.Frame(self.dialog, padding="15")
main_frame.pack(fill=tk.BOTH, expand=True)
# 警告标题
ttk.Label(main_frame, text="⚠️ 确认删除 RAID 阵列",
font=('Arial', 14, 'bold'),
foreground='red').pack(anchor=tk.W, pady=(0, 10))
# 阵列信息
info_frame = ttk.LabelFrame(main_frame, text="阵列信息", padding="10")
info_frame.pack(fill=tk.X, pady=(0, 10))
ttk.Label(info_frame, text=f"阵列名称: {self.array_path}",
font=('Arial', 10, 'bold')).pack(anchor=tk.W, pady=2)
ttk.Label(info_frame, text=f"阵列类型: RAID {self.array_data.get('level', 'Unknown')}",
font=('Arial', 9)).pack(anchor=tk.W, pady=2)
ttk.Label(info_frame, text=f"阵列大小: {self.array_data.get('size', 'Unknown')}",
font=('Arial', 9)).pack(anchor=tk.W, pady=2)
# 成员设备
member_frame = ttk.LabelFrame(main_frame, text="成员设备", padding="10")
member_frame.pack(fill=tk.BOTH, expand=True, pady=(0, 10))
for dev in self.member_devices:
ttk.Label(member_frame, text=f"{dev}",
font=('Arial', 9)).pack(anchor=tk.W, pady=1)
# 危险警告
warning_frame = ttk.Frame(main_frame)
warning_frame.pack(fill=tk.X, pady=(0, 15))
danger_frame = ttk.Frame(main_frame)
danger_frame.pack(fill=tk.X, pady=(0, 10))
warning_label = ttk.Label(
warning_frame,
text="⚠ 危险操作:此操作将永久删除 RAID 阵列!",
font=("Arial", 13, "bold"),
foreground="red"
)
warning_label.pack()
ttk.Label(danger_frame,
text="⚠️ 删除操作将:",
font=('Arial', 10, 'bold'),
foreground='red').pack(anchor=tk.W, pady=5)
# 阵列详情
array_frame = ttk.LabelFrame(main_frame, text="阵列详情", padding="10")
array_frame.pack(fill=tk.X, pady=(0, 15))
details = [
("阵列设备", self.array_path),
("RAID 级别", self.array_data.get('level', 'N/A')),
("阵列状态", self.array_data.get('state', 'N/A')),
("阵列大小", self.array_data.get('array_size', 'N/A')),
("UUID", self.array_data.get('uuid', 'N/A')),
("Chunk 大小", self.array_data.get('chunk_size', 'N/A')),
warnings = [
"• 停止并删除 RAID 阵列",
"• 清除成员设备上的 RAID 超级块",
"• 从配置文件中移除阵列配置",
"• 所有数据将永久丢失且无法恢复"
]
for i, (label, value) in enumerate(details):
ttk.Label(array_frame, text=f"{label}:",
font=("Arial", 10, "bold")).grid(
row=i, column=0, sticky=tk.W, padx=5, pady=3
)
ttk.Label(array_frame, text=str(value)).grid(
row=i, column=1, sticky=tk.W, padx=5, pady=3
)
# 成员设备列表
member_frame = ttk.LabelFrame(main_frame, text="成员设备(将被清除 RAID 超级块)",
padding="10")
member_frame.pack(fill=tk.BOTH, expand=True, pady=(0, 15))
if self.member_devices:
for i, dev in enumerate(self.member_devices):
if dev:
ttk.Label(member_frame, text=f"{dev}",
font=("Arial", 10), foreground="red").pack(
anchor=tk.W, pady=2
)
else:
ttk.Label(member_frame, text=" 无法获取成员设备列表",
foreground="orange").pack(anchor=tk.W, pady=5)
# 后果警告
consequence_frame = ttk.LabelFrame(main_frame, text="操作后果", padding="10")
consequence_frame.pack(fill=tk.X, pady=(0, 15))
consequences = [
"• 阵列将被停止并从系统中移除",
"• 所有成员设备的 RAID 超级块将被清除",
"• 阵列上的所有数据将永久丢失",
"• /etc/mdadm.conf 中的配置将被删除",
"• 此操作无法撤销!"
]
for con in consequences:
ttk.Label(consequence_frame, text=con,
foreground="red").pack(anchor=tk.W, pady=2)
# 二次确认
confirm_frame = ttk.Frame(main_frame)
confirm_frame.pack(fill=tk.X, pady=(0, 10))
ttk.Label(confirm_frame,
text="请输入 'DELETE' 以确认删除操作:",
foreground="red", font=("Arial", 10, "bold")).pack(
anchor=tk.W, pady=5)
self.confirm_var = tk.StringVar()
self.confirm_entry = ttk.Entry(confirm_frame, textvariable=self.confirm_var, width=15)
self.confirm_entry.pack(anchor=tk.W, pady=5)
self.confirm_entry.focus()
for warning in warnings:
ttk.Label(danger_frame, text=warning,
foreground='red').pack(anchor=tk.W, pady=2)
# 按钮
btn_frame = ttk.Frame(main_frame)
@@ -266,23 +434,14 @@ class EnhancedRaidDeleteDialog:
ttk.Button(btn_frame, text="取消",
command=self._on_cancel).pack(side=tk.RIGHT, padx=5)
ttk.Button(btn_frame, text="确认删除",
command=self._on_confirm,
style="Danger.TButton").pack(side=tk.RIGHT, padx=5)
# 配置危险按钮样式
style = ttk.Style()
style.configure("Danger.TButton", foreground="red")
command=self._on_confirm).pack(side=tk.RIGHT, padx=5)
def _on_confirm(self):
if self.confirm_var.get() != "DELETE":
messagebox.showerror("错误",
"输入错误!请输入 'DELETE'(全大写)以确认删除操作。")
return
self.result = True
self.dialog.destroy()
def _on_cancel(self):
self.result = False
self.dialog.destroy()
def wait_for_result(self):
@@ -291,7 +450,7 @@ class EnhancedRaidDeleteDialog:
class EnhancedPartitionDialog:
"""增强分区创建对话框 - 可视化滑块选择大小"""
"""增强分区创建对话框 - 智能分区建议"""
def __init__(self, parent, disk_path, total_disk_mib, max_available_mib):
self.parent = parent
@@ -302,8 +461,8 @@ class EnhancedPartitionDialog:
self.dialog = tk.Toplevel(parent)
self.dialog.title(f"创建分区 - {disk_path}")
self.dialog.geometry("550x575")
self.dialog.minsize(500, 575)
self.dialog.geometry("550x550")
self.dialog.minsize(500, 500)
self.dialog.transient(parent)
self.dialog.grab_set()
@@ -311,90 +470,99 @@ class EnhancedPartitionDialog:
self._create_widgets()
def _center_window(self):
"""居中窗口"""
self.dialog.update_idletasks()
width = self.dialog.winfo_width()
height = self.dialog.winfo_height()
width = 550
height = 550
x = (self.dialog.winfo_screenwidth() // 2) - (width // 2)
y = (self.dialog.winfo_screenheight() // 2) - (height // 2)
self.dialog.geometry(f"{width}x{height}+{x}+{y}")
self.dialog.geometry(f'{width}x{height}+{x}+{y}')
def _create_widgets(self):
"""创建界面元素"""
# 主框架
main_frame = ttk.Frame(self.dialog, padding="15")
main_frame.pack(fill=tk.BOTH, expand=True)
# 标题
ttk.Label(main_frame, text=f"{self.disk_path} 上创建分区",
font=('Arial', 12, 'bold')).pack(anchor=tk.W, pady=(0, 10))
# 磁盘信息
info_frame = ttk.LabelFrame(main_frame, text="磁盘信息", padding="10")
info_frame.pack(fill=tk.X, pady=(0, 15))
info_frame.pack(fill=tk.X, pady=(0, 10))
total_gb = self.total_disk_mib / 1024
max_gb = self.max_available_mib / 1024
available_gb = self.max_available_mib / 1024
ttk.Label(info_frame, text=f"磁盘: {self.disk_path}").pack(anchor=tk.W, pady=2)
ttk.Label(info_frame, text=f"总容量: {total_gb:.2f} GB").pack(anchor=tk.W, pady=2)
ttk.Label(info_frame, text=f"可用空间: {max_gb:.2f} GB",
foreground="green").pack(anchor=tk.W, pady=2)
ttk.Label(info_frame,
text=f"磁盘总大小: {total_gb:.2f} GB").pack(anchor=tk.W)
ttk.Label(info_frame,
text=f"最大可用空间: {available_gb:.2f} GB",
foreground='green' if available_gb > 1 else 'red').pack(anchor=tk.W)
# 分区表类型
pt_frame = ttk.LabelFrame(main_frame, text="分区表类型", padding="10")
pt_frame.pack(fill=tk.X, pady=(0, 15))
# 分区表类型选择
part_type_frame = ttk.LabelFrame(main_frame, text="分区表类型", padding="10")
part_type_frame.pack(fill=tk.X, pady=(0, 10))
self.part_type_var = tk.StringVar(value="gpt")
ttk.Radiobutton(pt_frame, text="GPT (推荐,支持大容量磁盘)",
variable=self.part_type_var, value="gpt").pack(anchor=tk.W, pady=2)
ttk.Radiobutton(pt_frame, text="MBR/MSDOS (兼容旧系统)",
variable=self.part_type_var, value="msdos").pack(anchor=tk.W, pady=2)
ttk.Radiobutton(part_type_frame, text="GPT (推荐,支持2TB以上磁盘)",
variable=self.part_type_var,
value="gpt").pack(anchor=tk.W, pady=2)
ttk.Radiobutton(part_type_frame, text="MBR (传统格式,兼容性更好)",
variable=self.part_type_var,
value="msdos").pack(anchor=tk.W, pady=2)
# 大小选择
# 大小设置
size_frame = ttk.LabelFrame(main_frame, text="分区大小", padding="10")
size_frame.pack(fill=tk.X, pady=(0, 15))
size_frame.pack(fill=tk.X, pady=(0, 10))
# 使用最大空间复选框
self.use_max_var = tk.BooleanVar(value=True)
ttk.Checkbutton(size_frame, text="使用最大可用空间",
# 使用全部空间选项
self.use_max_var = tk.BooleanVar(value=False)
ttk.Checkbutton(size_frame,
text="使用全部可用空间",
variable=self.use_max_var,
command=self._toggle_size_input).pack(anchor=tk.W, pady=5)
# 滑块和输入框容器
self.size_control_frame = ttk.Frame(size_frame)
self.size_control_frame.pack(fill=tk.X, pady=5)
# 大小输入
size_input_frame = ttk.Frame(size_frame)
size_input_frame.pack(fill=tk.X, pady=5)
# 滑块
self.size_var = tk.DoubleVar(value=max_gb)
self.size_slider = ttk.Scale(
self.size_control_frame,
from_=0.1,
to=max_gb,
orient=tk.HORIZONTAL,
variable=self.size_var,
command=self._on_slider_change
)
self.size_slider.pack(fill=tk.X, pady=5)
ttk.Label(size_input_frame, text="大小 (GB):").pack(side=tk.LEFT)
# 输入框和标签
input_frame = ttk.Frame(self.size_control_frame)
input_frame.pack(fill=tk.X, pady=5)
ttk.Label(input_frame, text="大小:").pack(side=tk.LEFT, padx=5)
self.size_spin = tk.Spinbox(
input_frame,
from_=0.1,
to=max_gb,
increment=0.1,
textvariable=self.size_var,
width=10,
command=self._on_spin_change
)
self.size_var = tk.DoubleVar(value=min(available_gb, 100))
# 使用 tk.Spinbox 兼容旧版 tkinter (Python 3.6)
try:
self.size_spin = tk.Spinbox(size_input_frame,
from_=0.1,
to=available_gb,
textvariable=self.size_var,
width=10,
increment=0.1)
except AttributeError:
# 如果 tk.Spinbox 也不可用,使用 Entry 替代
self.size_spin = tk.Entry(size_input_frame,
textvariable=self.size_var,
width=10)
self.size_spin.pack(side=tk.LEFT, padx=5)
ttk.Label(input_frame, text="GB").pack(side=tk.LEFT, padx=5)
# 滑块
self.size_slider = ttk.Scale(size_frame,
from_=0,
to=available_gb,
orient=tk.HORIZONTAL,
length=400)
self.size_slider.set(min(available_gb, 100))
self.size_slider.pack(fill=tk.X, pady=5)
# 显示百分比
self.percent_label = ttk.Label(input_frame, text="(100%)")
self.percent_label.pack(side=tk.LEFT, padx=10)
# 百分比显示
self.percent_var = tk.StringVar(value="0%")
ttk.Label(size_frame, textvariable=self.percent_var,
foreground='gray').pack(anchor=tk.E)
# 可视化空间显示
self._create_space_visualizer(main_frame, max_gb)
# 绑定更新事件
self.size_var.trace('w', self._on_size_change)
self.size_slider.configure(command=self._on_slider_change)
# 初始状态
self._toggle_size_input()
@@ -405,90 +573,49 @@ class EnhancedPartitionDialog:
ttk.Button(btn_frame, text="取消",
command=self._on_cancel).pack(side=tk.RIGHT, padx=5)
ttk.Button(btn_frame, text="创建分区",
ttk.Button(btn_frame, text="确认创建",
command=self._on_confirm).pack(side=tk.RIGHT, padx=5)
def _create_space_visualizer(self, parent, max_gb):
"""创建空间可视化条"""
viz_frame = ttk.LabelFrame(parent, text="空间使用预览", padding="10")
viz_frame.pack(fill=tk.X, pady=(0, 15))
# Canvas 用于绘制
self.viz_canvas = tk.Canvas(viz_frame, height=40, bg="white")
self.viz_canvas.pack(fill=tk.X, pady=5)
# 等待 Canvas 渲染完成后再更新
self.viz_canvas.update_idletasks()
# 绘制初始状态
self._update_visualizer(max_gb, max_gb)
def _update_visualizer(self, current_gb, max_gb):
"""更新空间可视化"""
self.viz_canvas.delete("all")
width = self.viz_canvas.winfo_width() or 400
height = self.viz_canvas.winfo_height() or 40
# 计算比例
if max_gb > 0:
ratio = current_gb / max_gb
else:
ratio = 0
fill_width = int(width * ratio)
# 绘制背景
self.viz_canvas.create_rectangle(0, 0, width, height, fill="lightgray", outline="")
# 绘制已用空间
if fill_width > 0:
color = "green" if ratio < 0.8 else "orange" if ratio < 0.95 else "red"
self.viz_canvas.create_rectangle(0, 0, fill_width, height, fill=color, outline="")
# 绘制文字
self.viz_canvas.create_text(
width // 2, height // 2,
text=f"{current_gb:.2f} GB / {max_gb:.2f} GB",
font=("Arial", 10, "bold")
)
def _on_size_change(self, *args):
"""大小输入变化时更新滑块"""
try:
value = self.size_var.get()
self.size_slider.set(value)
self._update_percent(value)
except:
pass
def _on_slider_change(self, value):
"""滑块变化回调"""
"""滑块变化时更新输入"""
try:
gb = float(value)
self._update_percent(gb)
self._update_visualizer(gb, self.max_available_mib / 1024)
self.size_var.set(float(value))
self._update_percent(float(value))
except:
pass
def _on_spin_change(self):
"""输入框变化回调"""
try:
gb = self.size_var.get()
self._update_percent(gb)
self._update_visualizer(gb, self.max_available_mib / 1024)
except:
pass
def _update_percent(self, gb):
def _update_percent(self, value):
"""更新百分比显示"""
max_gb = self.max_available_mib / 1024
if max_gb > 0:
percent = (gb / max_gb) * 100
self.percent_label.configure(text=f"({percent:.1f}%)")
if self.max_available_mib > 0:
percent = (value * 1024 / self.max_available_mib) * 100
self.percent_var.set(f"{percent:.1f}%")
def _toggle_size_input(self):
"""切换大小输入状态"""
if self.use_max_var.get():
self.size_slider.configure(state=tk.DISABLED)
# ttk.Scale 在某些旧版本 Tkinter 中不支持 state 选项
try:
self.size_slider.configure(state=tk.DISABLED)
except tk.TclError:
pass # 忽略不支持的选项
self.size_spin.configure(state=tk.DISABLED)
max_gb = self.max_available_mib / 1024
self.size_var.set(max_gb)
self._update_percent(max_gb)
self._update_visualizer(max_gb, max_gb)
else:
self.size_slider.configure(state=tk.NORMAL)
try:
self.size_slider.configure(state=tk.NORMAL)
except tk.TclError:
pass # 忽略不支持的选项
self.size_spin.configure(state=tk.NORMAL)
def _on_confirm(self):
@@ -508,8 +635,8 @@ class EnhancedPartitionDialog:
'disk_path': self.disk_path,
'partition_table_type': self.part_type_var.get(),
'size_gb': size_gb,
'total_disk_mib': self.total_disk_mib,
'use_max_space': use_max_space
'use_max_space': use_max_space,
'total_disk_mib': self.total_disk_mib
}
self.dialog.destroy()