图书管理系统(已在本人GitHub仓库开源)

项目地址:suyihang15/BookStack: 基于python的图书管理系统

文件下载:https://github.com/suyihang15/BookStack/releases/download/1.0.0/default.exe

1、环境准备

python

第三方库 pip install openpyxl

2、源码介绍

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
import json
import os
import tkinter as tk
from tkinter import ttk, messagebox, filedialog
from datetime import datetime, timedelta
import openpyxl

# 数据文件
图书文件 = "图书数据.json"
读者文件 = "读者数据.json"
借阅文件 = "借阅记录.json"

# 读取保存工具
def 读取数据(文件):
if not os.path.exists(文件):
return []
with open(文件, "r", encoding="utf-8") as f:
try:
return json.load(f)
except:
return []

def 保存数据(文件, 数据):
with open(文件, "w", encoding="utf-8") as f:
json.dump(数据, f, ensure_ascii=False, indent=2)

# 核心管理类
class 图书馆系统:
def __init__(self):
self.图书列表 = 读取数据(图书文件)
self.读者列表 = 读取数据(读者文件)
self.借阅记录 = 读取数据(借阅文件)

def 保存全部(self):
保存数据(图书文件, self.图书列表)
保存数据(读者文件, self.读者列表)
保存数据(借阅文件, self.借阅记录)

# 图书
def 添加图书(self, 编号, 书名, 作者, 出版社, ISBN):
for 书 in self.图书列表:
if 书["编号"] == 编号:
return False
self.图书列表.append({
"编号": 编号,
"书名": 书名,
"作者": 作者,
"出版社": 出版社,
"ISBN": ISBN,
"总数量": 1,
"可借数量": 1,
"状态": "可借"
})
self.保存全部()
return True

def 删除图书(self, 编号):
for 书 in self.图书列表:
if 书["编号"] == 编号:
self.图书列表.remove(书)
self.保存全部()
return True
return False

def 搜索图书(self, 关键词):
结果 = []
for 书 in self.图书列表:
if 关键词 in 书["编号"] or 关键词 in 书["书名"] or 关键词 in 书["作者"] or 关键词 in 书["ISBN"]:
结果.append(书)
return 结果

# 读者
def 添加读者(self, 学号, 姓名, 电话):
for 人 in self.读者列表:
if 人["学号"] == 学号:
return False
self.读者列表.append({
"学号": 学号,
"姓名": 姓名,
"电话": 电话
})
self.保存全部()
return True

def 删除读者(self, 学号):
for 人 in self.读者列表:
if 人["学号"] == 学号:
self.读者列表.remove(人)
self.保存全部()
return True
return False

def 搜索读者(self, 关键词):
结果 = []
for 人 in self.读者列表:
if 关键词 in 人["学号"] or 关键词 in 人["姓名"] or 关键词 in 人["电话"]:
结果.append(人)
return 结果

# 借阅
def 借阅图书(self, 学号, 图书编号):
图书 = None
for 书 in self.图书列表:
if 书["编号"] == 图书编号:
图书 = 书
break
if not 图书:
return "图书不存在"
if 图书["可借数量"] < 1:
return "暂无库存"

读者 = None
for 人 in self.读者列表:
if 人["学号"] == 学号:
读者 = 人
break
if not 读者:
return "读者不存在"

当前时间 = datetime.now()
self.借阅记录.append({
"学号": 学号,
"图书编号": 图书编号,
"借阅时间": 当前时间.strftime("%Y-%m-%d %H:%M"),
"应还时间": (当前时间 + timedelta(days=30)).strftime("%Y-%m-%d %H:%M"),
"归还时间": "",
"状态": "借阅中"
})
图书["可借数量"] -= 1
图书["状态"] = "可借" if 图书["可借数量"] > 0 else "不可借"
self.保存全部()
return "借阅成功"

def 归还图书(self, 学号, 图书编号):
for 记录 in self.借阅记录:
if 记录["学号"] == 学号 and 记录["图书编号"] == 图书编号 and 记录["归还时间"] == "":
记录["归还时间"] = datetime.now().strftime("%Y-%m-%d %H:%M")
记录["状态"] = "已归还"
for 书 in self.图书列表:
if 书["编号"] == 图书编号:
书["可借数量"] += 1
书["状态"] = "可借"
self.保存全部()
return "归还成功"
return "未找到该借阅记录"

def 删除借阅记录(self, 学号, 图书编号):
for 记录 in self.借阅记录:
if 记录["学号"] == 学号 and 记录["图书编号"] == 图书编号:
self.借阅记录.remove(记录)
self.保存全部()
return True
return False

def 搜索借阅记录(self, 学号关键词, 图书编号关键词):
结果 = []
for 记录 in self.借阅记录:
匹配学号 = 学号关键词 == "" or 学号关键词 in 记录["学号"]
匹配图书 = 图书编号关键词 == "" or 图书编号关键词 in 记录["图书编号"]
if 匹配学号 and 匹配图书:
结果.append(记录)
return 结果

# 界面
class 图书管理界面:
def __init__(self, 窗口):
self.窗口 = 窗口
self.窗口.title("图书管理系统")
self.窗口.geometry("950x680")
self.系统 = 图书馆系统()

# 分页
分页 = ttk.Notebook(窗口)
分页.pack(fill=tk.BOTH, expand=True)

self.图书页 = ttk.Frame(分页)
self.读者页 = ttk.Frame(分页)
self.借阅页 = ttk.Frame(分页)
self.数据页 = ttk.Frame(分页)

分页.add(self.图书页, text="图书管理")
分页.add(self.读者页, text="读者管理")
分页.add(self.借阅页, text="借阅归还")
分页.add(self.数据页, text="数据导入导出")

self.初始化图书页()
self.初始化读者页()
self.初始化借阅页()
self.初始化数据页()

# 图书页面
def 初始化图书页(self):
框架 = ttk.LabelFrame(self.图书页, text="新增图书")
框架.pack(fill=tk.X, padx=10, pady=6)

ttk.Label(框架, text="图书编号").grid(row=0, column=0, padx=5, pady=4)
self.图书编号 = ttk.Entry(框架)
self.图书编号.grid(row=0, column=1, padx=5, pady=4)

ttk.Label(框架, text="书名").grid(row=0, column=2, padx=5, pady=4)
self.图书书名 = ttk.Entry(框架)
self.图书书名.grid(row=0, column=3, padx=5, pady=4)

ttk.Label(框架, text="作者").grid(row=1, column=0, padx=5, pady=4)
self.图书作者 = ttk.Entry(框架)
self.图书作者.grid(row=1, column=1, padx=5, pady=4)

ttk.Label(框架, text="出版社").grid(row=1, column=2, padx=5, pady=4)
self.图书出版社 = ttk.Entry(框架)
self.图书出版社.grid(row=1, column=3, padx=5, pady=4)

