Skip to content

Commit f3d48aa

Browse files
committed
添加份数分割模式,添加应用图标
1 parent 82c51c5 commit f3d48aa

6 files changed

Lines changed: 131 additions & 37 deletions

File tree

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
2. **按行分割**:按指定的行数分割
1111
3. **严格行分割**:如果当前行加入会导致超过字符数限制,放弃当前行,保持行完整性,同时不会超过字符限制
1212
4. **灵活行分割**:如果当前行加入会导致超过字符数限制,保留当前行,保持行完整性,同时将会超过字符限制
13-
5. **份数分割**:按输入份数自动计算字符数分割(待完成)
13+
5. **份数分割**:按输入份数自动计算字符数分割
1414
- 多线程处理机制,防止大文件分割时界面卡死
1515

1616
### 🔤 编码支持
@@ -70,7 +70,7 @@ file_splitter/
7070
- 点击"输出目录"旁的"浏览..."按钮选择保存分割文件的目录
7171

7272
2. **设置分割参数**
73-
- 在"每个文件的字符数"输入框中指定分割大小如5000字符
73+
- 在"每个文件的字符数"输入框中指定分割大小(如5000字符)
7474
- 在"分割方式"下拉框选择模式:
7575
- `按字符分割`:精确控制每个文件的字符数
7676
- `按行分割`:保持行的完整性

build.bat

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
@echo off
2+
pyinstaller -y ^
3+
--onefile ^
4+
--windowed ^
5+
--name FileSplitter ^
6+
--icon=icon.ico ^
7+
--add-data "icon.ico;." ^
8+
main.py
9+
pause

core/config_manager.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ def _ensure_config_file(self):
4444
'font_weight': 'normal',
4545
'font_slant': 'roman',
4646
'split_mode': '按字符分割',
47+
'arts_per_file': '4'
4748
}
4849
self.save_config()
4950

core/splitter.py

