import
tkinter as tk
from
tkinter
import
ttk
from
tkinter
import
filedialog, messagebox, scrolledtext
import
os
import
sys
import
re
import
logging
from
datetime
import
datetime
try
:
import
docx
DOCX_ENABLED
=
True
except
ImportError:
DOCX_ENABLED
=
False
APP_NAME
=
"批量生成文件以及修改文件前缀及后缀工具"
class
TextHandler(logging.Handler):
def
__init__(
self
, text_widget):
logging.Handler.__init__(
self
)
self
.text_widget
=
text_widget
def
emit(
self
, record):
msg
=
self
.
format
(record)
def
append():
self
.text_widget.configure(state
=
'normal'
)
self
.text_widget.insert(tk.END, msg
+
'\n'
)
self
.text_widget.configure(state
=
'disabled'
)
self
.text_widget.yview(tk.END)
self
.text_widget.after(
0
, append)
def
backend_create_files(folder_path, count, start_num, prefix, suffix):
logging.info(f
"--- 开始生成任务 (后端) ---"
)
logging.info(f
"配置: 文件夹='{folder_path}', 数量={count}, 起始={start_num}, 前缀='{prefix}', 后缀='{suffix}'"
)
generated_count
=
0
skipped_count
=
0
if
not
os.path.isdir(folder_path):
logging.error(f
"目标文件夹 '{folder_path}' 不存在或无效。"
)
return
(
0
, count)
if
not
suffix.startswith(
'.'
):
suffix
=
'.'
+
suffix
logging.info(f
"后缀名自动修正为 '{suffix}'"
)
is_docx
=
suffix.lower()
=
=
'.docx'
if
is_docx
and
not
DOCX_ENABLED:
logging.warning(
"无法导入 'python-docx' 库,将尝试创建名为 .docx 的空文件。"
)
elif
is_docx
and
DOCX_ENABLED:
logging.info(
"检测到 .docx 后缀,使用 'python-docx' 创建标准文档。"
)
for
i
in
range
(count):
current_num
=
start_num
+
i
file_name
=
f
"{prefix}{current_num}{suffix}"
if
prefix
else
f
"{current_num}{suffix}"
full_path
=
os.path.join(folder_path, file_name)
if
os.path.exists(full_path):
logging.warning(f
"文件 '{file_name}' 已存在,跳过。"
)
skipped_count
+
=
1
continue
try
:
if
is_docx
and
DOCX_ENABLED:
document
=
docx.Document()
document.save(full_path)
logging.info(f
"成功: 标准 .docx '{file_name}'"
)
else
:
with
open
(full_path,
'a'
) as f:
pass
if
is_docx:
logging.warning(f
"创建: 空文件 '{file_name}' (非标准 Word)。"
)
else
:
logging.info(f
"成功: 空文件 '{file_name}'"
)
generated_count
+
=
1
except
OSError as e:
logging.error(f
"失败: 无法创建 '{file_name}' (OS错误)。原因: {e}"
)
skipped_count
+
=
1
except
Exception as e:
logging.error(f
"失败: 创建 .docx '{file_name}' 出错。原因: {e}"
)
skipped_count
+
=
1
logging.info(f
"--- 生成任务完成 (后端) ---"
)
logging.info(f
"成功生成 {generated_count},跳过 {skipped_count}。"
)
return
(generated_count, skipped_count)
def
backend_build_rename_plan(folder_path, rename_type, params):
logging.info(f
"--- 开始构建重命名计划 (后端) ---"
)
logging.info(f
"配置: 文件夹='{folder_path}', 类型='{rename_type}', 参数={params}"
)
rename_plan
=
[]
if
not
os.path.isdir(folder_path):
logging.error(f
"目标文件夹 '{folder_path}' 不存在或无效。"
)
return
None
try
:
items
=
os.listdir(folder_path)
except
OSError as e:
logging.error(f
"无法访问文件夹 '{folder_path}'。原因: {e}"
)
return
None
for
item_name
in
items:
old_path
=
os.path.join(folder_path, item_name)
if
not
os.path.isfile(old_path):
continue
base_name, suffix
=
os.path.splitext(item_name)
new_name
=
None
try
:
if
rename_type
=
=
'suffix'
:
old_s
=
params[
'old_suffix'
]
new_s
=
params[
'new_suffix'
]
if
item_name.lower().endswith(old_s.lower()):
new_name
=
base_name
+
new_s
elif
rename_type
=
=
'prefix_part'
:
old_p
=
params[
'old_part'
]
new_p
=
params[
'new_part'
]
if
old_p
in
base_name:
new_base_name
=
base_name.replace(old_p, new_p)
if
new_base_name !
=
base_name:
new_name
=
new_base_name
+
suffix
elif
rename_type
=
=
'regex'
:
pattern
=
params[
'pattern'
]
replacement
=
params[
'replacement'
]
if
pattern.search(base_name):
new_base_name
=
pattern.sub(replacement, base_name)
if
new_base_name !
=
base_name:
new_name
=
new_base_name
+
suffix
if
new_name:
new_path
=
os.path.join(folder_path, new_name)
rename_plan.append((old_path, new_path))
except
KeyError as e:
logging.error(f
"构建计划时缺少参数: {e}"
)
return
None
except
Exception as e:
logging.error(f
"处理文件 '{item_name}' 时出错: {e}"
)
continue
logging.info(f
"计划构建完成,共找到 {len(rename_plan)} 个可能重命名的文件。"
)
return
rename_plan
def
backend_execute_rename(rename_plan):
renamed_count
=
0
skipped_count
=
0
error_count
=
0
logging.info(f
"--- 开始执行重命名 (后端), 共 {len(rename_plan)} 项 ---"
)
existing_targets
=
set
()
plan_sources
=
set
(old
for
old, new
in
rename_plan)
for
old_path, new_path
in
rename_plan:
if
os.path.exists(new_path)
and
new_path
not
in
plan_sources:
existing_targets.add(os.path.basename(new_path))
if
existing_targets:
logging.warning(
"执行前再次检查到冲突,以下目标已存在,将被跳过:"
)
for
name
in
sorted
(
list
(existing_targets)):
logging.warning(f
"- {name}"
)
for
old_path, new_path
in
rename_plan:
old_name
=
os.path.basename(old_path)
new_name
=
os.path.basename(new_path)
if
os.path.exists(new_path):
logging.warning(f
"跳过: 目标 '{new_name}' 已存在。"
)
skipped_count
+
=
1
continue
try
:
os.rename(old_path, new_path)
logging.info(f
"成功: '{old_name}' -> '{new_name}'"
)
renamed_count
+
=
1
except
OSError as e:
logging.error(f
"失败: 无法重命名 '{old_name}'。原因: {e}"
)
error_count
+
=
1
logging.info(f
"--- 重命名执行完毕 (后端) ---"
)
logging.info(f
"成功: {renamed_count}, 跳过: {skipped_count}, 错误: {error_count}"
)
return
(renamed_count, skipped_count, error_count)
class
BatchFileApp(tk.Tk):
def
__init__(
self
):
super
().__init__()
self
.title(APP_NAME)
self
.geometry(
"750x650"
)
self
.current_rename_plan
=
[]
self
.setup_logging()
self
.create_widgets()
logging.info(f
"--- {APP_NAME} 已启动 ---"
)
if
not
DOCX_ENABLED:
logging.warning(
"未找到 'python-docx' 库,无法创建标准 .docx 文件。"
)
def
setup_logging(
self
):
log_format
=
'%(asctime)s - %(levelname)s - %(message)s'
log_level
=
logging.INFO
self
.logger
=
logging.getLogger()
self
.logger.setLevel(log_level)
for
handler
in
self
.logger.handlers[:]:
self
.logger.removeHandler(handler)
self
.gui_log_handler
=
None
def
create_widgets(
self
):
main_frame
=
ttk.Frame(
self
, padding
=
"10"
)
main_frame.pack(fill
=
tk.BOTH, expand
=
True
)
self
.notebook
=
ttk.Notebook(main_frame)
self
.notebook.pack(fill
=
tk.BOTH, expand
=
True
, pady
=
(
0
,
10
))
self
.tab_generate
=
ttk.Frame(
self
.notebook, padding
=
"10"
)
self
.notebook.add(
self
.tab_generate, text
=
"批量生成文件"
)
self
.create_generate_tab()
self
.tab_rename
=
ttk.Frame(
self
.notebook, padding
=
"10"
)
self
.notebook.add(
self
.tab_rename, text
=
"批量重命名文件"
)
self
.create_rename_tab()
log_frame
=
ttk.LabelFrame(main_frame, text
=
"日志和状态"
, padding
=
"5"
)
log_frame.pack(fill
=
tk.BOTH, expand
=
True
)
self
.log_area
=
scrolledtext.ScrolledText(log_frame, state
=
'disabled'
, height
=
15
, wrap
=
tk.WORD, font
=
(
"Consolas"
,
9
))
self
.log_area.pack(fill
=
tk.BOTH, expand
=
True
)
if
hasattr
(
self
,
'log_area'
):
self
.gui_log_handler
=
TextHandler(
self
.log_area)
self
.gui_log_handler.setFormatter(logging.Formatter(
'%(asctime)s - %(levelname)s - %(message)s'
))
self
.logger.addHandler(
self
.gui_log_handler)
else
:
self
.logger.error(
"GUI Log Area 未成功创建,无法添加 TextHandler。"
)
def
create_generate_tab(
self
):
frame
=
self
.tab_generate
row_idx
=
0
ttk.Label(frame, text
=
"目标文件夹:"
).grid(row
=
row_idx, column
=
0
, padx
=
5
, pady
=
5
, sticky
=
"w"
)
self
.gen_folder_var
=
tk.StringVar(value
=
os.getcwd())
self
.gen_folder_entry
=
ttk.Entry(frame, textvariable
=
self
.gen_folder_var, width
=
60
)
self
.gen_folder_entry.grid(row
=
row_idx, column
=
1
, padx
=
5
, pady
=
5
, sticky
=
"ew"
)
ttk.Button(frame, text
=
"浏览..."
, command
=
lambda
:
self
.browse_folder(
self
.gen_folder_var)).grid(row
=
row_idx, column
=
2
, padx
=
5
, pady
=
5
)
row_idx
+
=
1
ttk.Label(frame, text
=
"生成数量:"
).grid(row
=
row_idx, column
=
0
, padx
=
5
, pady
=
5
, sticky
=
"w"
)
self
.gen_count_var
=
tk.StringVar(value
=
"10"
)
self
.gen_count_entry
=
ttk.Entry(frame, textvariable
=
self
.gen_count_var, width
=
10
)
self
.gen_count_entry.grid(row
=
row_idx, column
=
1
, padx
=
5
, pady
=
5
, sticky
=
"w"
)
ttk.Label(frame, text
=
"起始编号:"
).grid(row
=
row_idx, column
=
1
, padx
=
(
80
,
5
), pady
=
5
, sticky
=
"w"
)
self
.gen_start_var
=
tk.StringVar(value
=
"1"
)
self
.gen_start_entry
=
ttk.Entry(frame, textvariable
=
self
.gen_start_var, width
=
10
)
self
.gen_start_entry.grid(row
=
row_idx, column
=
1
, padx
=
(
140
,
5
), pady
=
5
, sticky
=
"w"
)
row_idx
+
=
1
ttk.Label(frame, text
=
"文件前缀 (可选):"
).grid(row
=
row_idx, column
=
0
, padx
=
5
, pady
=
5
, sticky
=
"w"
)
self
.gen_prefix_var
=
tk.StringVar()
self
.gen_prefix_entry
=
ttk.Entry(frame, textvariable
=
self
.gen_prefix_var, width
=
30
)
self
.gen_prefix_entry.grid(row
=
row_idx, column
=
1
, padx
=
5
, pady
=
5
, sticky
=
"w"
)
ttk.Label(frame, text
=
"文件后缀 (如 .txt):"
).grid(row
=
row_idx, column
=
1
, padx
=
(
230
,
5
), pady
=
5
, sticky
=
"w"
)
self
.gen_suffix_var
=
tk.StringVar(value
=
".docx"
)
self
.gen_suffix_entry
=
ttk.Entry(frame, textvariable
=
self
.gen_suffix_var, width
=
15
)
self
.gen_suffix_entry.grid(row
=
row_idx, column
=
1
, padx
=
(
330
,
5
), pady
=
5
, sticky
=
"w"
)
row_idx
+
=
1
generate_button
=
ttk.Button(frame, text
=
"开始生成文件"
, command
=
self
.generate_files_gui)
generate_button.grid(row
=
row_idx, column
=
1
, padx
=
5
, pady
=
15
, sticky
=
"e"
)
frame.grid_columnconfigure(
1
, weight
=
1
)
def
create_rename_tab(
self
):
frame
=
self
.tab_rename
row_idx
=
0
ttk.Label(frame, text
=
"目标文件夹:"
).grid(row
=
row_idx, column
=
0
, padx
=
5
, pady
=
5
, sticky
=
"w"
)
self
.rename_folder_var
=
tk.StringVar(value
=
os.getcwd())
self
.rename_folder_entry
=
ttk.Entry(frame, textvariable
=
self
.rename_folder_var, width
=
60
)
self
.rename_folder_entry.grid(row
=
row_idx, column
=
1
, columnspan
=
2
, padx
=
5
, pady
=
5
, sticky
=
"ew"
)
ttk.Button(frame, text
=
"浏览..."
, command
=
lambda
:
self
.browse_folder(
self
.rename_folder_var)).grid(row
=
row_idx, column
=
3
, padx
=
5
, pady
=
5
)
row_idx
+
=
1
ttk.Label(frame, text
=
"重命名类型:"
).grid(row
=
row_idx, column
=
0
, padx
=
5
, pady
=
10
, sticky
=
"w"
)
self
.rename_type_var
=
tk.StringVar(value
=
"suffix"
)
types_frame
=
ttk.Frame(frame)
types_frame.grid(row
=
row_idx, column
=
1
, columnspan
=
3
, padx
=
5
, pady
=
5
, sticky
=
"w"
)
ttk.Radiobutton(types_frame, text
=
"修改后缀名"
, variable
=
self
.rename_type_var, value
=
"suffix"
, command
=
self
.update_rename_options).pack(side
=
tk.LEFT, padx
=
5
)
ttk.Radiobutton(types_frame, text
=
"修改前缀 (简单替换)"
, variable
=
self
.rename_type_var, value
=
"prefix_part"
, command
=
self
.update_rename_options).pack(side
=
tk.LEFT, padx
=
5
)
ttk.Radiobutton(types_frame, text
=
"使用正则表达式"
, variable
=
self
.rename_type_var, value
=
"regex"
, command
=
self
.update_rename_options).pack(side
=
tk.LEFT, padx
=
5
)
row_idx
+
=
1
self
.rename_options_frame
=
ttk.Frame(frame)
self
.rename_options_frame.grid(row
=
row_idx, column
=
0
, columnspan
=
4
, padx
=
5
, pady
=
5
, sticky
=
"ew"
)
self
.rename_options_frame.grid_columnconfigure(
1
, weight
=
1
)
self
.create_rename_suffix_options()
self
.create_rename_prefix_part_options()
self
.create_rename_regex_options()
row_idx
+
=
1
action_frame
=
ttk.Frame(frame)
action_frame.grid(row
=
row_idx, column
=
0
, columnspan
=
4
, padx
=
5
, pady
=
15
, sticky
=
"e"
)
self
.preview_button
=
ttk.Button(action_frame, text
=
"预览重命名"
, command
=
self
.preview_rename_gui)
self
.preview_button.pack(side
=
tk.LEFT, padx
=
5
)
self
.confirm_button
=
ttk.Button(action_frame, text
=
"确认重命名"
, command
=
self
.confirm_rename_gui, state
=
tk.DISABLED)
self
.confirm_button.pack(side
=
tk.LEFT, padx
=
5
)
self
.update_rename_options()
frame.grid_columnconfigure(
1
, weight
=
1
)
def
create_rename_suffix_options(
self
):
self
.suffix_frame
=
ttk.Frame(
self
.rename_options_frame, padding
=
"5"
)
ttk.Label(
self
.suffix_frame, text
=
"旧后缀名 (如 .txt):"
).grid(row
=
0
, column
=
0
, padx
=
5
, pady
=
5
, sticky
=
"w"
)
self
.old_suffix_var
=
tk.StringVar()
ttk.Entry(
self
.suffix_frame, textvariable
=
self
.old_suffix_var, width
=
20
).grid(row
=
0
, column
=
1
, padx
=
5
, pady
=
5
, sticky
=
"w"
)
ttk.Label(
self
.suffix_frame, text
=
"新后缀名 (如 .md):"
).grid(row
=
1
, column
=
0
, padx
=
5
, pady
=
5
, sticky
=
"w"
)
self
.new_suffix_var
=
tk.StringVar()
ttk.Entry(
self
.suffix_frame, textvariable
=
self
.new_suffix_var, width
=
20
).grid(row
=
1
, column
=
1
, padx
=
5
, pady
=
5
, sticky
=
"w"
)
def
create_rename_prefix_part_options(
self
):
self
.prefix_part_frame
=
ttk.Frame(
self
.rename_options_frame, padding
=
"5"
)
ttk.Label(
self
.prefix_part_frame, text
=
"查找内容:"
).grid(row
=
0
, column
=
0
, padx
=
5
, pady
=
5
, sticky
=
"w"
)
self
.old_part_var
=
tk.StringVar()
ttk.Entry(
self
.prefix_part_frame, textvariable
=
self
.old_part_var, width
=
40
).grid(row
=
0
, column
=
1
, padx
=
5
, pady
=
5
, sticky
=
"ew"
)
ttk.Label(
self
.prefix_part_frame, text
=
"替换为:"
).grid(row
=
1
, column
=
0
, padx
=
5
, pady
=
5
, sticky
=
"w"
)
self
.new_part_var
=
tk.StringVar()
ttk.Entry(
self
.prefix_part_frame, textvariable
=
self
.new_part_var, width
=
40
).grid(row
=
1
, column
=
1
, padx
=
5
, pady
=
5
, sticky
=
"ew"
)
self
.prefix_part_frame.grid_columnconfigure(
1
, weight
=
1
)
def
create_rename_regex_options(
self
):
self
.regex_frame
=
ttk.Frame(
self
.rename_options_frame, padding
=
"5"
)
ttk.Label(
self
.regex_frame, text
=
"正则表达式:"
).grid(row
=
0
, column
=
0
, padx
=
5
, pady
=
5
, sticky
=
"w"
)
self
.regex_pattern_var
=
tk.StringVar()
ttk.Entry(
self
.regex_frame, textvariable
=
self
.regex_pattern_var, width
=
50
).grid(row
=
0
, column
=
1
, padx
=
5
, pady
=
5
, sticky
=
"ew"
)
ttk.Label(
self
.regex_frame, text
=
"替换字符串:"
).grid(row
=
1
, column
=
0
, padx
=
5
, pady
=
5
, sticky
=
"w"
)
self
.regex_replace_var
=
tk.StringVar()
ttk.Entry(
self
.regex_frame, textvariable
=
self
.regex_replace_var, width
=
50
).grid(row
=
1
, column
=
1
, padx
=
5
, pady
=
5
, sticky
=
"ew"
)
self
.regex_frame.grid_columnconfigure(
1
, weight
=
1
)
def
update_rename_options(
self
):
self
.suffix_frame.grid_remove()
self
.prefix_part_frame.grid_remove()
self
.regex_frame.grid_remove()
if
hasattr
(
self
,
'confirm_button'
):
self
.confirm_button.config(state
=
tk.DISABLED)
self
.current_rename_plan
=
[]
selected_type
=
self
.rename_type_var.get()
if
selected_type
=
=
"suffix"
:
self
.suffix_frame.grid(row
=
0
, column
=
0
, sticky
=
"ew"
)
elif
selected_type
=
=
"prefix_part"
:
self
.prefix_part_frame.grid(row
=
0
, column
=
0
, sticky
=
"ew"
)
elif
selected_type
=
=
"regex"
:
self
.regex_frame.grid(row
=
0
, column
=
0
, sticky
=
"ew"
)
def
browse_folder(
self
, path_var):
folder_selected
=
filedialog.askdirectory()
if
folder_selected:
path_var.
set
(folder_selected)
logging.info(f
"已选择文件夹: {folder_selected}"
)
def
generate_files_gui(
self
):
folder
=
self
.gen_folder_var.get()
count_str
=
self
.gen_count_var.get()
start_str
=
self
.gen_start_var.get()
prefix
=
self
.gen_prefix_var.get()
suffix
=
self
.gen_suffix_var.get()
if
not
os.path.isdir(folder):
messagebox.showerror(
"错误"
, f
"目标文件夹无效或不存在:\n{folder}"
)
return
try
:
count
=
int
(count_str)
if
count <
=
0
:
raise
ValueError(
"数量必须大于0"
)
except
ValueError as e:
messagebox.showerror(
"错误"
, f
"生成数量无效: {e}"
)
return
try
:
start_num
=
int
(start_str)
except
ValueError:
messagebox.showerror(
"错误"
,
"起始编号必须是一个整数。"
)
return
if
not
suffix:
messagebox.showerror(
"错误"
,
"文件后缀不能为空。"
)
return
confirm_msg
=
(
f
"将在文件夹:\n{folder}\n\n"
f
"生成 {count} 个文件,从编号 {start_num} 开始。\n"
f
"前缀: '{prefix}' (无前缀则为空)\n"
f
"后缀: '{suffix}'\n\n"
f
"确定要开始吗?"
)
if
messagebox.askyesno(
"确认生成操作"
, confirm_msg):
logging.info(
"用户确认生成。"
)
try
:
success_count, skipped_count
=
backend_create_files(folder, count, start_num, prefix, suffix)
messagebox.showinfo(
"生成完成"
, f
"成功生成 {success_count} 个文件。\n跳过 {skipped_count} 个文件 (已存在或错误)。\n详情请查看日志。"
)
except
Exception as e:
logging.error(f
"生成文件时发生未预期错误: {e}"
)
messagebox.showerror(
"意外错误"
, f
"生成过程中发生错误:\n{e}"
)
else
:
logging.info(
"用户取消了生成操作。"
)
def
preview_rename_gui(
self
):
folder
=
self
.rename_folder_var.get()
rename_type
=
self
.rename_type_var.get()
params
=
{}
self
.current_rename_plan
=
[]
self
.confirm_button.config(state
=
tk.DISABLED)
if
not
os.path.isdir(folder):
messagebox.showerror(
"错误"
, f
"目标文件夹无效或不存在:\n{folder}"
)
return
if
rename_type
=
=
'suffix'
:
params[
'old_suffix'
]
=
self
.old_suffix_var.get()
params[
'new_suffix'
]
=
self
.new_suffix_var.get()
if
not
params[
'old_suffix'
]
or
not
params[
'new_suffix'
]:
messagebox.showerror(
"错误"
,
"新旧后缀名都不能为空。"
)
return
if
not
params[
'old_suffix'
].startswith(
'.'
): params[
'old_suffix'
]
=
'.'
+
params[
'old_suffix'
]
if
not
params[
'new_suffix'
].startswith(
'.'
): params[
'new_suffix'
]
=
'.'
+
params[
'new_suffix'
]
if
params[
'old_suffix'
].lower()
=
=
params[
'new_suffix'
].lower():
messagebox.showerror(
"错误"
,
"新旧后缀名不能相同 (忽略大小写)。"
)
return
elif
rename_type
=
=
'prefix_part'
:
params[
'old_part'
]
=
self
.old_part_var.get()
params[
'new_part'
]
=
self
.new_part_var.get()
if
not
params[
'old_part'
]:
messagebox.showerror(
"错误"
,
"要查找的旧内容不能为空。"
)
return
elif
rename_type
=
=
'regex'
:
pattern_str
=
self
.regex_pattern_var.get()
params[
'replacement'
]
=
self
.regex_replace_var.get()
if
not
pattern_str:
messagebox.showerror(
"错误"
,
"正则表达式不能为空。"
)
return
try
:
params[
'pattern'
]
=
re.
compile
(pattern_str)
except
re.error as e:
messagebox.showerror(
"错误"
, f
"无效的正则表达式:\n{e}"
)
return
else
:
messagebox.showerror(
"错误"
,
"未知的重命名类型。"
)
return
logging.info(
"--- 开始预览重命名 ---"
)
try
:
self
.current_rename_plan
=
backend_build_rename_plan(folder, rename_type, params)
except
Exception as e:
logging.error(f
"构建重命名计划时发生意外错误: {e}"
)
messagebox.showerror(
"意外错误"
, f
"构建预览时出错:\n{e}"
)
return
if
self
.current_rename_plan
is
None
:
messagebox.showerror(
"预览失败"
,
"构建重命名计划时遇到错误,请检查日志。"
)
return
if
not
self
.current_rename_plan:
logging.info(
"预览结果:没有找到符合条件可供重命名的文件。"
)
messagebox.showinfo(
"预览结果"
,
"没有找到符合条件的文件进行重命名。"
)
return
logging.info(
"--- 重命名预览 (共 {} 项) ---"
.
format
(
len
(
self
.current_rename_plan)))
potential_conflicts
=
[]
plan_sources
=
set
(old
for
old, new
in
self
.current_rename_plan)
for
old_path, new_path
in
self
.current_rename_plan:
old_name
=
os.path.basename(old_path)
new_name
=
os.path.basename(new_path)
logging.info(f
"'{old_name}' -> '{new_name}'"
)
if
os.path.exists(new_path)
and
new_path
not
in
plan_sources:
potential_conflicts.append(new_name)
if
potential_conflicts:
logging.warning(
"!!! 潜在冲突警告 (目标已存在,将被跳过): "
+
", "
.join(
sorted
(
list
(
set
(potential_conflicts)))))
logging.info(
"--- 预览结束 ---"
)
logging.warning(
"请仔细检查日志区域的预览!"
)
self
.confirm_button.config(state
=
tk.NORMAL)
messagebox.showinfo(
"预览生成"
, f
"预览已生成 ({len(self.current_rename_plan)} 项),请在日志区域查看。\n核对无误后可点击 '确认重命名'。"
)
def
confirm_rename_gui(
self
):
if
not
self
.current_rename_plan:
messagebox.showwarning(
"无法执行"
,
"没有有效的重命名计划可供执行。\n请先点击 '预览重命名' 生成计划。"
)
return
if
messagebox.askyesno(
"确认执行重命名"
, f
"即将根据预览重命名 {len(self.current_rename_plan)} 个文件。\n此操作通常不可逆,确定要继续吗?"
):
logging.info(
"用户确认执行重命名。"
)
try
:
self
.preview_button.config(state
=
tk.DISABLED)
self
.confirm_button.config(state
=
tk.DISABLED)
self
.update_idletasks()
success, skipped, errors
=
backend_execute_rename(
self
.current_rename_plan)
messagebox.showinfo(
"重命名完成"
, f
"重命名操作执行完毕。\n成功: {success}, 跳过: {skipped}, 错误: {errors}\n详情请查看日志。"
)
except
Exception as e:
logging.error(f
"执行重命名时发生意外错误: {e}"
)
messagebox.showerror(
"意外错误"
, f
"执行重命名时出错:\n{e}"
)
finally
:
self
.current_rename_plan
=
[]
self
.preview_button.config(state
=
tk.NORMAL)
else
:
logging.info(
"用户取消了执行重命名操作。"
)
self
.confirm_button.config(state
=
tk.NORMAL)
if
__name__
=
=
"__main__"
:
app
=
BatchFileApp()
app.mainloop()