ttk.Label(框架, text="ISBN").grid(row=2, column=0, padx=5, pady=4)
self.图书ISBN = ttk.Entry(框架)
self.图书ISBN.grid(row=2, column=1, padx=5, pady=4)

ttk.Button(框架, text="添加图书", command=self.执行添加图书).grid(row=2, column=3, padx=5, pady=5)

# 删除区域
删除框架 = ttk.LabelFrame(self.图书页, text="删除图书")
删除框架.pack(fill=tk.X, padx=10, pady=6)
ttk.Label(删除框架, text="图书编号").pack(side=tk.LEFT, padx=5)
self.删除图书编号 = ttk.Entry(删除框架)
self.删除图书编号.pack(side=tk.LEFT, padx=5)
ttk.Button(删除框架, text="删除", command=self.执行删除图书).pack(side=tk.LEFT, padx=5)

# 搜索区域
搜索框架 = ttk.LabelFrame(self.图书页, text="搜索图书")
搜索框架.pack(fill=tk.X, padx=10, pady=6)
ttk.Label(搜索框架, text="输入关键词").pack(side=tk.LEFT, padx=5)
self.搜索图书关键词 = ttk.Entry(搜索框架)
self.搜索图书关键词.pack(side=tk.LEFT, padx=5)
ttk.Button(搜索框架, text="搜索", command=self.执行搜索图书).pack(side=tk.LEFT, padx=5)
ttk.Button(搜索框架, text="显示全部", command=self.刷新图书表格).pack(side=tk.LEFT, padx=5)

# 表格
表格框架 = ttk.Frame(self.图书页)
表格框架.pack(fill=tk.BOTH, expand=True, padx=10, pady=5)
栏位 = ("编号", "书名", "作者", "出版社", "ISBN", "可借数量", "状态")
self.图书表格 = ttk.Treeview(表格框架, columns=栏位, show="headings")
for 栏 in 栏位:
self.图书表格.heading(栏, text=栏)
self.图书表格.column(栏, width=110)
self.图书表格.pack(fill=tk.BOTH, expand=True)
self.刷新图书表格()

def 执行添加图书(self):
编号 = self.图书编号.get().strip()
书名 = self.图书书名.get().strip()
作者 = self.图书作者.get().strip()
出版社 = self.图书出版社.get().strip()
ISBN = self.图书ISBN.get().strip()
if not all([编号, 书名, 作者, 出版社, ISBN]):
messagebox.showwarning("提示", "请把信息填写完整")
return
if self.系统.添加图书(编号, 书名, 作者, 出版社, ISBN):
messagebox.showinfo("成功", "图书添加完成")
self.刷新图书表格()
else:
messagebox.showerror("失败", "该图书编号已存在")

def 执行删除图书(self):
编号 = self.删除图书编号.get().strip()
if not 编号:
messagebox.showwarning("提示", "请输入要删除的图书编号")
return
if messagebox.askyesno("确认", "确定删除这本图书?"):
if self.系统.删除图书(编号):
messagebox.showinfo("成功", "图书已删除")
self.刷新图书表格()
else:
messagebox.showerror("失败", "未找到该图书")

def 执行搜索图书(self):
关键词 = self.搜索图书关键词.get().strip()
结果 = self.系统.搜索图书(关键词)
for 行 in self.图书表格.get_children():
self.图书表格.delete(行)
for 书 in 结果:
self.图书表格.insert("", tk.END, values=(
书["编号"], 书["书名"], 书["作者"], 书["出版社"],
书["ISBN"], 书["可借数量"], 书["状态"]
))

def 刷新图书表格(self):
for 行 in self.图书表格.get_children():
self.图书表格.delete(行)
for 书 in self.系统.图书列表:
self.图书表格.insert("", tk.END, values=(
书["编号"], 书["书名"], 书["作者"], 书["出版社"],
书["ISBN"], 书["可借数量"], 书["状态"]
))

# 读者页面
def 初始化读者页(self):
框架 = ttk.LabelFrame(self.读者页, text="新增读者")
框架.pack(fill=tk.X, padx=10, pady=6)

ttk.Label(框架, text="学号").grid(row=0, column=0, padx=5, pady=4)
self.读者学号 = ttk.Entry(框架)
self.读者学号.grid(row=0, column=1, padx=5, pady=4)

ttk.Label(框架, text="姓名").grid(row=0, column=2, padx=5, pady=4)
self.读者姓名 = ttk.Entry(框架)
self.读者姓名.grid(row=0, column=3, padx=5, pady=4)

ttk.Label(框架, text="电话").grid(row=1, column=0, padx=5, pady=4)
self.读者电话 = ttk.Entry(框架)
self.读者电话.grid(row=1, column=1, padx=5, pady=4)

ttk.Button(框架, text="添加读者", command=self.执行添加读者).grid(row=1, column=3, padx=5, pady=5)

# 删除区域
删除框架 = ttk.LabelFrame(self.读者页, text="删除读者")
删除框架.pack(fill=tk.X, padx=10, pady=6)
ttk.Label(删除框架, text="读者学号").pack(side=tk.LEFT, padx=5)
self.删除读者学号 = ttk.Entry(删除框架)
self.删除读者学号.pack(side=tk.LEFT, padx=5)
ttk.Button(删除框架, text="删除", command=self.执行删除读者).pack(side=tk.LEFT, padx=5)

# 搜索区域
搜索框架 = ttk.LabelFrame(self.读者页, text="搜索读者")
搜索框架.pack(fill=tk.X, padx=10, pady=6)
ttk.Label(搜索框架, text="关键词").pack(side=tk.LEFT, padx=5)
self.搜索读者关键词 = ttk.Entry(搜索框架)
self.搜索读者关键词.pack(side=tk.LEFT, padx=5)
ttk.Button(搜索框架, text="搜索", command=self.执行搜索读者).pack(side=tk.LEFT, padx=5)
ttk.Button(搜索框架, text="显示全部", command=self.刷新读者表格).pack(side=tk.LEFT, padx=5)

表格框架 = ttk.Frame(self.读者页)
表格框架.pack(fill=tk.BOTH, expand=True, padx=10, pady=5)
栏位 = ("学号", "姓名", "电话")
self.读者表格 = ttk.Treeview(表格框架, columns=栏位, show="headings")
for 栏 in 栏位:
self.读者表格.heading(栏, text=栏)
self.读者表格.column(栏, width=180)
self.读者表格.pack(fill=tk.BOTH, expand=True)
self.刷新读者表格()