Lines changed: 58 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -185,4 +185,61 @@ def split_file_by_lines(input_path, output_dir, lines_per_file,
185185

186186
if out_f:
187187
out_f.close()
188-
return file_no - 1
188+
return file_no - 1
189+
190+
def split_file_by_parts(input_path, output_dir, total_parts,
191+
input_encoding, output_encoding,
192+
progress_callback=None, log_callback=None):
193+
"""
194+
按指定份数 **严格按字符数** 均分文件(行可能被截断)
195+
"""
196+
if not os.path.isfile(input_path):
197+
raise FileNotFoundError(f"文件不存在: {input_path}")
198+
if total_parts <= 0:
199+
raise ValueError("份数必须大于 0")
200+
201+
os.makedirs(output_dir, exist_ok=True)
202+
filename = os.path.basename(input_path)
203+
base_name, ext = os.path.splitext(filename)
204+
205+
# 编码处理
206+
if input_encoding == "auto":
207+
with open(input_path, "rb") as f:
208+
raw = f.read(4096)
209+
input_encoding = chardet.detect(raw)['encoding'] or 'utf-8'
210+
if log_callback:
211+
log_callback(f"自动检测到输入编码: {input_encoding}")
212+
213+
if output_encoding == "同输入编码":
214+
output_encoding = input_encoding
215+
elif output_encoding == "ansi":
216+
output_encoding = locale.getpreferredencoding(do_setlocale=False)
217+
218+
# 总字符数 & 每份字符数
219+
total_chars = calculate_total_chars(input_path, input_encoding)
220+
chars_per_part = total_chars // total_parts
221+
remainder = total_chars % total_parts # 余数,前 remainder 份多 1 字符
222+
if log_callback:
223+
log_callback(f"文件总字符数: {total_chars}")
224+
log_callback(f"将按 {total_parts} 份分割,"
225+
f"前 {remainder} 份每份 {chars_per_part + 1} 字符,"
226+
f"最后一份 {chars_per_part} 字符")
227+
228+
# 开始切块
229+
with open(input_path, "r", encoding=input_encoding, errors="replace") as f:
230+
for part_no in range(1, total_parts + 1):
231+
# 计算当前份大小
232+
current_chunk_size = chars_per_part + (1 if part_no <= remainder else 0)
233+
chunk = f.read(current_chunk_size)
234+
if not chunk:
235+
break
236+
out_path = os.path.join(output_dir, f"{base_name}_part{part_no}{ext}")
237+
with open(out_path, "w", encoding=output_encoding, errors="replace") as out_f:
238+
out_f.write(chunk)
239+
if log_callback:
240+
log_callback(f"已创建: {os.path.basename(out_path)} ({len(chunk)} 字符)")
241+
if progress_callback:
242+
progress = (part_no / total_parts) * 100
243+
progress_callback(progress)
244+
245+
return total_parts

gui/main_window.py

Lines changed: 61 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import os
1+
import os,sys
22
import tkinter as tk
33
from tkinter import filedialog, messagebox, ttk
44
import platform
@@ -19,6 +19,15 @@ def __init__(self, root):
1919
self.root.geometry("750x600")
2020
self.root.resizable(True, True)
2121

22+
#替换Tkinter默认窗口图标
23+
if getattr(sys, 'frozen', False):
24+
# 打包后 exe 运行时
25+
icon_path = os.path.join(sys._MEIPASS, "icon.ico")
26+
else:
27+
# 源码运行时
28+
icon_path = os.path.join(os.path.dirname(__file__), "..", "icon.ico")
29+
self.root.iconbitmap(icon_path)
30+
2231
self.config = ConfigManager()
2332
self.style_manager = StyleManager(root)
2433

@@ -31,13 +40,20 @@ def __init__(self, root):
3140
self.log_widget.log("欢迎使用文件分割工具")
3241
self.log_widget.log("请选择输入文件和输出目录")
3342

43+
self.root.protocol("WM_DELETE_WINDOW", self.on_closing)#临死前的挣扎(保存配置)
44+
3445
# ---------- 事件绑定 ----------
3546
def bind_setting_changes(self):
3647
self.chars_entry.entry.bind("<FocusOut>", self.save_settings)
3748
self.encoding_combo.combo.bind("<<ComboboxSelected>>", self.save_settings)
3849
self.output_encoding_combo.combo.bind("<<ComboboxSelected>>", self.save_settings)
3950
self.line_mode_combo.combo.bind("<<ComboboxSelected>>", self.save_settings)
4051

52+
def on_closing(self):
53+
"""窗口关闭时保存设置再退出"""
54+
self.save_settings() # 把当前输入写进配置文件
55+
self.root.destroy() # 真正关闭窗口
56+
4157
# ---------- 应用保存的设置 ----------
4258
def apply_saved_font_settings(self):
4359
font_family = self.config.get_setting('Settings', 'font_family', '微软雅黑')
@@ -56,24 +72,32 @@ def apply_saved_settings(self):
5672
if mode == "按行分割":
5773
val = self.config.get_setting('Settings', 'lines_per_file', '1000')
5874
self.chars_entry.set_value(val)
59-
self.chars_entry.set_label_text("每个文件的行数:")
60-
else:
75+
self.chars_entry.set_label_text("每份文件的字符数/行数/份数:")
76+
77+
elif mode == "份数分割":
78+
val = self.config.get_setting('Settings', 'parts_per_file', '4')
79+
self.chars_entry.set_value(val)
80+
self.chars_entry.set_label_text("每份文件的字符数/行数/份数:")
81+
82+
else: # 按字符 / 严格行 / 灵活行 都走这里
6183
val = self.config.get_setting('Settings', 'chars_per_file', '1000')
6284
self.chars_entry.set_value(val)
63-
self.chars_entry.set_label_text("每个文件的字符数:")
64-
# 恢复编码下拉框
85+
self.chars_entry.set_label_text("每份文件的字符数/行数/份数:")
86+
87+
# 3. 恢复编码下拉框
6588
in_enc = self.config.get_setting('Settings', 'input_encoding', 'auto')
6689
out_enc = self.config.get_setting('Settings', 'output_encoding', '同输入编码')
6790
self.encoding_combo.set_value(in_enc)
6891
self.output_encoding_combo.set_value(out_enc)
69-
7092
# ---------- 保存设置 ----------
7193
def save_settings(self, event=None):
7294
mode = self.line_mode_combo.get_value()
7395
self.config.set_setting('Settings', 'split_mode', mode)
7496

7597
if mode == "按行分割":
7698
self.config.set_setting('Settings', 'lines_per_file', self.chars_entry.get_value())
99+
elif mode == "份数分割":
100+
self.config.set_setting('Settings', 'parts_per_file', self.chars_entry.get_value())
77101
else:
78102
self.config.set_setting('Settings', 'chars_per_file', self.chars_entry.get_value())
79103

@@ -134,18 +158,17 @@ def create_widgets(self):
134158
self.output_entry.grid(row=2, column=0, columnspan=3, sticky="ew", pady=5)
135159

136160
self.chars_entry = LabelledEntry(
137-
self.main_frame, "每个文件的字符数:",
161+
self.main_frame, "每个文件的字符数/行数/份数:",
138162
default_value="1000", entry_width=15)
139163
self.chars_entry.grid(row=3, column=0, columnspan=3, sticky="w", pady=5)
140164

141165
split_frame = ttk.Frame(self.main_frame)
142166
split_frame.grid(row=4, column=0, columnspan=3, sticky="ew", pady=5)
143167
ttk.Label(split_frame, text="分割方式:", style="Label.TLabel").pack(side=tk.LEFT, padx=(0, 10))
144168
self.line_mode_combo = LabelledCombobox(
145-
split_frame, "", values=["按字符分割", "按行分割", "严格行分割", "灵活行分割"],
169+
split_frame, "", values=["按字符分割", "按行分割", "严格行分割", "灵活行分割", "份数分割"],
146170
default_value="按字符分割", width=15)
147171
self.line_mode_combo.pack(side=tk.LEFT)
148-
self.line_mode_combo.combo.bind("<<ComboboxSelected>>", self.on_split_mode_changed)
149172

150173
enc_frame = ttk.Frame(self.main_frame)
151174
enc_frame.grid(row=5, column=0, columnspan=3, sticky="ew", pady=5)
@@ -185,32 +208,8 @@ def create_widgets(self):
185208
self.log_widget.grid(row=9, column=0, columnspan=3, sticky="nsew")
186209
self.main_frame.columnconfigure(0, weight=1)
187210
self.main_frame.rowconfigure(9, weight=1)
188-
self.line_mode_combo.combo.bind("<<ComboboxSelected>>", self.on_split_mode_changed)
189211
self.bind_setting_changes()
190212

191-
def on_split_mode_changed(self, event=None):
192-
"""下拉框切换时即时更新界面"""
193-
mode = self.line_mode_combo.get_value()
194-
if mode == "按行分割":
195-
self.chars_entry.set_label_text("每个文件的行数:")
196-
val = self.config.get_setting('Settings', 'lines_per_file', '1000')
197-
else:
198-
self.chars_entry.set_label_text("每个文件的字符数:")
199-
val = self.config.get_setting('Settings', 'chars_per_file', '1000')
200-
self.chars_entry.set_value(val)
201-
self.root.update_idletasks() # 立即刷新
202-
203-
# ---------- 下拉框切换 ----------
204-
def on_split_mode_changed(self, event=None):
205-
mode = self.line_mode_combo.get_value()
206-
if mode == "按行分割":
207-
self.chars_entry.set_label_text("每个文件的行数:")
208-
val = self.config.get_setting('Settings', 'lines_per_file', '1000')
209-
else:
210-
self.chars_entry.set_label_text("每个文件的字符数:")
211-
val = self.config.get_setting('Settings', 'chars_per_file', '1000')
212-
self.chars_entry.set_value(val)
213-
214213
# ---------- 字体设置 ----------
215214
def open_font_settings(self):
216215
FontSettingsDialog(
@@ -255,7 +254,24 @@ def start_split(self):
255254
target=self.run_split_by_lines,
256255
args=(input_path, output_dir, lines, in_enc, out_enc),
257256
daemon=True).start()
257+
elif mode == "份数分割":
258+
try:
259+
parts = int(self.chars_entry.get_value())
260+
if parts <= 0:
261+
raise ValueError
262+
except ValueError:
263+
messagebox.showerror("错误", "请输入有效的份数")
264+
return
265+
266+
self.open_output_btn.config(state=tk.DISABLED)
267+
self.start_btn.config(state=tk.DISABLED)
268+
self.progress_var.set(0)
269+
self.status_var.set("开始按份数分割...")
258270

271+
threading.Thread(
272+
target=self.run_split_by_parts,
273+
args=(input_path, output_dir, parts, in_enc, out_enc),
274+
daemon=True).start()
259275
else:
260276
try:
261277
chars = int(self.chars_entry.get_value())
@@ -316,4 +332,15 @@ def on_split_error(self, e):
316332
self.progress_var.set(0)
317333
self.status_var.set("处理出错")
318334
messagebox.showerror("错误", str(e))
319-
self.start_btn.config(state=tk.NORMAL)
335+
self.start_btn.config(state=tk.NORMAL)
336+
337+
def run_split_by_parts(self, *args):
338+
try:
339+
from core.splitter import split_file_by_parts
340+
num = split_file_by_parts(
341+
*args,
342+
progress_callback=self.update_progress,
343+
log_callback=self.log_in_ui_thread)
344+
self.root.after(0, self.on_split_completed, num)
345+
except Exception as e:
346+
self.root.after(0, self.on_split_error, e)

icon.ico

98.6 KB
Binary file not shown.

0 commit comments

Comments
 (0)