1. 前言
很早之前,我实现过一个【Python 实战】---- 接口自动化:60行代码,如何通过Python requests实现图片上传,这个的确简化了前端开发对图片操作的时间,但是随着项目开发的越多,遇到一个新的问题,就是图片需要上传到每个项目的自己服务器中,而且每个项目的参数也不一样,有的需要加密,有的不需要,这就会出现我们需要根据不同的项目,对代码进行上传的微调,然后再打包成工具使用,最后就会发现有很多工具,完全达不到一个程序员的优雅,因此就想到使用 GUI 配置管理,将所有的工具进行配置管理。上一篇将读取配置文件和基础界面结构实现,这一篇就是对界面上按钮输入框的事件处理,主要事件:选择上传配置、上传文件、导出文件、复制文件、预览文件。
2. 选择事件【选择使用那个配置项进行上传】
2.1 实现分析
- 从配置下拉框(
config_combobox)中获取用户选择的配置显示名称。 - 检查是否选择了配置,如果没有选择则将当前配置设置为
None。 - 如果选择了配置,则通过
config_name_map映射获取实际的配置名称(处理显示名称与内部名称不一致的情况)。 - 使用配置管理器(
config_manager)根据实际配置名称获取配置数据。 - 如果成功获取到配置数据:
- 将配置数据封装成
UploadConfig对象并赋值给current_config属性 - 调用日志回调函数显示成功加载配置的消息
- 将配置数据封装成
- 如果未能获取到配置数据:
- 将当前配置设置为
None - 调用日志回调函数显示加载配置失败的消息
- 将当前配置设置为
- 当没有选择任何配置时,同样将当前配置设置为
None。
2.2 实现代码
def load_selected_config(self):
"""加载选中的配置"""
selected_display_name = self.config_combobox.get()
if selected_display_name:
# 通过映射获取实际的配置名称
selected_name = self.config_name_map.get(selected_display_name, selected_display_name)
config_data = self.config_manager.get_config(selected_name)
if config_data:
self.current_config = UploadConfig(config_data)
self.log_callback(f"已加载配置: {selected_display_name}")
else:
self.current_config = None
self.log_callback(f"无法加载配置: {selected_display_name}")
else:
self.current_config = None
2.3 实现效果
3. 开始上传
3.1 实现分析
- 验证是否已选择上传配置,未选择则弹出错误提示并返回。
- 获取用户选择的目录路径并验证其有效性,无效则弹出错误提示并返回。
- 解析用户输入的文件类型后缀列表并验证,为空则弹出错误提示并返回。
- 禁用上传按钮防止重复点击,并在日志中记录开始上传的信息。
- 创建
FileUploader实例并调用其upload_images方法执行实际的文件上传操作。 - 根据上传结果:
- 如果有上传结果,统计成功和失败的文件数量并在日志中显示上传完成信息,同时保存上传结果供后续导出使用
- 如果没有上传结果,在日志中显示未找到匹配文件的信息
- 重新启用上传按钮。
3.2 实现代码
def start_upload(self):
"""开始上传"""
if not self.current_config:
messagebox.showerror("错误", "请先选择一个配置")
return
dir_path = self.dir_entry.get()
if not dir_path or not os.path.exists(dir_path):
messagebox.showerror("错误", "请选择有效的目录")
return
suffixes = [s.strip() for s in self.type_entry.get().split(',') if s.strip()]
if not suffixes:
messagebox.showerror("错误", "请输入有效的文件类型")
return
self.upload_btn.config(state=tk.DISABLED)
self.log_callback("开始上传...")
# 创建上传器实例
uploader = FileUploader(self.current_config)
# 执行上传
results = uploader.upload_images(dir_path, suffixes, self.log_callback)
if results:
success_count = len([r for r in results if r['result']])
self.log_callback(f"上传完成。成功: {success_count}, 失败: {len(results) - success_count}")
# 保存上传结果供导出使用
self.upload_results = results
else:
self.log_callback("没有找到匹配的文件进行上传")
self.upload_btn.config(state=tk.NORMAL)
4. 导出文件
4.1 实现分析
- 检查是否存在上传结果(
upload_results),不存在则弹出警告提示并返回。 - 验证是否已选择上传配置(
current_config),未选择则弹出错误提示并返回。 - 获取用户选择的目录路径并验证其有效性,无效则弹出错误提示并返回。
- 获取用户输入的前缀:
- 如果有输入前缀,则设置到当前配置中
- 如果没有输入前缀且当前配置中有前缀属性,则删除该属性
- 创建
FileUploader实例并调用其write_results_to_icon_js方法将上传结果写入 icon.js 文件。 - 根据导出结果弹出相应的提示信息:
- 导出成功则显示成功消息
- 导出失败则显示错误消息
4.2 实现代码
def export_icon_js(self):
"""导出icon.js文件"""
if not hasattr(self, 'upload_results'):
messagebox.showwarning("警告", "请先上传文件")
return
if not self.current_config:
messagebox.showerror("错误", "请先选择一个配置")
return
# 获取用户选择的目录
dir_path = self.dir_entry.get()
if not dir_path or not os.path.exists(dir_path):
messagebox.showerror("错误", "请选择有效的目录")
return
# 设置前缀
prefix = self.prefix_entry.get().strip()
if prefix:
self.current_config.prefix = prefix
else:
# 如果没有输入前缀,确保不使用配置中的前缀
if hasattr(self.current_config, 'prefix'):
delattr(self.current_config, 'prefix')
uploader = FileUploader(self.current_config)
if uploader.write_results_to_icon_js(self.upload_results, dir_path):
messagebox.showinfo("成功", "icon.js文件导出成功")
else:
messagebox.showerror("错误", "icon.js文件导出失败")
5. 复制文件和预览文件
5.1 实现分析
- 检查是否存在上传结果(
upload_results),不存在则弹出警告提示并返回。 - 验证是否已选择上传配置(
current_config),未选择则弹出错误提示并返回。 - 获取用户选择的目录路径并验证其有效性,无效则弹出错误提示并返回。
- 获取用户输入的前缀:
- 如果有输入前缀,则设置到当前配置中
- 如果没有输入前缀且当前配置中有前缀属性,则删除该属性
- 创建
FileUploader实例用于处理数据格式转换。 - 遍历上传结果,为每个成功的上传项:
- 使用文件名生成 key 值
- 根据配置的前缀生成完整的 key 名称(采用驼峰命名规则)
- 获取上传结果中的内容字段值
- 结合图片路径前缀生成最终值
- 将 key-value 对添加到
icon_data字典中
- 根据
icon_data字典生成符合 JavaScript 模块格式的文件内容字符串。 - 清空剪贴板并添加生成的内容,然后更新剪贴板。
- 弹出成功提示信息。
5.2 实现代码
def copy_to_clipboard(self):
"""复制icon.js内容到剪贴板"""
if not hasattr(self, 'upload_results'):
messagebox.showwarning("警告", "请先上传文件")
return
if not self.current_config:
messagebox.showerror("错误", "请先选择一个配置")
return
# 获取用户选择的目录
dir_path = self.dir_entry.get()
if not dir_path or not os.path.exists(dir_path):
messagebox.showerror("错误", "请选择有效的目录")
return
# 设置前缀
prefix = self.prefix_entry.get().strip()
if prefix:
self.current_config.prefix = prefix
else:
# 如果没有输入前缀,确保不使用配置中的前缀
if hasattr(self.current_config, 'prefix'):
delattr(self.current_config, 'prefix')
# 生成icon.js内容但不写入文件
uploader = FileUploader(self.current_config)
icon_data = {}
# 构建 icon_data 对象
for item in self.upload_results:
if item["result"].get("code") == self.current_config.success_code:
# 使用原文件名生成key
file_name = os.path.basename(item["fileName"])
file_name_without_ext = os.path.splitext(file_name)[0]
# 生成key
key = f"{file_name_without_ext}Icon"
# 添加前缀(如果配置了前缀)并转换为驼峰命名
prefix = getattr(self.current_config, 'prefix', '')
if prefix:
# 将前缀和key转换为驼峰命名
prefix = uploader.to_camel_case(prefix)
key = uploader.to_camel_case(key)
key = f"{prefix}{key}"
# 获取原始值
original_value = item["result"].get(self.current_config.content_field)
# 获取图片路径前缀并拼接
img_prefix = self.img_prefix_entry.get() if hasattr(self, 'img_prefix_entry') else ''
if img_prefix and original_value:
icon_data[key] = img_prefix + original_value
else:
icon_data[key] = original_value
# 生成文件内容
content = "export default {\n"
for key, value in icon_data.items():
content += f' "{key}": "{value}",\n'
content += "}\n"
# 复制到剪贴板
self.root.clipboard_clear()
self.root.clipboard_append(content)
self.root.update()
messagebox.showinfo("成功", "icon.js内容已复制到剪贴板")
5.3 预览实现[创建预览窗口]
- 首先创建一个名为"icon.js 预览"的顶级窗口(Toplevel),并设置窗口标题和尺寸为600x400。
- 在窗口中创建一个框架(Frame),用于容纳文本框和滚动条组件。
- 创建一个文本框(Text widget)用于显示内容,并将传入的
content参数插入到文本框中。 - 添加垂直和水平滚动条,并与文本框进行关联,使用户可以滚动查看内容。
- 使用网格布局(grid)管理文本框和滚动条的位置,设置适当的权重使组件能够随窗口大小变化而自适应调整。
- 在窗口底部添加一个按钮框架,并在其中放置一个"关闭"按钮,点击后可销毁预览窗口。
# 创建预览窗口
preview_window = tk.Toplevel(self.root)
preview_window.title("icon.js 预览")
preview_window.geometry("600x400")
# 创建文本框和滚动条
text_frame = ttk.Frame(preview_window)
text_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=10)
text_widget = tk.Text(text_frame, wrap=tk.NONE)
text_widget.insert(tk.END, content)
# 添加滚动条
v_scrollbar = ttk.Scrollbar(text_frame, orient=tk.VERTICAL, command=text_widget.yview)
h_scrollbar = ttk.Scrollbar(text_frame, orient=tk.HORIZONTAL, command=text_widget.xview)
text_widget.configure(yscrollcommand=v_scrollbar.set, xscrollcommand=h_scrollbar.set)
# 布局
text_widget.grid(row=0, column=0, sticky="nsew")
v_scrollbar.grid(row=0, column=1, sticky="ns")
h_scrollbar.grid(row=1, column=0, sticky="ew")
text_frame.grid_rowconfigure(0, weight=1)
text_frame.grid_columnconfigure(0, weight=1)
# 添加关闭按钮
button_frame = ttk.Frame(preview_window)
button_frame.pack(fill=tk.X, padx=10, pady=5)
close_btn = ttk.Button(button_frame, text="关闭", command=preview_window.destroy)
close_btn.pack(side=tk.RIGHT)
5.4 预览效果
6. 日志的显示和清空
6.1 代码实现
-
log_callback(self, message)函数:这是一个日志回调函数,用于在GUI界面中显示日志信息。- 将传入的消息(
message)添加到日志文本框(self.log_text)的末尾,并在消息后添加换行符 - 自动滚动日志文本框,确保最新添加的日志信息可见(
see(tk.END)) - 更新界面的空闲任务,确保日志立即显示到界面上(
update_idletasks())
- 将传入的消息(
-
clear_log(self)函数:这是一个清空日志的函数。- 清除日志文本框(
self.log_text)中的所有内容,从第一行第一个字符开始删除到文本框末尾
- 清除日志文本框(
def log_callback(self, message):
"""日志回调函数"""
self.log_text.insert(tk.END, message + "\n")
self.log_text.see(tk.END)
self.root.update_idletasks()
def clear_log(self):
"""清空日志"""
self.log_text.delete(1.0, tk.END)
6.2 实现效果
7. 添加文件名前缀
7.1 源文件名
7.2 添加文件名前缀
7.3 说明
可以看到统一在图片的名称前边加了一个前缀,这样就可以根据不同模块的图片,将图片分类,即便不同模块图片命名相同,也不会发生冲突。
8. 图片路径前缀
8.1 添加前缀
8.2 添加后效果
8.3 说明
这里是方便有些接口只返回图片在服务器中的id,需要将返回参数作为一个参数读取图片的处理,就可以统一给图片这样添加域名。
9. 总结
- 上传的基本功能就实现了,没有太多的难点,就是按照步骤依次实现就好。
- 其实还有一些功能可以完善,比如导出的文件名可以进行修改,比如导出的文件格式可以进行选择等等。
- 就像第一个版本,可以看到那会没有预览和复制,也没有文件前缀和图片前缀,这些都是在使用过程中,发现不是很方便,为了更加方便快捷,完善了对应的功能,后期如果发现还需要更多功能,就能在此基础上不断的完善。就像《黑客与画家》所讲,我们先不管产品有什么问题,需要先做出来使用,在使用的过程中不断的发现问题,修改问题,完善工具,这样最后就能成为符合我们要求的产品。