def 执行添加读者(self):
学号 = self.读者学号.get().strip()
姓名 = self.读者姓名.get().strip()
电话 = self.读者电话.get().strip()
if not all([学号, 姓名, 电话]):
messagebox.showwarning("提示", "信息不能为空")
return
if self.系统.添加读者(学号, 姓名, 电话):
messagebox.showinfo("成功", "读者添加完成")
self.刷新读者表格()
else:
messagebox.showerror("失败", "该学号已存在")

def 执行删除读者(self):
学号 = self.删除读者学号.get().strip()
if not 学号:
messagebox.showwarning("提示", "请输入读者学号")
return
if messagebox.askyesno("确认", "确定删除该读者?"):
if self.系统.删除读者(学号):
messagebox.showinfo("成功", "读者已删除")
self.刷新读者表格()
else:
messagebox.showerror("失败", "未找到该读者")

def 执行搜索读者(self):
关键词 = self.搜索读者关键词.get().strip()
结果 = self.系统.搜索读者(关键词)
for 行 in self.读者表格.get_children():
self.读者表格.delete(行)
for 人 in 结果:
self.读者表格.insert("", tk.END, values=(人["学号"], 人["姓名"], 人["电话"]))

def 刷新读者表格(self):
for 行 in self.读者表格.get_children():
self.读者表格.delete(行)
for 人 in self.系统.读者列表:
self.读者表格.insert("", tk.END, values=(人["学号"], 人["姓名"], 人["电话"]))

# 借阅页面
def 初始化借阅页(self):
框架 = ttk.LabelFrame(self.借阅页, text="借阅与归还")
框架.pack(fill=tk.X, padx=10, pady=6)

ttk.Label(框架, text="读者学号").grid(row=0, column=0, padx=5, pady=4)
self.借阅学号 = ttk.Entry(框架)
self.借阅学号.grid(row=0, column=1, padx=5, pady=4)

ttk.Label(框架, text="图书编号").grid(row=0, column=2, padx=5, pady=4)
self.借阅图书编号 = ttk.Entry(框架)
self.借阅图书编号.grid(row=0, column=3, padx=5, pady=4)

ttk.Button(框架, text="确认借阅", command=self.执行借阅).grid(row=1, column=1, padx=5, pady=5)
ttk.Button(框架, text="确认归还", command=self.执行归还).grid(row=1, column=3, padx=5, pady=5)

# 删除借阅记录
删除框架 = ttk.LabelFrame(self.借阅页, text="删除借阅记录")
删除框架.pack(fill=tk.X, padx=10, pady=6)
ttk.Button(删除框架, text="删除当前记录", command=self.执行删除借阅记录).pack(padx=5)

# 搜索区域
搜索框架 = ttk.LabelFrame(self.借阅页, text="搜索借阅记录")
搜索框架.pack(fill=tk.X, padx=10, pady=6)
ttk.Label(搜索框架, text="学号关键词").pack(side=tk.LEFT, padx=5)
self.搜索借阅学号 = ttk.Entry(搜索框架, width=10)
self.搜索借阅学号.pack(side=tk.LEFT, padx=5)

ttk.Label(搜索框架, text="图书编号关键词").pack(side=tk.LEFT, padx=5)
self.搜索借阅图书编号 = ttk.Entry(搜索框架, width=10)
self.搜索借阅图书编号.pack(side=tk.LEFT, padx=5)

ttk.Button(搜索框架, text="搜索", command=self.执行搜索借阅记录).pack(side=tk.LEFT, padx=5)
ttk.Button(搜索框架, text="显示全部", command=self.刷新借阅表格).pack(side=tk.LEFT, padx=5)

表格框架 = ttk.Frame(self.借阅页)
表格框架.pack(fill=tk.BOTH, expand=True, padx=10, pady=5)
栏位 = ("学号", "图书编号", "借阅时间", "应还时间", "归还时间", "状态")
self.借阅表格 = ttk.Treeview(表格框架, columns=栏位, show="headings")
for 栏 in 栏位:
self.借阅表格.heading(栏, text=栏)
self.借阅表格.column(栏, width=110)
self.借阅表格.pack(fill=tk.BOTH, expand=True)
self.刷新借阅表格()

def 执行借阅(self):
学号 = self.借阅学号.get().strip()
图书编号 = self.借阅图书编号.get().strip()
结果 = self.系统.借阅图书(学号, 图书编号)
messagebox.showinfo("操作结果", 结果)
self.刷新借阅表格()
self.刷新图书表格()

def 执行归还(self):
学号 = self.借阅学号.get().strip()
图书编号 = self.借阅图书编号.get().strip()
结果 = self.系统.归还图书(学号, 图书编号)
messagebox.showinfo("操作结果", 结果)
self.刷新借阅表格()
self.刷新图书表格()

def 执行删除借阅记录(self):
学号 = self.借阅学号.get().strip()
图书编号 = self.借阅图书编号.get().strip()
if not (学号 and 图书编号):
messagebox.showwarning("提示", "请先填写学号和图书编号")
return
if messagebox.askyesno("确认", "确定删除这条借阅记录?"):
if self.系统.删除借阅记录(学号, 图书编号):
messagebox.showinfo("成功", "记录已删除")
self.刷新借阅表格()
else:
messagebox.showerror("失败", "未找到对应记录")

def 执行搜索借阅记录(self):
学号关键词 = self.搜索借阅学号.get().strip()
图书编号关键词 = self.搜索借阅图书编号.get().strip()
结果 = self.系统.搜索借阅记录(学号关键词, 图书编号关键词)
for 行 in self.借阅表格.get_children():
self.借阅表格.delete(行)
for 记录 in 结果:
self.借阅表格.insert("", tk.END, values=(
记录["学号"], 记录["图书编号"], 记录["借阅时间"],
记录["应还时间"], 记录["归还时间"], 记录["状态"]
))

def 刷新借阅表格(self):
for 行 in self.借阅表格.get_children():
self.借阅表格.delete(行)
for 记录 in self.系统.借阅记录:
self.借阅表格.insert("", tk.END, values=(
记录["学号"], 记录["图书编号"], 记录["借阅时间"],
记录["应还时间"], 记录["归还时间"], 记录["状态"]
))

# 导入导出
def 初始化数据页(self):
框架 = ttk.Frame(self.数据页)
框架.pack(pady=30)

ttk.Button(框架, text="导出图书数据到Excel", width=30, command=self.导出图书).pack(pady=8)
ttk.Button(框架, text="导出读者数据到Excel", width=30, command=self.导出读者).pack(pady=8)
ttk.Button(框架, text="导出借阅记录到Excel", width=30, command=self.导出借阅).pack(pady=8)
ttk.Button(框架, text="从Excel导入图书", width=30, command=self.导入图书).pack(pady=8)

ttk.Label(self.数据页, text="导入格式:编号、书名、作者、出版社、ISBN", font=("", 10)).pack()

