diff --git a/data/names.xlsx b/data/names.xlsx index 7cb70d3..955601f 100644 Binary files a/data/names.xlsx and b/data/names.xlsx differ diff --git a/main.spec b/main.spec new file mode 100644 index 0000000..98e2897 --- /dev/null +++ b/main.spec @@ -0,0 +1,39 @@ +# -*- mode: python ; coding: utf-8 -*- + + +a = Analysis( + ['main.pyw'], + pathex=[], + binaries=[], + datas=[], + hiddenimports=[], + hookspath=[], + hooksconfig={}, + runtime_hooks=[], + excludes=[], + noarchive=False, + optimize=0, +) +pyz = PYZ(a.pure) + +exe = EXE( + pyz, + a.scripts, + a.binaries, + a.datas, + [], + name='main', + debug=False, + bootloader_ignore_signals=False, + strip=False, + upx=True, + upx_exclude=[], + runtime_tmpdir=None, + console=False, + disable_windowed_traceback=False, + argv_emulation=False, + target_arch=None, + codesign_identity=None, + entitlements_file=None, + icon=['public\\icon.ico'], +) diff --git a/public/icon.ico b/public/icon.ico new file mode 100644 index 0000000..2954be1 Binary files /dev/null and b/public/icon.ico differ diff --git a/pyproject.toml b/pyproject.toml index b10b2f9..7f8a356 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -13,6 +13,7 @@ dependencies = [ "pandas>=2.3.3", "pandas-stubs==2.3.3.251201", "pillow>=12.0.0", + "pyinstaller>=6.17.0", "python-pptx>=1.0.2", "pywin32>=311", "rich>=14.2.0", diff --git a/templates/横板/中班 幼儿学期发展报告.pptx b/templates/横板/中班 幼儿学期发展报告.pptx index ad5de19..29de2bd 100644 Binary files a/templates/横板/中班 幼儿学期发展报告.pptx and b/templates/横板/中班 幼儿学期发展报告.pptx differ diff --git a/templates/竖版/中班 幼儿学期发展报告.pptx b/templates/竖版/中班 幼儿学期发展报告.pptx index d9899a5..1002d9c 100644 Binary files a/templates/竖版/中班 幼儿学期发展报告.pptx and b/templates/竖版/中班 幼儿学期发展报告.pptx differ diff --git a/utils/file_utils.py b/utils/file_utils.py index 68056f4..d4aff02 100644 --- a/utils/file_utils.py +++ b/utils/file_utils.py @@ -191,3 +191,14 @@ def initialize_project(root_dir="."): logger.warning( f"⚠️ 警告: 模板文件不存在 ({src_excel}),data 文件夹内将没有 Excel 文件。" ) + +def check_file_exists(file_path): + """ + 判断文件是否存在 + """ + if (file_path and isinstance(file_path, str) and os.path.exists(file_path)): + logger.info(f"✅ 文件存在: {file_path}") + return True + else: + logger.error(f"❌ 文件不存在: {file_path}") + return False \ No newline at end of file diff --git a/utils/generate_utils.py b/utils/generate_utils.py index c3c6ed6..503e6ed 100644 --- a/utils/generate_utils.py +++ b/utils/generate_utils.py @@ -11,6 +11,7 @@ import traceback import comtypes.client from config.config import load_config from utils.agent_utils import generate_comment +from utils.file_utils import check_file_exists from utils.font_utils import install_fonts_from_directory from utils.image_utils import find_image_path from utils.zodiac_utils import calculate_zodiac @@ -222,47 +223,50 @@ def generate_report(): # --- 页面 3 --- student_image_folder = os.path.join(config["image_folder"], name) logger.info(f"学生:{name},图片文件夹: {student_image_folder}") - if os.path.exists(student_image_folder): - me_image_path = find_image_path(student_image_folder, "me") + # 判断学生图片文件夹是否存在 + if not os.path.exists(student_image_folder): + logger.warning( + f"⚠️ 警告: 学生:{name},学生图片文件夹不存在 {student_image_folder}" + ) + continue - # 构造信息字典供 helper 使用 - info_dict = { - "name": name, - "english_name": english_name, - "sex": sex, - "birthday": birthday.strftime("%Y-%m-%d") if pd.notna(birthday) else "", - "zodiac": zodiac, - "friend": friend, - "hobby": hobby, - "game": game, - "food": food, - } - # 逻辑:必须同时满足 "不是None" 且 "是字符串" 且 "文件存在" 才能执行 - if ( - me_image_path - and isinstance(me_image_path, str) - and os.path.exists(me_image_path) - ): - replace_three_page(prs, info_dict, me_image_path) - else: - # 只有在这里打印日志,告诉用户跳过了,但不中断程序 - replace_three_page(prs, info_dict, None) + # 检查姓名是否为空 + if not name: + logger.error(f"⚠️ 警告: 学生:{name},姓名为空,跳过") + break + # 构造学生信息字典 + student_info_dict = { + "name": name, + "english_name": english_name if pd.notna(english_name) else "", + "sex": sex if pd.notna(sex) else "男", + "birthday": ( + birthday.strftime("%Y-%m-%d") if pd.notna(birthday) else "" + ), + "zodiac": zodiac if pd.notna(zodiac) else "", + "friend": friend if pd.notna(friend) else "", + "hobby": hobby if pd.notna(hobby) else "", + "game": game if pd.notna(game) else "", + "food": food if pd.notna(food) else "", + } + # 获取学生个人照片路径 + me_image_path = find_image_path(student_image_folder, "me") + # 检查学生图片是否存在,若不存在则跳过 + if check_file_exists(me_image_path): + replace_three_page(prs, student_info_dict, me_image_path) else: - logger.warning(f"⚠️ 警告: 学生:{name},学生图片文件夹不存在 {student_image_folder}") + logger.warning(f"⚠️ 警告: 学生图片文件不存在 {me_image_path}") # --- 页面 4 --- class_image_path = find_image_path( config["image_folder"], config["class_name"] ) - if ( - class_image_path - and isinstance(class_image_path, str) - and os.path.exists(class_image_path) - ): + + # 添加检查班级图片是否存在,若不存在则跳过 + if check_file_exists(class_image_path): replace_four_page(prs, class_image_path) else: logger.warning(f"⚠️ 警告: 班级图片文件不存在 {class_image_path}") - + # --- 页面 5 --- if os.path.exists(student_image_folder): img1_path = find_image_path(student_image_folder, "1") @@ -335,10 +339,10 @@ def batch_convert_folder(folder_path): try: # 1. 启动应用 (只启动一次) powerpoint = comtypes.client.CreateObject("PowerPoint.Application") - + # 【建议】在后台线程运行时,有时设置为不可见更稳定, # 但如果遇到转换卡死,可以尝试去掉下面这行的注释,让它显示出来 - # powerpoint.Visible = 1 + # powerpoint.Visible = 1 for filename in files: ppt_path = os.path.join(folder_path, filename) @@ -354,7 +358,7 @@ def batch_convert_folder(folder_path): try: # 打开 -> 另存为 -> 关闭 deck = powerpoint.Presentations.Open(ppt_path) - deck.SaveAs(pdf_path, 32) # 32 代表 PDF 格式 + deck.SaveAs(pdf_path, 32) # 32 代表 PDF 格式 deck.Close() except Exception as e: logger.error(f"文件 {filename} 转换出错: {e}") @@ -376,6 +380,7 @@ def batch_convert_folder(folder_path): # 【核心修复 2】释放资源 pythoncom.CoUninitialize() + # ========================================== # 5. 生成属相(根据names.xlsx文件生成属相) # ========================================== diff --git a/uv.lock b/uv.lock index 1c41422..74f70e2 100644 --- a/uv.lock +++ b/uv.lock @@ -2,6 +2,15 @@ version = 1 revision = 2 requires-python = ">=3.13" +[[package]] +name = "altgraph" +version = "0.17.5" +source = { registry = "https://mirrors.aliyun.com/pypi/simple/" } +sdist = { url = "https://mirrors.aliyun.com/pypi/packages/7e/f8/97fdf103f38fed6792a1601dbc16cc8aac56e7459a9fff08c812d8ae177a/altgraph-0.17.5.tar.gz", hash = "sha256:c87b395dd12fabde9c99573a9749d67da8d29ef9de0125c7f536699b4a9bc9e7" } +wheels = [ + { url = "https://mirrors.aliyun.com/pypi/packages/a9/ba/000a1996d4308bc65120167c21241a3b205464a2e0b58deda26ae8ac21d1/altgraph-0.17.5-py2.py3-none-any.whl", hash = "sha256:f3a22400bce1b0c701683820ac4f3b159cd301acab067c51c653e06961600597" }, +] + [[package]] name = "annotated-types" version = "0.7.0" @@ -122,6 +131,7 @@ dependencies = [ { name = "pandas" }, { name = "pandas-stubs" }, { name = "pillow" }, + { name = "pyinstaller" }, { name = "python-pptx" }, { name = "pywin32" }, { name = "rich" }, @@ -138,6 +148,7 @@ requires-dist = [ { name = "pandas", specifier = ">=2.3.3" }, { name = "pandas-stubs", specifier = "==2.3.3.251201" }, { name = "pillow", specifier = ">=12.0.0" }, + { name = "pyinstaller", specifier = ">=6.17.0" }, { name = "python-pptx", specifier = ">=1.0.2" }, { name = "pywin32", specifier = ">=311" }, { name = "rich", specifier = ">=14.2.0" }, @@ -459,6 +470,18 @@ wheels = [ { url = "https://mirrors.aliyun.com/pypi/packages/92/aa/df863bcc39c5e0946263454aba394de8a9084dbaff8ad143846b0d844739/lxml-6.0.2-cp314-cp314t-win_arm64.whl", hash = "sha256:bb4c1847b303835d89d785a18801a883436cdfd5dc3d62947f9c49e24f0f5a2c" }, ] +[[package]] +name = "macholib" +version = "1.16.4" +source = { registry = "https://mirrors.aliyun.com/pypi/simple/" } +dependencies = [ + { name = "altgraph" }, +] +sdist = { url = "https://mirrors.aliyun.com/pypi/packages/10/2f/97589876ea967487978071c9042518d28b958d87b17dceb7cdc1d881f963/macholib-1.16.4.tar.gz", hash = "sha256:f408c93ab2e995cd2c46e34fe328b130404be143469e41bc366c807448979362" } +wheels = [ + { url = "https://mirrors.aliyun.com/pypi/packages/c7/d1/a9f36f8ecdf0fb7c9b1e78c8d7af12b8c8754e74851ac7b94a8305540fc7/macholib-1.16.4-py2.py3-none-any.whl", hash = "sha256:da1a3fa8266e30f0ce7e97c6a54eefaae8edd1e5f86f3eb8b95457cae90265ea" }, +] + [[package]] name = "markdown-it-py" version = "4.0.0" @@ -692,6 +715,15 @@ wheels = [ { url = "https://mirrors.aliyun.com/pypi/packages/e2/68/78a3c253f146254b8e2c19f4a4768f272e12ef11001d9b45ec7b165db054/pandas_stubs-2.3.3.251201-py3-none-any.whl", hash = "sha256:eb5c9b6138bd8492fd74a47b09c9497341a278fcfbc8633ea4b35b230ebf4be5" }, ] +[[package]] +name = "pefile" +version = "2024.8.26" +source = { registry = "https://mirrors.aliyun.com/pypi/simple/" } +sdist = { url = "https://mirrors.aliyun.com/pypi/packages/03/4f/2750f7f6f025a1507cd3b7218691671eecfd0bbebebe8b39aa0fe1d360b8/pefile-2024.8.26.tar.gz", hash = "sha256:3ff6c5d8b43e8c37bb6e6dd5085658d658a7a0bdcd20b6a07b1fcfc1c4e9d632" } +wheels = [ + { url = "https://mirrors.aliyun.com/pypi/packages/54/16/12b82f791c7f50ddec566873d5bdd245baa1491bac11d15ffb98aecc8f8b/pefile-2024.8.26-py3-none-any.whl", hash = "sha256:76f8b485dcd3b1bb8166f1128d395fa3d87af26360c2358fb75b80019b957c6f" }, +] + [[package]] name = "pillow" version = "12.0.0" @@ -827,6 +859,47 @@ wheels = [ { url = "https://mirrors.aliyun.com/pypi/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b" }, ] +[[package]] +name = "pyinstaller" +version = "6.17.0" +source = { registry = "https://mirrors.aliyun.com/pypi/simple/" } +dependencies = [ + { name = "altgraph" }, + { name = "macholib", marker = "sys_platform == 'darwin'" }, + { name = "packaging" }, + { name = "pefile", marker = "sys_platform == 'win32'" }, + { name = "pyinstaller-hooks-contrib" }, + { name = "pywin32-ctypes", marker = "sys_platform == 'win32'" }, + { name = "setuptools" }, +] +sdist = { url = "https://mirrors.aliyun.com/pypi/packages/01/80/9e0dad9c69a7cfd4b5aaede8c6225d762bab7247a2a6b7651e1995522001/pyinstaller-6.17.0.tar.gz", hash = "sha256:be372bd911392b88277e510940ac32a5c2a6ce4b8d00a311c78fa443f4f27313" } +wheels = [ + { url = "https://mirrors.aliyun.com/pypi/packages/35/f5/37e419d84d5284ecab11ef8b61306a3b978fe6f0fd69a9541e16bfd72e65/pyinstaller-6.17.0-py3-none-macosx_10_13_universal2.whl", hash = "sha256:4e446b8030c6e5a2f712e3f82011ecf6c7ead86008357b0d23a0ec4bcde31dac" }, + { url = "https://mirrors.aliyun.com/pypi/packages/9e/b6/2e184879ab9cf90a1d2867fdd34d507c4d246b3cc52ca05aad00bfc70ee7/pyinstaller-6.17.0-py3-none-manylinux2014_aarch64.whl", hash = "sha256:aa9fd87aaa28239c6f0d0210114029bd03f8cac316a90bab071a5092d7c85ad7" }, + { url = "https://mirrors.aliyun.com/pypi/packages/40/76/f529de98f7e5cce7904c19b224990003fc2267eda2ee5fdd8452acb420a9/pyinstaller-6.17.0-py3-none-manylinux2014_i686.whl", hash = "sha256:060b122e43e7c0b23e759a4153be34bd70914135ab955bb18a67181e0dca85a2" }, + { url = "https://mirrors.aliyun.com/pypi/packages/a3/10/c02bfbb050cafc4c353cf69baf95407e211e1372bd286ab5ce5cbc13a30a/pyinstaller-6.17.0-py3-none-manylinux2014_ppc64le.whl", hash = "sha256:cd213d1a545c97dfe4a3c40e8213ff7c5127fc115c49229f27a3fa541503444b" }, + { url = "https://mirrors.aliyun.com/pypi/packages/11/9d/69fdacfd9335695f5900a376cfe3e4aed28f0720ffc15fee81fdb9d920bc/pyinstaller-6.17.0-py3-none-manylinux2014_s390x.whl", hash = "sha256:89c0d18ba8b62c6607abd8cf2299ae5ffa5c36d8c47f39608ce8c3f357f6099f" }, + { url = "https://mirrors.aliyun.com/pypi/packages/5e/1e/e8e36e1568f6865ac706c6e1f875c1a346ddaa9f9a8f923d66545d2240ed/pyinstaller-6.17.0-py3-none-manylinux2014_x86_64.whl", hash = "sha256:2a147b83cdebb07855bd5a663600891550062373a2ca375c58eacead33741a27" }, + { url = "https://mirrors.aliyun.com/pypi/packages/8d/15/9dc0f81ccb746c27bfa6ee53164422fe47ee079c7a717d9c4791aba78797/pyinstaller-6.17.0-py3-none-musllinux_1_1_aarch64.whl", hash = "sha256:f8cfbbfa6708e54fb936df6dd6eafaf133e84efb0d2fe25b91cfeefa793c4ca4" }, + { url = "https://mirrors.aliyun.com/pypi/packages/97/e6/bed54821c1ebe1275c559661d3e7bfa23c406673b515252dfbf89db56c65/pyinstaller-6.17.0-py3-none-musllinux_1_1_x86_64.whl", hash = "sha256:97f4c1942f7b4cd73f9e38b49cc8f5f8a6fbb44922cb60dd3073a189b77ee1ae" }, + { url = "https://mirrors.aliyun.com/pypi/packages/c7/84/897d759198676b910d69d42640b6d25d50b449f2209e18127a974cf59dbe/pyinstaller-6.17.0-py3-none-win32.whl", hash = "sha256:ce0be227a037fd4be672226db709088565484f597d6b230bceec19850fdd4c85" }, + { url = "https://mirrors.aliyun.com/pypi/packages/2d/f5/6a122efe024433ecc34aab6f499e0bd2bbe059c639b77b0045aa2421b0bf/pyinstaller-6.17.0-py3-none-win_amd64.whl", hash = "sha256:b019940dbf7a01489d6b26f9fb97db74b504e0a757010f7ad078675befc85a82" }, + { url = "https://mirrors.aliyun.com/pypi/packages/c4/96/14991773c9e599707a53594429ccf372f9ee638df3b7d26b65fd1a7433f0/pyinstaller-6.17.0-py3-none-win_arm64.whl", hash = "sha256:3c92a335e338170df7e615f75279cfeea97ade89e6dd7694943c8c185460f7b7" }, +] + +[[package]] +name = "pyinstaller-hooks-contrib" +version = "2025.10" +source = { registry = "https://mirrors.aliyun.com/pypi/simple/" } +dependencies = [ + { name = "packaging" }, + { name = "setuptools" }, +] +sdist = { url = "https://mirrors.aliyun.com/pypi/packages/26/4f/e33132acdb8f732978e577b8a0130a412cbfe7a3414605e3fd380a975522/pyinstaller_hooks_contrib-2025.10.tar.gz", hash = "sha256:a1a737e5c0dccf1cf6f19a25e2efd109b9fec9ddd625f97f553dac16ee884881" } +wheels = [ + { url = "https://mirrors.aliyun.com/pypi/packages/86/de/a7688eed49a1d3df337cdaa4c0d64e231309a52f269850a72051975e3c4a/pyinstaller_hooks_contrib-2025.10-py3-none-any.whl", hash = "sha256:aa7a378518772846221f63a84d6306d9827299323243db890851474dfd1231a9" }, +] + [[package]] name = "python-dateutil" version = "2.9.0.post0" @@ -876,6 +949,15 @@ wheels = [ { url = "https://mirrors.aliyun.com/pypi/packages/c0/d2/21af5c535501a7233e734b8af901574572da66fcc254cb35d0609c9080dd/pywin32-311-cp314-cp314-win_arm64.whl", hash = "sha256:a508e2d9025764a8270f93111a970e1d0fbfc33f4153b388bb649b7eec4f9b42" }, ] +[[package]] +name = "pywin32-ctypes" +version = "0.2.3" +source = { registry = "https://mirrors.aliyun.com/pypi/simple/" } +sdist = { url = "https://mirrors.aliyun.com/pypi/packages/85/9f/01a1a99704853cb63f253eea009390c88e7131c67e66a0a02099a8c917cb/pywin32-ctypes-0.2.3.tar.gz", hash = "sha256:d162dc04946d704503b2edc4d55f3dba5c1d539ead017afa00142c38b9885755" } +wheels = [ + { url = "https://mirrors.aliyun.com/pypi/packages/de/3d/8161f7711c017e01ac9f008dfddd9410dff3674334c233bde66e7ba65bbf/pywin32_ctypes-0.2.3-py3-none-any.whl", hash = "sha256:8a1513379d709975552d202d942d9837758905c8d01eb82b8bcc30918929e7b8" }, +] + [[package]] name = "pyyaml" version = "6.0.3" @@ -1016,6 +1098,15 @@ wheels = [ { url = "https://mirrors.aliyun.com/pypi/packages/25/7a/b0178788f8dc6cafce37a212c99565fa1fe7872c70c6c9c1e1a372d9d88f/rich-14.2.0-py3-none-any.whl", hash = "sha256:76bc51fe2e57d2b1be1f96c524b890b816e334ab4c1e45888799bfaab0021edd" }, ] +[[package]] +name = "setuptools" +version = "80.9.0" +source = { registry = "https://mirrors.aliyun.com/pypi/simple/" } +sdist = { url = "https://mirrors.aliyun.com/pypi/packages/18/5d/3bf57dcd21979b887f014ea83c24ae194cfcd12b9e0fda66b957c69d1fca/setuptools-80.9.0.tar.gz", hash = "sha256:f36b47402ecde768dbfafc46e8e4207b4360c654f1f3bb84475f0a28628fb19c" } +wheels = [ + { url = "https://mirrors.aliyun.com/pypi/packages/a3/dc/17031897dae0efacfea57dfd3a82fdd2a2aeb58e0ff71b77b87e44edc772/setuptools-80.9.0-py3-none-any.whl", hash = "sha256:062d34222ad13e0cc312a4c02d73f059e86a4acbfbdea8f8f76b28c99f306922" }, +] + [[package]] name = "six" version = "1.17.0"