def 导出图书(self):
路径 = filedialog.asksaveasfilename(defaultextension=".xlsx", filetypes=[("Excel文件", "*.xlsx")])
if not 路径:
return
工作簿 = openpyxl.Workbook()
表 = 工作簿.active
表.title = "图书"
表.append(["编号", "书名", "作者", "出版社", "ISBN", "可借数量", "状态"])
for 书 in self.系统.图书列表:
表.append([书["编号"], 书["书名"], 书["作者"], 书["出版社"], 书["ISBN"], 书["可借数量"], 书["状态"]])
工作簿.save(路径)
messagebox.showinfo("完成", "图书数据已导出")

def 导出读者(self):
路径 = filedialog.asksaveasfilename(defaultextension=".xlsx", filetypes=[("Excel文件", "*.xlsx")])
if not 路径:
return
工作簿 = openpyxl.Workbook()
表 = 工作簿.active
表.append(["学号", "姓名", "电话"])
for 人 in self.系统.读者列表:
表.append([人["学号"], 人["姓名"], 人["电话"]])
工作簿.save(路径)
messagebox.showinfo("完成", "读者数据已导出")

def 导出借阅(self):
路径 = filedialog.asksaveasfilename(defaultextension=".xlsx", filetypes=[("Excel文件", "*.xlsx")])
if not 路径:
return
工作簿 = openpyxl.Workbook()
表 = 工作簿.active
表.append(["学号", "图书编号", "借阅时间", "应还时间", "归还时间", "状态"])
for 记录 in self.系统.借阅记录:
表.append([记录["学号"], 记录["图书编号"], 记录["借阅时间"], 记录["应还时间"], 记录["归还时间"], 记录["状态"]])
工作簿.save(路径)
messagebox.showinfo("完成", "借阅记录已导出")

def 导入图书(self):
路径 = filedialog.askopenfilename(filetypes=[("Excel文件", "*.xlsx")])
if not 路径:
return
工作簿 = openpyxl.load_workbook(路径)
表 = 工作簿.active
成功数 = 0
for 行 in list(表.iter_rows(min_row=2, values_only=True)):
编号, 书名, 作者, 出版社, ISBN = 行[0], 行[1], 行[2], 行[3], 行[4]
if 编号 and 书名 and self.系统.添加图书(str(编号), 书名, 作者, 出版社, ISBN):
成功数 += 1
self.刷新图书表格()
messagebox.showinfo("导入完成", f"成功导入 {成功数} 本图书")

if __name__ == "__main__":
主窗口 = tk.Tk()
图书管理界面(主窗口)
主窗口.mainloop()

#大致的模块都写清楚了,可以根据自己的需要来更改。

3、基于python的图书管理系统
具有图形化界面。如下是界面的图形和功能

读者界面

借阅归还

导入数据

当如果想要导出可以生成excel文件,方便查看

其本身是json文件用于储存

大致数据就在这里,根据需求来搞就行

希望可以帮到你。

关于学习和教育

注:以下观点仅代表个人的看法和想法,不代表任何其他的东西。

我个人理解的学习不是考试什么的,而是真正可以用到的东西,有的人可能觉得学习什么高等数学什么的基本上用不到,但是我个人觉得,我们学习的不仅仅只是这里面的东西,为了考试。我觉得我们学习的更多的是里面的思维方式和逻辑,去当我们学习其他的东西,所以为什么有的人说小学看不出成绩,我觉得小学学的数学更应该叫算数,我们可以通过大量的练习提升成绩,但是,人的思维方式却很难通过这种方式提升,我觉得提升的方式更多是要提高人的思维方式,关于怎么提升,我也不知道,这个是教育家的事,我在这里只提供自己的想法,但是,我们在提升思维方式时,还是应该要大量的练习,因为一般来说只有你对一个东西很熟悉了,才能发现其他的东西。还有就是我现在还记得我爹说的就是,无论你在什么地方,学到东西才是实的,其他的任何东西都是虚的。

还有我觉得教育人的话,第一件事情是教会人有一个正确的三观,这是一切的前提,要让其对这个世界有了一个初步的认识,然后教会人有一个良好的品德,这是做人最基本的两个东西,还有就是自己都做不到的东西就不要要求人家去做,我们每个人都有不好或做不到的地方,在我眼里没有完美的人,只有不断追求完美的人,还有任何娱乐设备,不应该做过多的限制,可以有一定的限制,但是一定要让他有一定的娱乐,毕竟人的精力是有限的,没有人可以一直工作和学习,当人过度沉迷于一个东西时刻,我觉得他只是其他的方面不太舒服,想要靠沉迷于一个东西来短暂躲避这些不快的事情,这就是我对于教育的观点。

无论在任何地方,我们不应该忘记学习或者叫提升自己更贴切,这才是我们学习和成功的关键。

这些都是我突发奇想想写的东西,用于记录自己的一些想法。

编程语言介绍

注:按照我个人的理解就是设定好一系列的命令,让人或者物去执行。

那么什么是编程语言,就是给电脑下指令的语言,对就这么简单。

我们知道电脑就只认识0和1

因为电脑内部全是电路,所以只有两种状态:

  • 通电 → 记为 1

  • 断电 → 记为 0

所有计算、图片、视频、软件,本质上都是一长串 0 和 1。
这种直接用 0 和 1 写的程序,叫机器语言。(这玩意应该算最老的语言了吧,有大佬可以试试看用这个写有多费事),而且如果要用这个写,人根本记不住、看不懂、写不动一长串 0和1
写一个简单加法都要几十位二进制,很容易写错,还没法改。

所以有人发明了“更像人话”的语言

为了不直接写 0 和 1,人们一步步创造了编程语言:

1. 汇编语言
用简单英文单词代替 0 和 1,比如  ADD  代表加法。
还是很难用,只能写很简单的程序。(但是这比用0和1好用多了,却也还不够简洁)
于是便有了更高级的语言。

2. 高级编程语言
比如 C、Python、Java、JavaScrip
语法更接近英文和数学,人能看懂、好写、好改。
写完后,再由软件自动翻译成 0 和 1 给电脑执行。

所以本质他还是要转换成0和1,但是这就好用多了。

  1. 那么为什么要有这么多种语言?

就像现实中有中文、英文、法语、日语一样,不同语言适合不同事:

  • 做系统、硬件 → 用 C、C++(快、接近底层)

  • 做网站、小程序 → 用 JavaScript(网页专用)

  • 做 AI、数据分析 → 用 Python(简单、库多)

  • 做手机 APP → 用 Java/Kotlin、Swift

  • 做游戏 → 用 C#、C++、Lua

用不同的语言做不同的事情可以事半功倍,就和造房子一样不可能只用一种材料。

4、总结

就我个人来说我是比较喜欢python的因为这是我最开始学的,我也觉得很最适合新手上手的语言,反正编程语言的目的其实就是为了方便你操作的工具,只要可以达到目的哪怕是写出屎山都行。

一套完整的产品的组成

注:这是本人理解的一套完整产品的大致组成

  1. 前端(商场的前台)

就是你打开软件第一眼看到的所有东西:页面长啥样、按钮在哪、点了会跳去哪。
它只负责好看、好点、好操作,做前端的通常叫切片仔,当然只是调侃。

基本上前端用这些写

网页结构用 HTML

样式美化用 CSS

点击交互用 JavaScript。

  1. API 接口(商场里的通道)

你在柜台前想买东西,不能直接冲进仓库,得有人帮你传话、拿货。
API 就是干这个的:前端和后端之间的传话员。
你点一下“登录”“提交”,前端就通过 API 去找后端办事。

理解为接口就行,大火的ai基本上经常用到这些接口,用于方便使用,不用单独自己去写,会调用别人的接口就可以做很多事情

接口不是单独写的,直接写在后端代码里。

  1. 后端(统一商场的管理

用户看不见,但所有真正的事情都在这处理:
判断账号对不对、算钱、存订单、查记录、发验证码……
相当于整个软件的大脑。

Java、Python、Go、Node.js 这些都行,选一种就可以,我个人觉得python挺好的用于写接口这些,而且上手也还好。

  1. 数据库(商场的仓库)

专门存所有信息:用户账号、内容、记录、设置。
软件关掉再打开,数据还在,就是靠它。

个人比较喜欢的是my SQL,毕竟我的个人博客就是用的my SQL

  1. 缓存(商场门口的临时货架)

有些东西大家天天拿,总去仓库翻太慢。
缓存就把常用的放近点,打开更快、不卡顿。

  1. 文件存储(商场的图片广告)

专门放图片、头像、视频、文件这些,不和数据混在一起,我个人是无所谓了,反正数据也不多,一般随便乱存,但是一般建议归档好,不然越到后面越难受。

  1. 服务器 & 运维(商场的物业和管理)

软件得有地方跑,服务器就是它的“房子”。
运维负责把代码放上去、连上网、保证 24 小时不关门、坏了能修。

  1. 安全(商场的保安)

防别人乱搞、偷数据、恶意攻击,保护用户信息和系统稳定,一般就是防火墙啊这些

  1. 设计(商场的布局规划)

决定软件有哪些功能、页面怎么排、怎么用更顺手。
没有设计,就是一堆代码,不是好用的产品。

  1. 测试(商场开业前的试运营)

找人从头到尾用一遍,差不多就是测试bug

一个完整的流程就是:
用户看到的前端 + 传话的API + 处理事情的后端 + 存数据的数据库 + 提速的缓存 + 放图片的存储 + 提供场地的服务器 + 保安一样的安全 + 做布局的设计 + 找问题的测试。

但是这些一般来说就是对与大公司,我们个人开发是怎么舒服怎么来。

我个人就准备前后端,一个数据库和一个服务器,还有写后端好接口,其他的就不管了。

搞懂这几个基本上就等于知道了一个完整的开发流程。

对于大公司来说当然可能更复杂,我这里简化了很多东西,但是我个人觉得大致的流程就是这样,如果有大佬是专门做这方面的,欢迎补充

一些感想

这里就当写个日记把,没啥内容好写的,就只是养成个写博客的习惯。想写点自己想说的,这个板块仅代表个人观点。

就发表一下自己的感想吧,感觉就是你做一些自己喜欢的事情就感觉不会累,这是一个很享受的过程,我不知道我对这些爱好会坚持多久,可能后面上班了就不会有这么多时间搞这些乱七八糟的东西了,我个人做这个网站纯粹算圆了自己的一个梦吧,可能有的人不太理解。觉得我算那种游手好闲的,但是我觉得,大学算是我后面可能为数不多不会受其他人影响的想做自己想做的事情的,比较我开始确实没有想好做什么,上大学其实算有点迷茫的吧,看见大一就有不少人卷,而且他们真的很努力,我觉得我做不到,我就觉得普普通通的做点自己喜欢的就行了,不太去想太多东西,算是比较蠢的,这是我自己的看法,人就那么多年,做好自己喜欢的就行。

我个人觉得我算是一个比较幸运的人吧,虽然家庭不算大富大贵,但是起码一生的安安稳稳的,我希望就这样一直下去,上大学这段时间我感受就是大学是一个很开放的地方,就是你在学完基本课程以后,想干什么都行,不会有人要求你做什么,开始很爽,基本就是打游戏,但是多了也无聊,就会想一些乱七八糟的东西,我个人就是闲的蛋疼学了一堆乱七八糟的,也不知道后面用不用的上,又不想这些东西荒废,就搞成了博客,我相信后面的自己看到了也觉得挺好的吧,起码可以吹牛,大学是真的搞了写东西的,虽然就最多是吹个牛。

还有就是我不知道我的父母和我兄弟是怎么看待这些东西的,但我觉得我父母应该也是算比较开明的吧,我兄弟的想法我不知道,但感觉应该也还好,在父母眼中可能有点算不务正业吧,就天天抱着个电脑看,但是只要不影响正常的学习什么的不会特别反对就是了。

说了这么多,我个人的想法就是有什么想做的,什么想法就去做,我开始写代码的时候全是ai的,现在慢慢的好起来了,什么事情都怕第一步,现在不去做,后面想做就太难了。做一件事最好的时间就是你想到去做的时候,不要怕做了,就起码不后悔。

也不知道怎么写结尾,就用语文我读书是经常做结尾的来说吧,希望你能活出一个你想活出的人生吧,不要太在乎别人的看法了。

手机投屏到电脑(adb加python)

注:这是本人用adb连接手机用python写的图像界面,使用前请先用adb连接好要控制的手机如果不知道怎么连接的可以看我这篇文章电脑adb连接模拟器,本项目以上传到本人Github仓库,但因为是python的原因可能会有一点延迟。

Github仓库地址:suyihang15/ADB-link-tool: 用于有电脑上用ADB连接手机的需求的人,做了简单的图形化界面

1、环境准备

python

2、源码介绍

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
import subprocess
import cv2
import numpy as np
import tkinter as tk
from PIL import Image, ImageTk
import threading
import time

# 改成你自己的 adb 路径
ADB_PATH = r"\adb.exe" #填你下载的adb.exe文件的地址,不然后面的都不行的,记住
WIN_W = 480
WIN_H = 950

class PhoneControl:
def __init__(self, root):
self.root = root
self.root.title("手机联机软件(作者Mr.苏)")
self.root.geometry(f"{WIN_W}x{WIN_H}")

# 1. 投屏显示区域:用于显示手机画面
self.screen = tk.Label(root)
self.screen.pack(fill=tk.BOTH, expand=True, padx=10, pady=5)
self.screen.bind("<Button-1>", self.click) # 绑定鼠标点击事件

# 2. 功能按键区:返回、主页、唤醒等
btn_frame = tk.Frame(root)
btn_frame.pack(pady=5)
tk.Button(btn_frame, text="返回", command=self.back).grid(row=0, column=0, padx=3)
tk.Button(btn_frame, text="主页", command=self.home).grid(row=0, column=1, padx=3)
tk.Button(btn_frame, text="唤醒", command=self.wake).grid(row=0, column=2, padx=3)
tk.Button(btn_frame, text="电源", command=self.power).grid(row=0, column=3, padx=3)
tk.Button(btn_frame, text="截图", command=self.screenshot).grid(row=0, column=4, padx=3)

# 3. ADB 命令终端区:输入自定义命令
term_frame = tk.Frame(root)
term_frame.pack(fill=tk.X, padx=10, pady=5)
tk.Label(term_frame, text="ADB命令:").pack(side=tk.LEFT)
self.cmd_input = tk.Entry(term_frame)
self.cmd_input.pack(side=tk.LEFT, fill=tk.X, expand=True, padx=5)
tk.Button(term_frame, text="执行", command=self.run_cmd).pack(side=tk.LEFT)

# 命令输出框:显示命令执行结果
self.output_box = tk.Text(root, height=6)
self.output_box.pack(fill=tk.BOTH, padx=10, pady=5)

self.running = True
self.current_frame = None

# 启动投屏线程
threading.Thread(target=self.stream, daemon=True).start()
# 启动声音转发线程
threading.Thread(target=self.audio, daemon=True).start()
# 启动界面刷新循环
self.update_ui()

# ---------------------- 核心功能模块 ----------------------
# 投屏流获取:不断从手机抓取屏幕数据(核心循环)
def stream(self):
while self.running:
try:
# 使用 ADB exec-out 命令抓取手机屏幕二进制流
data = subprocess.run([ADB_PATH, "exec-out", "screencap", "-p"], stdout=subprocess.PIPE).stdout
# 数据过短表示抓取失败,跳过
if len(data) < 1000:
continue
# 将二进制流转为 numpy 数组
nparr = np.frombuffer(data, np.uint8)
# 解码为图片
img = cv2.imdecode(nparr, cv2.IMREAD_COLOR)
# 转换颜色格式 (BGR -> RGB) 并赋值给当前帧变量
if img is not None:
self.current_frame = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
except:
pass
# 控制抓取频率,0.008秒 = 125帧左右
time.sleep(0.008)

# UI 画面刷新:将 current_frame 显示在 Label 上
def update_ui(self):
if self.current_frame is not None:
try:
h, w = self.current_frame.shape[:2]
# 计算缩放比例,保证画面适应窗口且不变形
scale = min(WIN_W/w, 850/h)
img = cv2.resize(self.current_frame, (int(w*scale), int(h*scale)))
# 转为 Tkinter 可用的图片格式
imgtk = ImageTk.PhotoImage(Image.fromarray(img))
# 更新界面显示
self.screen.config(image=imgtk)
self.screen.imgtk = imgtk # 强制保存引用,防止被垃圾回收
except:
pass
# 递归调用,实现每 8 毫秒刷新一次界面
if self.running:
self.root.after(8, self.update_ui)

# 鼠标点击控制:将电脑鼠标坐标映射为手机坐标并发送点击指令
def click(self, e):
if self.current_frame is None:
return
h, w = self.current_frame.shape[:2]
# 计算真实的手机点击位置
x = int(e.x * w / WIN_W)
y = int(e.y * h / 850)
# 执行 ADB 点击命令
subprocess.Popen([ADB_PATH, "shell", "input", "tap", str(x), str(y)])

# 音频转发:通过 screenrecord 转发手机声音流
def audio(self):
while self.running:
try:
# 启动手机录屏并输出到标准输出(用于转发声音)
subprocess.Popen([ADB_PATH, "shell", "screenrecord", "--output-format", "h264", "-"],
stdout=subprocess.PIPE, stderr=subprocess.DEVNULL)
except:
pass
time.sleep(1)

# ---------------------- 基础按键功能模块 ----------------------
# 模拟手机返回键
def back(self):
subprocess.Popen([ADB_PATH, "shell", "input", "keyevent", "4"])
# 模拟手机主页键
def home(self):
subprocess.Popen([ADB_PATH, "shell", "input", "keyevent", "3"])
# 模拟多任务键
def recent(self):
subprocess.Popen([ADB_PATH, "shell", "input", "keyevent", "187"])
# 模拟唤醒屏幕/点亮屏幕
def wake(self):
subprocess.Popen([ADB_PATH, "shell", "input", "keyevent", "224"])
# 模拟电源键(锁屏/点亮)
def power(self):
subprocess.Popen([ADB_PATH, "shell", "input", "keyevent", "26"])

# 截图功能:截取手机屏幕并保存到电脑
def screenshot(self):
try:
# 先在手机内存截图
subprocess.run([ADB_PATH, "shell", "screencap", "/sdcard/screenshot.png"])
# 拉取到电脑当前目录
subprocess.run([ADB_PATH, "pull", "/sdcard/screenshot.png", "screenshot.png"])
self.output_box.insert(tk.END, "\n截图已保存到当前目录:screenshot.png")
except:
self.output_box.insert(tk.END, "\n截图失败")

# ADB 命令执行:读取输入框的命令并执行
def run_cmd(self):
cmd = self.cmd_input.get().strip()
if not cmd:
return
try:
# 拆分命令并执行
res = subprocess.run([ADB_PATH] + cmd.split(), stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
# 输出结果
self.output_box.insert(tk.END, f"\n> {cmd}\n{res.stdout}{res.stderr}")
self.output_box.see(tk.END) # 滚动到最底部
except Exception as e:
self.output_box.insert(tk.END, f"\n错误:{str(e)}")

if __name__ == "__main__":
root = tk.Tk()
app = PhoneControl(root)
root.mainloop()

3、运行

(1)安装所需第三方库 pip install opencv-python numpy pillow

(2)运行python文件

4、效果展示

这些就是全部功能,如果想直接使用就直接用我打包好的exe文件就行。

5、打包

因为本身功能不算多,打包用pyinstaller这个,后面会再优化以下,可以自行打包试试记得要用到adb.exe哦,要把他一起打包才有用哦,因为本身是基于它的。

如何定制一个自己的Windows镜像

注意:如果你已经厌倦了Windows自带的一些你可能这一辈子都不会用的内置软件或一些功能,可以试试自己定制一款自己的Windows镜像,在原版的基础上进行魔改,这是本人根据前面自己操作和别人分享的资料以后写下了这些流程,那么你可以看看

1、前期准备工具与文件

(1)windows镜像(主要是提取出install.win文件与后面放入)

(2)NTLite(用于更改install.win文件的内容,就系统层面的,打补丁什么的都可以)这个他一些功能是要收费的,但是我们用免费版的足够了。

(3)vmware(用于一些基本的设置和内置捆绑的软件,就内置软件和设置什么的)

(4)PE.iso(用于在虚拟机上安装系统因为这是的文件是install.win不是我们知道的iso文件了,是要更改这里的内容)

(5)Easy sysprep(用于更改后的install.win文件封装)

(6)AnyBurn(用于把文件最后封装为iso文件)

2、更改install.win

在电脑上挂载windows11光盘

在光盘的驱动盘:\sources有install.win文件

复制粘贴出来,这就是我们要更改的文件

用NTLite这一个软件打开install.win文件

这里你可以根据自己的需要来删除你需要的windows版本,只保留你需要的

前面挂载完成后就可以对一些不要的组件和功能进行改动了,但是如果不清楚是干什么的就不要乱改,不然在这个层面修改可能会导致改出来的系统连不上网容易崩溃等一系列问题,如果要打补丁也可以在这里修改,但是你自己要确定你的补丁没有问题,这里你就要自己发挥了

以下是其作用和介绍

1、映像

  • 功能:加载、选择、保存系统镜像,选择系统版本。

  • 注意:用原版镜像,操作前备份,建议在虚拟机操作。

2、整合

更新整合

  • 功能:把系统补丁注入镜像,装完自带更新。

  • 注意:只加正式补丁,分批添加,版本匹配系统。

驱动程序

  • 功能:注入硬件驱动,装机自动识别设备。

  • 注意:用通用驱动,必加USB、网卡、硬盘驱动,不重复添加。

注册表

  • 功能:预设系统优化,装完自动生效。

  • 注意:不用不明注册表文件,不乱改核心项。

后期任务

  • 功能:首次进桌面自动装软件、运行脚本。

  • 注意:路径准确,不装大型软件,按驱动→软件→优化顺序执行。

3、移除(如果是第一次的话建议可以先试试改这里的内容)

组件移除

  • 功能:删除无用组件,精简系统。

  • 注意:不删.NET、驱动框架等核心组件,精简适度。

计划任务

  • 功能:禁用后台自动任务,减少占用。

  • 注意:保留安全相关任务,不全禁用。

4、配置

功能设置

  • 功能:开关系统自带功能(如.NET、虚拟机)。

  • 注意:必开.NET,不用的功能关闭。

设置

  • 功能:优化账户、网络、服务、电源等基础设置。

  • 注意:不关闭关键服务,隐私设置适度调整。

无人值守

  • 功能:自动跳过装机设置,实现全自动安装。

  • 注意:提前测试,信息填写准确。

结束→应用

  • 功能:保存所有修改,生成新镜像。

  • 注意:先清理垃圾,优先保存为WIM格式,生成后测试安装。

搞好以后点击应用,左边这么选择,右边是你更改的东西

之后点击开始就行。

3、创建一个虚拟磁盘(用于存放windows镜像)

注:有的人可能觉得为什么还要创建一个虚拟磁盘这不是浪费时间吗,但是这一部是很重要的,我们电脑本身是在Windows系统下运行,其可能会受到其本身系统的影响,创建一个虚拟磁盘可以很好的避免这些问题。

打开创建磁盘并格式化分区选择一个盘然后右键进行如下操作

选择好要放到文件夹,然后初始化并创建分区,就选择默认的就行

创建分区以后电脑上就会多出来一个盘了,后面要用到

把EasySysprep和制作好了的install.win放入这个虚拟磁盘里面

还有你要安装的软件的安装包放在里面如果你要安装软件的话,因为我们在后需虚拟机上的操作是没有网络的,各家网卡各不相同,所以我们要经量使自己做的镜像通用。而且方便后面挂载到虚拟机上面。

放入以后,还要分离VHD,不然虚拟机不能挂载的

4、PE生成镜像文件

这里我们要用微PE生成一个pe.iso文件,用于虚拟机对于镜像的操作

5、创建虚拟机

创建好虚拟机,选择wepe.iso只保留如下东西

不然可能会有问题,然后安装时选择他认的操作系统是windows10不要11不要硬盘要加密,不然添加不了硬盘,之前我在这里卡了很久,就这里

然后添加磁盘,把之前创建的虚拟磁盘添加好

启动虚拟机

先分区,如下操作就行

再安装系统,注意这里选镜像时是选择之前放入虚拟磁盘的install.win了

之后就会静入Windows界面,按住ctrl shift F3会自动重启进入审核桌面,这里可以直接进入windos界面,方便然后你就可以进行设置了,你在里面做的所有设置都会被封装下来的。

进入审核桌面改好操作好

就打开另外一个磁盘里的EasySysprep,全部默认,只改图片上的就行

重启以后要一定进入PE界面,虚拟机是按一下esc就行,然后进入,不然会封装失败

后面根据自己的需求继续设置就行,最后会有install.win文件

根据自己的需要改好就行,然后把install.win文件粘贴到Windows的iso里面替换到原位置就行了。

再用anyburn封装,点击编辑镜像文件把那个install.win替换就行了

总结:系统封装是根据自己的需求来进行调整,方便自己使用,而且可玩性很高,以上便是我自己总结的,如果有问题或者不会的地方可以留言,我会回复的。

希望对你有帮助。

虚拟机创建(windows沙盒与vmware)

注:当我们需要测试一些不太好在主机上跑的软件时,就可以利用虚拟机,这是一个单独隔离的环境可以方便我们玩,而且算是一个真实的环境做测试。

以下的实现基于你已经在Blos已经开启了虚拟化设置(各家主板的操作各不相同这里就不作介绍)

1、windows沙盒(个人觉得操作简单,但是只能模拟你自己的系统,如果有病毒这些可能有感染主机的风险)

(1)前提条件是你的电脑必须是Windows专业版,不然根本玩不了,可以手动升级一下

(2)打开控制面板,卸载软件,然后会有启用或关闭Windows功能点击,就会如下

找到Hyper-V和windows沙盒确定后重启就会有windows沙盒这个东西了,点开就行了,整体是很简单的

就是这个样子的

2、Vmware(一般来说是比较常用且通用的)

(1)你要先准备好所需要的对应系统的windows镜像文件

windows11的镜像下载,这里要选择一下下载的windows镜像文件

创建一个密码启动就行了

跟着引导走就行了,后面就可以得到这个了

wordpress文章转markdown

1、环境准备

下载node.js

2、这里提取出.xml文章文件

1
2
3
4
重命名为export.xml(很重要)
在存在export.xml的文件夹里打开cmd输入
npx wordpress-export-to-markdown
根据提示下一步就行。

python打包成exe(pyinstaller的使用)

注:当我们用python写好一个完整的程序以后想要把这个py主程序运行到没有安装python的windows时,我们就要用到pyinstaller这个第三方库用来把这些程序打包成exe的可执行文件方便其他人使用你的程序,以下是一个完整的流程

一、项目结构(必须这样放,否则打包必错)

你先建一个文件夹,比如叫  MyApp ,所有东西都放这里。

plaintext

MyApp/
├── main.py # 主程序入口
├── icon.ico # 程序图标(Windows)
├── config.json # 配置文件用于嵌套什么的
├── settings.ini # 配置文件一般是很简单的配置有时我们一般人都可以直接改这两是不同的东西
├── assets/ # 资源文件夹(图片、音频、数据、字体)
│ ├── images/ # 图片
│ ├── sounds/ # 音频
│ ├── data/ # 数据文件(csv、txt、xlsx)
│ └── fonts/ # 字体
├── src/ # 你自己写的模块
│ ├── utils.py #(用于处理时间加密的)
│ ├── api.py #(接口文件)
│ └── ui.py #(用户界面的布局)
└── libs/ # 第三方库/自己封装的库  

二、代码里必须写的路径函数(打包后不丢资源)

把这段代码放到  main.py  最前面,所有资源都用这个函数读取:

python

import sys
import os

def resource_path(relative_path):
if hasattr(sys, ‘_MEIPASS’):
return os.path.join(sys._MEIPASS, relative_path)
return os.path.join(os.path.abspath(‘.’), relative_path)  

例子:

img = resource_path(“assets/images/logo.png”)
config = resource_path(“config.json”)
data = resource_path(“assets/data/data.csv”)  

三、打包前必须做的环境准备(必做)

  1. 打开命令行,进入项目文件夹

cd D:\MyApp #(切换到对应目录)  

  1. 创建虚拟环境(避免打包体积大、报错)

python -m venv venv
 其实不创建也可以,就是感觉容易冲突出问题。

  1. 激活虚拟环境

venv\Scripts\activate  

  1. 安装项目需要的库(只装你用到的)

pip install 你需要的库 #(这个能懂吧)  

  1. 安装 PyInstaller(这就是打包的第三方库)

pip install pyinstaller  

四、最完整打包命令(包含所有文件、资源、模块、图标)

下面这条命令是万能终极版,包含所有你需要的功能,直接复制运行:

pyinstaller -F -w –clean –noconfirm -i icon.ico –add-data “assets;assets” –add-data “config.json;.” –add-data “settings.ini;.” –add-data “src;src” –add-data “libs;libs” main.py

这个前提是每个文件都放在其对应的位置,必须严格放置。  

五、命令每一部分详细解释(你必须懂)

我把上面的命令拆解开,每一段都告诉你是什么、干什么、为什么要加:

  1.  pyinstaller

 

  • 作用:启动打包工具

  • 必须写在最前面

 -F 

  • 全称: –onefile 

  • 作用:打包成单个 exe 文件

  • 不加的话会生成一个文件夹,里面一堆文件

  • 必须加,最常用

-w 

  • 全称: –windowed  /  –noconsole 

  • 作用:不显示黑色控制台窗口

  • 适合 GUI 程序、桌面软件

  • 如果你是命令行程序,就不要加  -w ,用  -c

 

--clean 

  • 作用:打包前自动清理旧缓存、旧文件

  • 避免旧文件干扰导致打包失败

  • 每次打包都建议加

--noconfirm 

  • 作用:打包时不询问是否覆盖,直接覆盖

  • 避免重复打包时弹确认框

-i icon.ico 

  • 作用:设置程序图标

  • 必须是  .ico  格式

  • 放在项目根目录

  1.  –add-data “assets;assets”

 

  • 作用:把  assets  文件夹打包进 exe

  • 格式: 源路径;目标路径 

  • Windows 用分号  ; ,Mac/Linux 用冒号  : 

  • 不加这个,打包后图片、音频、数据都会丢失

--add-data “config.json;.” 

  • 作用:把  config.json  打包到 exe 同目录

  •  .  表示 exe 所在目录

--add-data “settings.ini;.” 

  • 作用:把  settings.ini  打包到 exe 同目录

--add-data “src;src” 

  • 作用:把你自己写的  src  模块文件夹打包进去

  • 不加会导致  import src.utils  报错

--add-data “libs;libs” 

  • 作用:把  libs  文件夹打包进去

  • 如果你有自己的库文件,必须加

main.py 

  • 作用:指定主程序入口文件

  • 必须是你项目的启动文件

六、如果打包后提示“找不到模块”,加这个参数

--hidden-import=模块名  

实例

pyinstaller -F –hidden-import=requests –hidden-import=urllib3 –hidden-import=pandas._libs.tslibs.base main.py  

七、打包后文件在哪里?

打包完成后,在  MyApp  文件夹里:

  •  dist/main.exe  → 最终可执行文件

  •  build/  → 临时文件,可删除

  •  main.spec  → 配置文件,不用管

  • 反正找exe文件就对了

八、最常见问题怎么解决

  1. 打包后闪退

把  -w  换成  -c ,看报错:

pyinstaller -F -c main.py  

  1. 打包后找不到资源

必须加  –add-data ,并且代码用  resource_path 

  1. 打包后 exe 很大(我自己一般不管,因为自己用懒得配虚拟环境了,个人感觉问题也不大)

用虚拟环境,只装必要库

  1. 打包后报毒

这个是正常的,因为是自己写的软件,现在软件基本上要搞数字签名才不会包病毒,如果觉得烦可以去研究一下

  1. 打包后功能不全

检查是否漏了  –add-data  或  –hidden-import 

九、以下是一些常用的模板吧,对应打命令就行

  1. 最常用(单文件 + 无黑窗 + 图标 + 资源)

pyinstaller -F -w –clean -i icon.ico –add-data “assets;assets” main.py  

  1. 调试用(单文件 + 显示黑窗 + 看报错)

pyinstaller -F -c –clean main.py  

  1. 完整项目(包含所有文件、模块、配置)

pyinstaller -F -w –clean –noconfirm -i icon.ico –add-data “assets;assets” –add-data “config.json;.” –add-data “src;src” main.py

总结:大致的方法就是这样,因为打包还是要根据你自己写的具体情况来分析,如果你是严格按照规定来的大概率没有问题,但是,你如果写的是屎山,那你就得自己研究了,反正方法大差不差,我就只能提供个思路。代码是死的,人是活的嘛,只要能达到目的一切都好,哪怕写的是屎山。