Compare commits

...

19 Commits

Author SHA1 Message Date
Voge1imkafig 202b742163 提交 2025-08-04 17:42:49 +08:00
Voge1imkafig 7808a0b637 增加ignore,修复因开发jf模板而导致dt模板出现的问题 2025-08-04 17:42:23 +08:00
Voge1imkafig e39e04cdc0 完成添加金风动态图片函数 2025-08-01 18:02:19 +08:00
Voge1imkafig 78ab1e3962 完成标题分级样式配置,添加横向页面,设计添加金风图片表格的函数。更改某些函数的样式获取等。 2025-07-31 18:10:56 +08:00
Voge1imkafig 8402a2cb7f 完成脱离模板的添加标题行函数,调试完了服务器返回数据->dict->格式化二维list->文档表格的无模板数据流。目前金风模板能生成到器具表格。 2025-07-30 17:31:18 +08:00
Voge1imkafig a354301f86 完成脱离模板的添加标题行函数,调试完了服务器返回数据->dict->格式化二维list->文档表格的无模板数据流。目前金风模板能生成到器具表格。 2025-07-30 17:31:05 +08:00
Voge1imkafig a3c430ff91 增加金风的生成,优化代码,目前能生成金风的静态内容 2025-07-29 18:01:15 +08:00
Voge1imkafig 98866d8d94 修改部分错误 2025-07-25 17:35:05 +08:00
Voge1imkafig 86080f5a63 修改部分错误 2025-07-25 17:34:20 +08:00
Voge1imkafig 92e7d24623 修改部分错误 2025-07-25 17:32:29 +08:00
Voge1imkafig 2270400dca 添加默认值 2025-07-25 17:22:51 +08:00
Voge1imkafig 1270bf321b 更新readme 2025-07-25 17:17:56 +08:00
Voge1imkafig 226c5d8ccd 模板未增加默认封面,更正 2025-07-25 16:58:36 +08:00
Voge1imkafig c5bdeb9ada 更正交叉编译未打包依赖bug 2025-07-25 16:54:31 +08:00
Voge1imkafig 3becbc3acd 更正汇总图插入bug,交叉编译exe执行文件 2025-07-25 16:44:31 +08:00
Voge1imkafig d71251be06 完成接口与接口文档 2025-07-25 11:13:36 +08:00
Voge1imkafig a6fa6ebf9a 完成图片的插入,典型图插入未测试 2025-07-24 18:00:03 +08:00
Voge1imkafig 165698c7a3 删除额外的打印信息 2025-07-22 18:04:15 +08:00
Voge1imkafig a62e3afddc 增加打包的额外参数,获取了典型、缺陷图,和对应的缺陷记录 2025-07-22 16:39:17 +08:00
76 changed files with 3747 additions and 1057 deletions

202
.gitignore vendored Normal file
View File

@ -0,0 +1,202 @@
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[codz]
*$py.class
# C extensions
*.so
# Distribution / packaging
.Python
build/
develop-eggs/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
*.py.cover
.hypothesis/
.pytest_cache/
cover/
# Translations
*.mo
*.pot
# Django stuff:
*.log
local_settings.py
db.sqlite3
db.sqlite3-journal
# Flask stuff:
instance/
.webassets-cache
# Scrapy stuff:
.scrapy
# Sphinx documentation
docs/_build/
# PyBuilder
.pybuilder/
target/
# Jupyter Notebook
.ipynb_checkpoints
# IPython
profile_default/
ipython_config.py
# pyenv
# For a library or package, you might want to ignore these files since the code is
# intended to run in multiple environments; otherwise, check them in:
# .python-version
# pipenv
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
# However, in case of collaboration, if having platform-specific dependencies or dependencies
# having no cross-platform support, pipenv may install dependencies that don't work, or not
# install all needed dependencies.
#Pipfile.lock
# UV
# Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control.
# This is especially recommended for binary packages to ensure reproducibility, and is more
# commonly ignored for libraries.
#uv.lock
# poetry
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
# This is especially recommended for binary packages to ensure reproducibility, and is more
# commonly ignored for libraries.
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
#poetry.lock
#poetry.toml
# pdm
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
# pdm recommends including project-wide configuration in pdm.toml, but excluding .pdm-python.
# https://pdm-project.org/en/latest/usage/project/#working-with-version-control
#pdm.lock
#pdm.toml
.pdm-python
.pdm-build/
# pixi
# Similar to Pipfile.lock, it is generally recommended to include pixi.lock in version control.
#pixi.lock
# Pixi creates a virtual environment in the .pixi directory, just like venv module creates one
# in the .venv directory. It is recommended not to include this directory in version control.
.pixi
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
__pypackages__/
# Celery stuff
celerybeat-schedule
celerybeat.pid
# SageMath parsed files
*.sage.py
# Environments
.env
.envrc
.venv
env/
venv/
ENV/
env.bak/
venv.bak/
# Spyder project settings
.spyderproject
.spyproject
# Rope project settings
.ropeproject
# mkdocs documentation
/site
# mypy
.mypy_cache/
.dmypy.json
dmypy.json
# Pyre type checker
.pyre/
# pytype static type analyzer
.pytype/
# Cython debug symbols
cython_debug/
# PyCharm
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
# and can be added to the global gitignore or merged into this file. For a more nuclear
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
#.idea/
# Abstra
# Abstra is an AI-powered process automation framework.
# Ignore directories containing user credentials, local state, and settings.
# Learn more at https://abstra.io/docs
.abstra/
# Visual Studio Code
# Visual Studio Code specific template is maintained in a separate VisualStudioCode.gitignore
# that can be found at https://github.com/github/gitignore/blob/main/Global/VisualStudioCode.gitignore
# and can be added to the global gitignore or merged into this file. However, if you prefer,
# you could uncomment the following to ignore the entire vscode folder
# .vscode/
# Ruff stuff:
.ruff_cache/
# PyPI configuration file
.pypirc
# Marimo
marimo/_static/
marimo/_lsp/
__marimo__/
# Streamlit
.streamlit/secrets.toml

575
Dt_report.py Normal file
View File

@ -0,0 +1,575 @@
# 文档处理工具
from tools.document_tools import (
create_document, add_documents,add_table_and_replace,
add_table_to_document,add_dynamic_table,
process_server_images_table,add_header,change_heading
)
# 内容处理工具
from tools.content_tools import (
add_picture,split_table_by_row_content,
search_and_replace,add_heading
)
from tools.get_pictures import (
process_picture_data,get_records_with_pic
)
from tools.Get_Json import (
get_project_info,get_jizu_info,
get_jizu_shigong_info,get_defect_detail,
get_part_picture,get_yepian_xiangqing,
check_pic_url,get_full_picture_url,
get_defect_record_list
)
from tools.dataproccess import (
caculate_work_days,get_year_month,
merge_info,get_defect_str,
safe_get,get_resource_path,merge_dicts
)
from core.tables import fill_tables
from tools.defines import *
import os, re, datetime
async def generate_dt_report(base_info, baogao_info):
#获取模板编号、模板名称
num_to_chinese = {1 : '', 2 : '', 3 : '', 4 : '', 5 : '', 6 : '', 7 : '', 8 : '', 9 : '', 10 : '', 11 : '十一', 12 : '十二'}
cover_encode = "encode"
cover_project = "project"
baogao_name1 = "baogaoname1"
baogao_name2 = "baogaoname2"
company_name_yi = "company_name_yi"
cover_date = "time"
TITLE_OF_REPORT = "companyencode"
jiegou_xuhao = 'num'
print(f"获取到参数:基本信息:{base_info}\n\n报告信息:{baogao_info}")
try:
base_info = merge_info(base_info, DEFAULT_BASE_INFO)
turbine_id = base_info['turbine_id']
jizu_data = get_jizu_info(turbine_id)
project_data = get_project_info(jizu_data['projectId'])
shigong_data = get_jizu_shigong_info(turbine_id)
fengchang_name = project_data['farmName']
Yi_company = project_data['inspectionUnit']
yi_fuzeren = project_data['inspectionContact']
yi_phone = project_data['inspectionPhone']
fengchang_location = project_data['farmAddress']
Jia_company = project_data['client']
jia_fuzeren = project_data['clientContact']
jia_phone = project_data['clientPhone']
jizu_num = project_data['scale']
jizu_xinghao = project_data['turbineModel']
project_name = project_data['projectName']
jizu_bianhao = jizu_data["turbineName"]
start_date = project_data['startDate']
end_date = project_data['endDate']
cover_url = project_data['coverUrl']
gongqi = caculate_work_days(start_date, end_date)
except Exception as e:
print(f"数据库的项目-机组基本信息获取失败:{e}")
return
try:
baogao_date = datetime.datetime.now().strftime("%Y年%m月%d%H:%M") #现在的时间
date_year_month = get_year_month(baogao_date)
#前端信息
baogao_info = merge_info(baogao_info, DEFAULT_BAOGAO_INFO)
key_words= re.compile('|'.join(map(re.escape, baogao_info['key_words'].split(',')))) #关键字
shengcheng_dir = baogao_info['shengcheng_dir'] #路径
if shengcheng_dir == "":
print("未配置生成路径,请检查配置")
return
if_waibu = baogao_info["if_waibu"]
if_neibu = baogao_info["if_neibu"]
if_fanglei = baogao_info["if_fanglei"]
quexian_type = baogao_info['quexian_enum']
dianxing_type = baogao_info['dianxing_enum']
other_type = baogao_info["other_enum"]
jiancha_renyuan = baogao_info['jiancha_renyuan'] #检查人员,目前是从命令行参数获取,需要完善
check_date = baogao_info['check_date']
if check_date == None:
check_date = "未获取"
baogao_bianzhi = baogao_info["userName"]
baogao_shenghe = baogao_info["baogaoCheck"]
Jiancha_date = baogao_info["check_date"]
data_processor = baogao_info["data_processor"]
coverurl = baogao_info["coverurl"]
#数据库拉取信息
# Jiancha_date = shigong_data["startTime"].replace("T", " ") #检查日期
# image_count = shigong_data['imageCount'] #从施工方案获取的图片数量,待定!!!
# temperature = shigong_data['temperature'] #温度
# wind_speed = shigong_data['windSpeed'] #风速
# weather = get_weather(shigong_data["weatherCode"]) #天气 不从此接口获取,待定!!!
#拉取部件、图片数据
part_data, picture_data1, picture_data2, Yepians = get_part_picture(turbine_id)
picture_data = merge_dicts(picture_data1, picture_data2)
print(Yepians)
Y1_info = get_yepian_xiangqing(Yepians[0]["partId"])
Y_Code = [yepian["partCode"] for yepian in Yepians]
partManufacturer = Y1_info["partManufacturer"]
print(f"找到叶片号{Y_Code},厂商{partManufacturer}")
image_source_to_find = []
baogao_label = []
renyuan_peizhi = []
gongzuo_neirong = []
shigong_fangan = []
shebei_peizhi = []
beizhu = []
jiancha = []
neirong = []
neirong2 = []
#获取对应枚举字段
if if_waibu:
baogao_label.append("外观")
image_source_to_find.append(baogao_info['waibu_enum'])
if baogao_info["shigong_fangan"] == None:
print("未传入施工方案,使用已有枚举")
renyuan_peizhi.append(SHIGONG_FANGAN_ENUM.WAIBU.RENYUAN_PEIZHI)
gongzuo_neirong.append(SHIGONG_FANGAN_ENUM.WAIBU.GONGZUO_NEIRONG)
shebei_peizhi.append(SHIGONG_FANGAN_ENUM.WAIBU.SHEBEI_PEIZHI)
shigong_fangan.append(SHIGONG_FANGAN_ENUM.WAIBU.SHIGONG_FANGAN)
else:
pass #待添加如果从平台传入枚举,但目前没必要,如果这种枚举标准信息库有更好的优化则可以实现
jiancha.append("无人机近距离外观检查")
neirong.append(f"".join(Y_Code) + f"{len(Y_Code)}支叶片的前缘、后缘、迎风面、背风面。")
neirong2.append("前缘、后缘、迎风面、背风面。")
if if_neibu:
baogao_label.append("内部")
image_source_to_find.append(baogao_info['neibu_enum'])
if baogao_info["shigong_fangan"] == None:
renyuan_peizhi.append(SHIGONG_FANGAN_ENUM.NEIBU.RENYUAN_PEIZHI)
gongzuo_neirong.append(SHIGONG_FANGAN_ENUM.NEIBU.GONGZUO_NEIRONG)
shebei_peizhi.append(SHIGONG_FANGAN_ENUM.NEIBU.SHEBEI_PEIZHI)
shigong_fangan.append(SHIGONG_FANGAN_ENUM.NEIBU.SHIGONG_FANGAN)
else:
pass
jiancha.append("人工内部拍摄")
neirong.append(f"".join(Y_Code) + f"{len(Y_Code)}支叶片的内部导雷卡、腹板、透光、人孔盖版、叶根盖板...")
neirong2.append("内部导雷卡、腹板、透光、人孔盖版、叶根盖板...")
if if_fanglei:
baogao_label.append("防雷")
image_source_to_find.append(baogao_info['fanglei_enum'])
if baogao_info["shigong_fangan"] == None:
renyuan_peizhi.append(SHIGONG_FANGAN_ENUM.FANGLEI.YEPIAN.RENYUAN_PEIZHI)
gongzuo_neirong.append(SHIGONG_FANGAN_ENUM.FANGLEI.YEPIAN.GONGZUO_NEIRONG)
shebei_peizhi.append(SHIGONG_FANGAN_ENUM.FANGLEI.YEPIAN.SHEBEI_PEIZHI)
shigong_fangan.append(SHIGONG_FANGAN_ENUM.FANGLEI.YEPIAN.SHIGONG_FANGAN)
else:
pass
jiancha.append("人工防雷")
neirong.append(f"轮毂至塔基导通、内部导线线阻、外部导线线阻...")
neirong2.append("轮毂至塔基导通、内部导线线阻、外部导线线阻...")
#获取缺陷图列表和典型图列表
filtered_picture_data, total_picture_num = process_picture_data(picture_data, image_source_to_find)
#获取所有缺陷记录
defect_records = get_defect_record_list()
#获取缺陷记录中对应图片的记录
defect_records_with_pic, error_pic_with_no_record = get_records_with_pic(defect_records, filtered_picture_data, quexian_type)
if len(error_pic_with_no_record) > 0:
print(f"!!!有部分缺陷图片没有对应的缺陷记录,将不会生成这些图片的缺陷表:{error_pic_with_no_record} ")
print(f"对应缺陷图的缺陷记录列表:{defect_records_with_pic}")
except Exception as e:
print(f"报告基本信息获取失败:{e}")
return
#检查参数合法性
if not if_fanglei or not if_neibu or not if_waibu:
print("请至少选择一种检查项目")
return
if not os.path.exists(shengcheng_dir):
print(f"生成路径{shengcheng_dir}不存在")
return
output_doc = None
head_num = 1
###封面创建###
cover_dirs = [get_resource_path("muban/fengmian1.docx"),get_resource_path("muban/fengmian.jpg"),get_resource_path("muban/fengmian2.docx")]
#输出目录
baogao_name = "叶片" + "".join(baogao_label) + "检查报告"
output_dir = os.path.normpath(f"{shengcheng_dir}/{project_name}项目{baogao_name}{jizu_bianhao}{baogao_date.split(' ')[0]}版.docx")
version = 1
while os.path.exists(output_dir):
if version != 1:
output_dir = output_dir.replace(f"{version - 1}",f"{version}")
else:
output_dir = output_dir.replace("",f"{version}")
version += 1
mianzhe_shengming = f"本报告仅涵盖{''.join(baogao_label)}检测内容"
print(await create_document(output_dir))
change_heading(output_dir, "Heading 1", DT_EADING_1_CONFIG)
if baogao_info["if_docx_fengmian"] :
#创建文档、添加封面
print(add_documents(output_dir, cover_dirs[0]))
if check_pic_url(coverurl): #手动导入封面图片测试用
print(add_picture(output_dir, get_full_picture_url(coverurl), width = 6.41, height = 4))
elif check_pic_url(cover_url):
print(add_picture(output_dir, get_full_picture_url(cover_url), width = 6.41, height = 4))
else:
print(add_picture(output_dir, cover_dirs[1]))
print(add_documents(output_dir, cover_dirs[2]))
print("封面创建成功")
#YYYY年MM月DD日 HH:MM:SS
#更改文档信息
print(search_and_replace(output_dir, TITLE_OF_REPORT, jizu_bianhao))
print(search_and_replace(output_dir, baogao_name1, baogao_name))
print(search_and_replace(output_dir, company_name_yi, Yi_company))
print(search_and_replace(output_dir, cover_project, fengchang_name))
print(search_and_replace(output_dir, cover_encode, jizu_bianhao))
print(search_and_replace(output_dir, cover_date, date_year_month))
print(search_and_replace(output_dir, 'bianzhi', baogao_bianzhi))
print(search_and_replace(output_dir, 'shenghe', baogao_shenghe))
print(search_and_replace(output_dir, 'mianzhe_shengming', mianzhe_shengming))
add_header(output_dir, TEMPLATE_HEADER.DT_HEADER.ENUM)
total_table_num = 0
if baogao_info["if_docx_project_overview"]:
#项目概况表
print("开始添加项目概况表")
XIANG_MU_GAI_KUANG = get_resource_path("muban/xiangmugaikuo.docx")
print(f"查找模板,找到模板:{XIANG_MU_GAI_KUANG}")
project_location = fengchang_location
company_name_jia = Jia_company
fuzeren = yi_fuzeren
phone_fuzeren = yi_phone
xiangmuguige = jizu_num
Yi_company = Yi_company
XIANGMU_GAIKUO = list(list("" for i in range(6)) for j in range(5))
XIANGMU_GAIKUO[0][1] = fengchang_name
XIANGMU_GAIKUO[0][4] = project_location
XIANGMU_GAIKUO[1][1] = company_name_jia
XIANGMU_GAIKUO[1][4] = Yi_company
XIANGMU_GAIKUO[2][1] = jia_fuzeren
XIANGMU_GAIKUO[2][4] = fuzeren
XIANGMU_GAIKUO[3][2] = jia_phone
XIANGMU_GAIKUO[3][5] = phone_fuzeren
XIANGMU_GAIKUO[4][1] = jizu_xinghao
XIANGMU_GAIKUO[4][3] = xiangmuguige
XIANGMU_GAIKUO[4][5] = gongqi
print("建立表结构完毕,开始插入模板")
#添加项目概况表
print(f"输出路径:{output_dir},模板路径:{XIANG_MU_GAI_KUANG},插入数据:{XIANGMU_GAIKUO}")
print(add_heading(output_dir, f"一、项目概括", 1))
output_doc, message = add_table_to_document(output_dir, XIANG_MU_GAI_KUANG,5,5,total_table_num,XIANGMU_GAIKUO, if_para=False)
print(message)
print("模板插入完毕,开始替换内容")
total_table_num += 1
print(search_and_replace(output_dir, jiegou_xuhao, num_to_chinese[head_num]))
print(search_and_replace(output_dir, TITLE_OF_REPORT, jizu_bianhao))
print(search_and_replace(output_dir, baogao_name2, baogao_name))
head_num += 1
if baogao_info['if_docx_inspection_method']:
#检查方案描述
FANGAN_JIANCHA_DIR = get_resource_path("muban/checkmethod.docx")
list_to_replace = {
'renyuan_peizhi' : "\n".join(renyuan_peizhi),
'shebei_peizhi' : "\n".join(shebei_peizhi),
'shigong_fangan' : "\n".join(shigong_fangan),
'gongzuo_neirong' : "\n".join(gongzuo_neirong),
'beizhu' : beizhu,
'num' : num_to_chinese[head_num],
}
print(add_heading(output_dir, f"二、检查方案", 1))
print(add_table_and_replace(output_dir, FANGAN_JIANCHA_DIR, 0, list_to_replace, no_para=True))
print(split_table_by_row_content(output_dir, output_dir, total_table_num))
total_table_num += 1
head_num += 1
if baogao_info['if_docx_inspection_info']:
#检查信息
JIANCHA_XINGXI_DIR = get_resource_path("muban/checkinfo.docx")
JIANCHA_XINGXI = list(list("" for i in range(4)) for j in range(9))
JIANCHA_XINGXI[0][1] = jiancha_renyuan
try:
JIANCHA_XINGXI[1][1] = Jiancha_date.split('T')[0]
except:
JIANCHA_XINGXI[1][1] = "格式不对或无数据"
JIANCHA_XINGXI[1][3] = jizu_bianhao
JIANCHA_XINGXI[2][1] = "风力发电机组" + baogao_name
JIANCHA_XINGXI[2][3] = "".join(jiancha)
JIANCHA_XINGXI[3][2] = partManufacturer
JIANCHA_XINGXI[4][1] = '叶片型号:' + jizu_xinghao
JIANCHA_XINGXI[5][1] = Y_Code[0] if len(Y_Code) > 0 else ""
JIANCHA_XINGXI[6][1] = Y_Code[1] if len(Y_Code) > 1 else ""
JIANCHA_XINGXI[7][1] = Y_Code[2] if len(Y_Code) > 2 else ""
JIANCHA_XINGXI[8][0] = "本次" + "".join(_ for _ in jiancha) + f"检查,采集叶片图片{total_picture_num}张,内容覆盖" + ";".join(_ for _ in neirong)
#新建检查信息表
print(add_heading(output_dir, f"三、检查信息", 1))
output_doc, message = add_table_to_document(output_dir, JIANCHA_XINGXI_DIR,9,4,total_table_num ,JIANCHA_XINGXI,False,if_para=False)
print(message)
print(search_and_replace(output_dir, jiegou_xuhao, num_to_chinese[head_num]))
head_num += 1
total_table_num += 1
if baogao_info['if_docx_chengguo_sub']:
# 添加成果递交表
CHENGGUO_DIJIAO_DIR = get_resource_path("muban/chengguo_sub.docx")
CHENGGUO_DIJIAO = list(list("" for i in range(4)) for j in range(6))
CHENGGUO_DIJIAO[0][1] = jiancha_renyuan
CHENGGUO_DIJIAO[1][1] = jia_fuzeren
try:
CHENGGUO_DIJIAO[2][1] = Jiancha_date.split('T')[0]
except:
CHENGGUO_DIJIAO[2][1] = "格式不对或无数据"
CHENGGUO_DIJIAO[3][1] = data_processor
CHENGGUO_DIJIAO[4][1] = baogao_bianzhi
CHENGGUO_DIJIAO[5][1] = baogao_shenghe
try:
CHENGGUO_DIJIAO[2][3] = Jiancha_date.split('T')[1]
except:
CHENGGUO_DIJIAO[2][3] = "格式不对或无数据"
CHENGGUO_DIJIAO[3][3] = baogao_date.split(' ')[0]
CHENGGUO_DIJIAO[4][3] = baogao_date.split(' ')[0]
CHENGGUO_DIJIAO[5][3] = "未审核"
print(add_heading(output_dir, f"四、成果递交", 1))
output_doc, message = add_table_to_document(output_dir, CHENGGUO_DIJIAO_DIR,5,5,total_table_num,CHENGGUO_DIJIAO,True,0.04,if_para=False)
print(message)
print(search_and_replace(output_dir, jiegou_xuhao, num_to_chinese[head_num]))
head_num += 1
total_table_num += 1
if baogao_info['if_docx_inspection_text'] and if_waibu:
#检查情况汇总表(文字信息)
try:
#获取缺陷信息
"""
需要获取
Y1Y2Y3叶片的缺陷数量缺陷字典{描述图片路径}
和数据库连接需要的新增异常处理
目前逻辑为找到图片后找缺陷类型的图片通过图片id查找对应缺陷记录
如果没有找到要返回对应异常即已选出标注缺陷图但未填写对应缺陷信息的异常返回
最后将无异常的图片入队缺陷字典同数量有异常的图片再另存
"""
Y1_quexian_list = []
Y2_quexian_list = []
Y3_quexian_list = []
Y1_quexian_list = defect_records_with_pic["叶片1"]["DEFECT"] if "叶片1" in defect_records_with_pic else {}
Y2_quexian_list = defect_records_with_pic["叶片2"]["DEFECT"] if "叶片2" in defect_records_with_pic else {}
Y3_quexian_list = defect_records_with_pic["叶片3"]["DEFECT"] if "叶片3" in defect_records_with_pic else {}
Y1_quexian_num = len(Y1_quexian_list)
Y2_quexian_num = len(Y2_quexian_list)
Y3_quexian_num = len(Y3_quexian_list)
no_defect_found = "未发现明显缺陷"
weak_num_Y1 = f"{Y_Code[0] if len(Y_Code) > 0 else ''}叶片" + f"共发现缺陷{Y1_quexian_num}" if Y1_quexian_num > 0 else no_defect_found
weak_num_Y2 = f"{Y_Code[1] if len(Y_Code) > 1 else ''}叶片" + f"共发现缺陷{Y2_quexian_num}" if Y1_quexian_num > 0 else no_defect_found
weak_num_Y3 = f"{Y_Code[2] if len(Y_Code) > 2 else ''}叶片" + f"共发现缺陷{Y3_quexian_num}" if Y1_quexian_num > 0 else no_defect_found
except Exception as e:
print(f"缺陷图获取失败:{e}")
return
#添加检查情况汇总表
JIANCHA_HUIZONG_DIR = get_resource_path("muban/total_check.docx")
JIANCHA_HUIZONG = list(list("" for i in range(3)) for j in range(4))
JIANCHA_HUIZONG[1][0] = weak_num_Y1
JIANCHA_HUIZONG[2][0] = weak_num_Y2
JIANCHA_HUIZONG[3][0] = weak_num_Y3
JIANCHA_HUIZONG[1][1] = "\n".join(neirong2)
JIANCHA_HUIZONG[2][1] = "\n".join(neirong2)
JIANCHA_HUIZONG[3][1] = "\n".join(neirong2)
JIANCHA_HUIZONG[1][2] = "\n".join([f"{i+1}.{s}" for i, s in enumerate(get_defect_str(Y1_quexian_list))]) if Y1_quexian_num else '未发现明显影响风力发电机组正常运行的缺陷'
JIANCHA_HUIZONG[2][2] = "\n".join([f"{i+1}.{s}" for i, s in enumerate(get_defect_str(Y2_quexian_list))]) if Y2_quexian_num else '未发现明显影响风力发电机组正常运行的缺陷'
JIANCHA_HUIZONG[3][2] = "\n".join([f"{i+1}.{s}" for i, s in enumerate(get_defect_str(Y3_quexian_list))]) if Y3_quexian_num else '未发现明显影响风力发电机组正常运行的缺陷'
print(add_heading(output_dir, f"五、检查情况汇总 ", 1))
output_doc, message = add_table_to_document(output_dir, JIANCHA_HUIZONG_DIR,4,3,total_table_num,JIANCHA_HUIZONG,False,ALIGMENT='LEFT', if_para=False)
print(message)
print(search_and_replace(output_dir, jiegou_xuhao, num_to_chinese[head_num]))
total_table_num += 1
head_num += 1
if baogao_info['if_docx_inspection_picture']:
#主要部位图片展示表/检查内容表
#获取典型图信息
try:
Y1_list = safe_get(filtered_picture_data, "叶片1", dianxing_type, default=[])
Y2_list = safe_get(filtered_picture_data, "叶片2", dianxing_type, default=[])
Y3_list = safe_get(filtered_picture_data, "叶片3", dianxing_type, default=[])
picture_Y1_num = len(Y1_list)
picture_Y2_num = len(Y2_list)
picture_Y3_num = len(Y3_list)
except Exception as e:
print(f"获取缺陷图失败:{e}")
print(f"图片、文字数量:{picture_Y1_num} {picture_Y2_num} {picture_Y3_num}")
JIANCHA_NEIRONG_TOTAL_NUM = picture_Y1_num+ picture_Y2_num + picture_Y3_num
if JIANCHA_NEIRONG_TOTAL_NUM <= 0:
print("无典型图片数据,无法生成典型图表")
else:
print(add_heading(output_dir, f"六、检查内容", 1))
col ,row = 3, 0
JIANCHA_NEIRONG_PICTURES_TABLE = get_resource_path("muban/check2.docx")
JIANCHA_NEIRONG_Y1_DIR = get_resource_path("muban/check_content.docx")
JIANCHA_NEIRONG_Y1 = list(list("" for _ in range(3)) for j in range(1))
Y1_code = Y_Code[0] if len(Y_Code) > 0 else ""
Y2_code = Y_Code[1] if len(Y_Code) > 1 else ""
Y3_code = Y_Code[2] if len(Y_Code) > 2 else ""
JIANCHA_NEIRONG_Y1[0][0] = f"叶片1{Y1_code}检查内容"
print(f"Y1标题内容{JIANCHA_NEIRONG_Y1}")
JIANCHA_NEIRONG_Y2_DIR = get_resource_path("muban/check3.docx")
JIANCHA_NEIRONG_Y2 = list(list("" for _ in range(3)) for j in range(1))
JIANCHA_NEIRONG_Y2[0][0] = f"叶片2{Y2_code}检查内容"
print(f"Y2标题内容{JIANCHA_NEIRONG_Y2}")
JIANCHA_NEIRONG_Y3_DIR = get_resource_path("muban/check3.docx")
JIANCHA_NEIRONG_Y3 = list(list("" for _ in range(3)) for j in range(1))
JIANCHA_NEIRONG_Y3[0][0] = f"叶片3{Y3_code}检查内容"
print(f"Y3标题内容{JIANCHA_NEIRONG_Y3}")
print(f"当前表格序号为 {total_table_num}")
print(key_words)
output_doc, message = add_table_to_document(output_dir, JIANCHA_NEIRONG_Y1_DIR,1,3,total_table_num,JIANCHA_NEIRONG_Y1,True, 1,if_para=False)
print(message)
total_table_num += 1
total_table_num = await process_server_images_table(Y1_list, image_source_to_find, output_dir, total_table_num, JIANCHA_NEIRONG_PICTURES_TABLE, key_words)
output_doc, message = add_table_to_document(output_dir, JIANCHA_NEIRONG_Y2_DIR,1,3,total_table_num,JIANCHA_NEIRONG_Y2,True, 1)
print(message)
total_table_num += 1
total_table_num = await process_server_images_table(Y2_list, image_source_to_find, output_dir, total_table_num, JIANCHA_NEIRONG_PICTURES_TABLE, key_words)
output_doc, message = add_table_to_document(output_dir, JIANCHA_NEIRONG_Y3_DIR,1,3,total_table_num,JIANCHA_NEIRONG_Y3,True, 1)
print(message)
total_table_num += 1
total_table_num = await process_server_images_table(Y3_list, image_source_to_find, output_dir, total_table_num, JIANCHA_NEIRONG_PICTURES_TABLE, key_words)
print(search_and_replace(output_dir, jiegou_xuhao, num_to_chinese[head_num]))
head_num += 1
if baogao_info['if_docx_defect_picture'] and if_waibu:
# #缺陷详情
QUEXIAN_XIANGQING_DIR = get_resource_path("muban/check_check.docx")
QUEXIAN_XIANGQING_TITLE_DIR = get_resource_path("muban/check_check_title.docx")
Y_tables = [Y1_quexian_list,Y2_quexian_list,Y3_quexian_list]
Y1_table_list = []
Y2_table_list = []
Y3_table_list = []
table_lists = [Y1_table_list, Y2_table_list, Y3_table_list]
for i, (table_list, Y_lists) in enumerate(zip(table_lists, Y_tables)):
for Y_list in Y_lists:
image_defect_record = Y_list["record"]
image_path = Y_list["imagePath"]
image_defect_record = get_defect_detail(image_defect_record["defectId"])
# 从imagedefectrecord中获取各个字段
defect_type = image_defect_record.get('defectTypeLabel', '未知缺陷类型')
defect_location = f"{image_defect_record.get('partName', '')}{image_defect_record.get('defectPosition', '')}"
# 使用axial和chordwise作为缺陷尺寸信息
axial = image_defect_record.get('axial', '')
chordwise = image_defect_record.get('chordwise', '')
defect_size = f"轴向:{axial} 弦向:{chordwise}" if axial or chordwise else ''
visibility = "暂无此项"
urgency = "暂无此项"
severity = image_defect_record.get('defectLevelLabel', '未知严重等级')
repair_suggestion = image_defect_record.get('repairIdea', '无维修建议')
print(f"获取第{i + 1}个叶片的缺陷图: {image_path}")
table_list.append({
"QueXianLeiXing": defect_type,
"QueXianWeiZhi": defect_location,
"QueXianChiCun": defect_size,
"WeiZongDengJi": severity,
"Tupian_Dir": get_full_picture_url(image_path) if check_pic_url(image_path) else None,
"visibility": visibility,
"urgency": urgency,
"repair_suggestion": repair_suggestion, # 新增维修建议字段
})
# for i, (table_list, Y_dict) in enumerate(zip(table_lists, Y_tables)):
# for image_defect_record, image_path in Y_dict.items():
# # 从图片名解析各个字段
# parts = image_name.split('_')
# if len(parts) >= 8: # 确保有7个部分
# defect_type = parts[1]
# defect_location = parts[2]
# defect_size = parts[3]
# visibility = parts[4]
# urgency = parts[5]
# severity = parts[6]
# repair_suggestion = parts[7]
# print(f"获取第{i+1}个叶片的缺陷图: {image_path}")
# table_list.append({
# "QueXianLeiXing": defect_type,
# "QueXianWeiZhi": defect_location,
# "QueXianChiCun": defect_size,
# "WeiZongDengJi": severity,
# "Tupian_Dir": image_path,
# "visibility": visibility,
# "urgency": urgency,
# "repair_suggestion": repair_suggestion.split('.')[0], # 新增维修建议字段
# })
# else:
# table_list.append({
# "QueXianLeiXing": "图片命名有误",
# "QueXianWeiZhi": "图片命名有误",
# "QueXianChiCun": "图片命名有误",
# "WeiZongDengJi": "图片命名有误",
# "Tupian_Dir": image_path,
# "visibility": "图片命名有误",
# "urgency": "图片命名有误",
# "repair_suggestion": "图片命名有误", # 新增维修建议字段
# })
Y1_TABLES, Y1_TABLES_PICTURES = fill_tables(table_lists[0],4,5,len(table_lists[0]),Y_Code[0] if len(Y_Code) > 0 else "")
Y2_TABLES, Y2_TABLES_PICTURES = fill_tables(table_lists[1],4,5,len(table_lists[1]),Y_Code[1] if len(Y_Code) > 1 else "")
Y3_TABLES, Y3_TABLES_PICTURES = fill_tables(table_lists[2],4,5,len(table_lists[2]),Y_Code[2] if len(Y_Code) > 2 else "")
print(add_documents(output_dir, QUEXIAN_XIANGQING_TITLE_DIR))
print(search_and_replace(output_dir, jiegou_xuhao, num_to_chinese[head_num]))
head_num += 1
table_num = 0
Xu_Hao = 0
total_table_num,table_num,Xu_Hao = await add_dynamic_table(output_doc,output_dir,table_num,Y1_TABLES,QUEXIAN_XIANGQING_DIR,Y1_TABLES_PICTURES,4,5,total_table_num,False,xuhao=Xu_Hao)
total_table_num,table_num,Xu_Hao = await add_dynamic_table(output_doc,output_dir,table_num,Y2_TABLES,QUEXIAN_XIANGQING_DIR,Y2_TABLES_PICTURES,4,5,total_table_num,False,xuhao=Xu_Hao)
total_table_num,table_num,Xu_Hao = await add_dynamic_table(output_doc,output_dir,table_num,Y3_TABLES,QUEXIAN_XIANGQING_DIR,Y3_TABLES_PICTURES,4,5,total_table_num,False,xuhao=Xu_Hao)
if baogao_info['if_docx_conclusion']:
#总结
ZONG_JIE_DIR = get_resource_path("muban/result.docx")
ZONG_JIE_BEFORE = "result"
ZONG_JIE = baogao_info['conclusion']
print(add_documents(output_dir, ZONG_JIE_DIR))
print(search_and_replace(output_dir, ZONG_JIE_BEFORE, ZONG_JIE))
print(search_and_replace(output_dir, 'company_yi', Yi_company))
print(search_and_replace(output_dir, 'baogao_date', baogao_date.split(' ')[0]))
print(search_and_replace(output_dir, jiegou_xuhao, num_to_chinese[head_num]))

View File

@ -1,525 +1,38 @@
# 文档处理工具
from tools.document_tools import (
create_document, add_documents,add_table_and_replace,
add_table_to_document,process_images_table,
)
# 内容处理工具
from tools.content_tools import (
add_picture,split_table_by_row_content,
search_and_replace,add_picture_to_table
)
from tools.get_pictures import (
make_Thumbnail,resize_and_reduce_quality,
get_picture_nums,find_image,collect_defect_data,
process_picture_data
)
from tools.Get_Json import (
get_project_info,get_jizu_info,
get_jizu_shigong_info,get_weather,
get_part_picture,get_yepian_xiangqing
)
from tools.dataproccess import (
caculate_work_days,add_dynamic_table,
get_year_month,merge_info,
)
import asyncio
from core.tables import fill_tables
from tools.defines import *
import os, re, datetime
from pathlib import Path
async def generate_report(base_info, baogao_info):
#获取模板编号、模板名称
num_to_chinese = {1 : '', 2 : '', 3 : '', 4 : '', 5 : '', 6 : '', 7 : '', 8 : '', 9 : '', 10 : '', 11 : '十一', 12 : '十二'}
cover_encode = "encode"
cover_project = "project"
baogao_name1 = "baogaoname1"
baogao_name2 = "baogaoname2"
company_name_yi = "company_name_yi"
cover_date = "time"
TITLE_OF_REPORT = "companyencode"
jiegou_xuhao = 'num'
turbine_id = base_info['turbine_id']
jizu_data = get_jizu_info(turbine_id)
project_data = get_project_info(jizu_data['projectId'])
shigong_data = get_jizu_shigong_info(turbine_id)
try:
fengchang_name = project_data['farmName']
Yi_company = project_data['inspectionUnit']
yi_fuzeren = project_data['inspectionContact']
yi_phone = project_data['inspectionPhone']
fengchang_location = project_data['farmAddress']
Jia_company = project_data['client']
jia_fuzeren = project_data['clientContact']
jia_phone = project_data['clientPhone']
jizu_num = project_data['scale']
jizu_xinghao = project_data['turbineModel']
project_name = project_data['projectName']
jizu_bianhao = jizu_data["turbineName"]
start_date = project_data['startDate']
end_date = project_data['endDate']
gongqi = caculate_work_days(start_date, end_date)
except Exception as e:
print(f"数据库的项目-机组基本信息获取失败:{e}")
return
try:
baogao_date = datetime.datetime.now().strftime("%Y年%m月%d%H:%M") #现在的时间
date_year_month = get_year_month(baogao_date)
#前端信息
baogao_info = merge_info(baogao_info, DEFAULT_BAOGAO_INFO)
key_words= re.compile('|'.join(map(re.escape, baogao_info['key_words'].split(','))))
shengcheng_dir = baogao_info['shengcheng_dir']
muban_dir = baogao_info['muban_dir']
if muban_dir == "" or shengcheng_dir == "":
print("未配置模板/生成路径,请检查配置")
return
if_waibu = baogao_info["if_waibu"]
if_neibu = baogao_info["if_neibu"]
if_fanglei = baogao_info["if_fanglei"]
quexian_type = baogao_info['quexian_enum']
dianxing_type = baogao_info['dianxing_enum']
other_type = baogao_info["other_enum"]
#数据库拉取信息
Jiancha_date = shigong_data["startTime"].replace("T", " ") #检查日期
image_count = shigong_data['imageCount'] #从施工方案获取的图片数量,待定!!!
temperature = shigong_data['temperature'] #温度
wind_speed = shigong_data['windSpeed'] #风速
weather = get_weather(shigong_data["weatherCode"]) #天气 不从此接口获取,待定!!!
#拉取部件、图片数据
part_data, picture_data = get_part_picture(turbine_id)
#获取叶片信息
print(part_data)
Yepians = []
yepian_to_find = ["叶片1","叶片2","叶片3"]
#依次获取叶片123的信息未考虑多个同叶片的信息情况正常情况下不会发生
for name in yepian_to_find:
# 找到第一个匹配的部件并添加到列表中
for part in part_data:
if part['partName'] == name:
Yepians.append(part)
print(Yepians)
Y1_info = get_yepian_xiangqing(Yepians[0]["partId"])
Y_Code = [yepian["partCode"] for yepian in Yepians]
partManufacturer = Y1_info["partManufacturer"]
print(f"找到叶片号{Y_Code},厂商{partManufacturer}")
image_source_to_find = []
baogao_label = []
renyuan_peizhi = []
gongzuo_neirong = []
shigong_fangan = []
shebei_peizhi = []
beizhu = []
jiancha = []
neirong = []
#获取对应枚举字段
if if_waibu:
baogao_label.append("外观")
image_source_to_find.append(baogao_info['waibu_enum'])
if baogao_info["shigong_fangan"] == "None":
print("未传入施工方案,使用已有枚举")
renyuan_peizhi.append(SHIGONG_FANGAN_ENUM.WAIBU.RENYUAN_PEIZHI)
gongzuo_neirong.append(SHIGONG_FANGAN_ENUM.WAIBU.GONGZUO_NEIRONG)
shebei_peizhi.append(SHIGONG_FANGAN_ENUM.WAIBU.SHEBEI_PEIZHI)
shigong_fangan.append(SHIGONG_FANGAN_ENUM.WAIBU.SHIGONG_FANGAN)
else:
pass #待添加如果从平台传入枚举,但目前没必要,如果这种枚举标准信息库有更好的优化则可以实现
jiancha.append("无人机近距离外观检查")
neirong.append(f"".join(Y_Code) + f"{len(Y_Code)}支叶片的前缘、后缘、迎风面、背风面。")
if if_neibu:
baogao_label.append("内部")
image_source_to_find.append(baogao_info['neibu_enum'])
if baogao_info["shigong_fangan"] == "None":
renyuan_peizhi.append(SHIGONG_FANGAN_ENUM.NEIBU.RENYUAN_PEIZHI)
gongzuo_neirong.append(SHIGONG_FANGAN_ENUM.NEIBU.GONGZUO_NEIRONG)
shebei_peizhi.append(SHIGONG_FANGAN_ENUM.NEIBU.SHEBEI_PEIZHI)
shigong_fangan.append(SHIGONG_FANGAN_ENUM.NEIBU.SHIGONG_FANGAN)
else:
pass
jiancha.append("人工内部拍摄")
neirong.append(f"".join(Y_Code) + f"{len(Y_Code)}支叶片的内部导雷卡、腹板、透光、人孔盖版、叶根盖板...")
if if_fanglei:
baogao_label.append("防雷")
image_source_to_find.append(baogao_info['fanglei_enum'])
if baogao_info["shigong_fangan"] == "None":
renyuan_peizhi.append(SHIGONG_FANGAN_ENUM.FANGLEI.YEPIAN.RENYUAN_PEIZHI)
gongzuo_neirong.append(SHIGONG_FANGAN_ENUM.FANGLEI.YEPIAN.GONGZUO_NEIRONG)
shebei_peizhi.append(SHIGONG_FANGAN_ENUM.FANGLEI.YEPIAN.SHEBEI_PEIZHI)
shigong_fangan.append(SHIGONG_FANGAN_ENUM.FANGLEI.YEPIAN.SHIGONG_FANGAN)
else:
pass
jiancha.append("人工防雷")
neirong.append(f"轮毂至塔基导通、内部导线线阻、外部导线线阻...")
#获取缺陷图列表和典型图列表
(defect_pictures, typical_pictures,
other_pictures, normal_picture_num) = process_picture_data(picture_data, image_source_to_find,
quexian_type, dianxing_type, other_type)
print(f"\n\n\n缺陷图片列表:{defect_pictures}\n\n\n典型图片列表:{typical_pictures}\n\n\n")
#处理图片数据待完成,图片绑定施工后
defect_pictures_id = [pic for pic in defect_pictures]
baogao_bianzhi = baogao_info["userName"]
baogao_shenghe = baogao_info["baogaoCheck"]
except Exception as e:
print(f"报告基本信息获取失败:{e}")
return
output_doc = None
head_num = 1
###封面创建###
cover_dirs = [os.path.join(muban_dir,"fengmian1.docx"),os.path.join(muban_dir,"fengmian.jpg"),os.path.join(muban_dir,"fengmian2.docx")]
#输出目录
baogao_name = "叶片" + "".join(baogao_label) + "检查报告"
output_dir = os.path.normpath(f"{shengcheng_dir}/{project_name}项目{baogao_name}{jizu_bianhao}{baogao_date.split(' ')[0]}版.docx")
version = 1
while os.path.exists(output_dir):
if version != 1:
output_dir = output_dir.replace(f"{version - 1}",f"{version}")
else:
output_dir = output_dir.replace("",f"{version}")
version += 1
mianzhe_shengming = f"本报告仅涵盖{''.join(baogao_label)}检测内容"
#创建文档、添加封面
print(await create_document(output_dir))
print(add_documents(output_dir, cover_dirs[0]))
print(await add_picture(output_dir, cover_dirs[1]))
print(add_documents(output_dir, cover_dirs[2]))
print("封面创建成功")
#更改文档信息
print(await search_and_replace(output_dir, TITLE_OF_REPORT, jizu_bianhao))
print(await search_and_replace(output_dir, baogao_name1, baogao_name))
print(await search_and_replace(output_dir, baogao_name2, baogao_name))
print(await search_and_replace(output_dir, company_name_yi, Yi_company))
print(await search_and_replace(output_dir, cover_project, fengchang_name))
print(await search_and_replace(output_dir, cover_encode, jizu_bianhao))
print(await search_and_replace(output_dir, cover_date, date_year_month))
print(await search_and_replace(output_dir, 'bianzhi', baogao_bianzhi))
print(await search_and_replace(output_dir, 'shenghe', baogao_shenghe))
print(await search_and_replace(output_dir, 'mianzhe_shengming', mianzhe_shengming))
total_table_num = 0
#项目概况表
print("开始添加项目概况表")
XIANG_MU_GAI_KUANG = os.path.join(muban_dir,"xiangmugaikuo.docx")
print(f"查找模板,找到模板:{XIANG_MU_GAI_KUANG}")
project_location = fengchang_location
company_name_jia = Jia_company
fuzeren = yi_fuzeren
phone_fuzeren = yi_phone
xiangmuguige = jizu_num
Yi_company = Yi_company
XIANGMU_GAIKUO = list(list("" for i in range(6)) for j in range(5))
XIANGMU_GAIKUO[0][1] = fengchang_name
#XIANGMU_GAIKUO[0][3]=XIANGMU_GAIKUO[0][4] = "盐城市滨海县"
XIANGMU_GAIKUO[0][4] = project_location
#XIANGMU_GAIKUO[1][1]=XIANGMU_GAIKUO[2,1]=XIANGMU_GAIKUO[3,1] = "国家电投集团滨海风力发电有限公司"
XIANGMU_GAIKUO[1][1] = company_name_jia
XIANGMU_GAIKUO[1][4] = Yi_company
XIANGMU_GAIKUO[2][1] = jia_fuzeren
XIANGMU_GAIKUO[2][4] = fuzeren
XIANGMU_GAIKUO[3][2] = jia_phone
XIANGMU_GAIKUO[3][5] = phone_fuzeren
XIANGMU_GAIKUO[4][1] = jizu_xinghao
XIANGMU_GAIKUO[4][3] = xiangmuguige
XIANGMU_GAIKUO[4][5] = gongqi
print("建立表结构完毕,开始插入模板")
#添加项目概况表
print(f"输出路径:{output_dir},模板路径:{XIANG_MU_GAI_KUANG},插入数据:{XIANGMU_GAIKUO}")
output_doc, message = await add_table_to_document(output_dir, XIANG_MU_GAI_KUANG,5,5,total_table_num,XIANGMU_GAIKUO)
print(message)
print("模板插入完毕,开始替换内容")
total_table_num += 1
print(await search_and_replace(output_dir, jiegou_xuhao, num_to_chinese[head_num]))
head_num += 1
#检查方案描述
FANGAN_JIANCHA_DIR = os.path.join(muban_dir,"checkmethod.docx")
list_to_replace = {
'renyuan_peizhi' : "\n".join(renyuan_peizhi),
'shebei_peizhi' : "\n".join(shebei_peizhi),
'shigong_fangan' : "\n".join(shigong_fangan),
'gongzuo_neirong' : "\n".join(gongzuo_neirong),
'beizhu' : beizhu,
'num' : num_to_chinese[head_num],
}
print(await add_table_and_replace(output_dir, FANGAN_JIANCHA_DIR, 0, list_to_replace))
print(split_table_by_row_content(output_dir, output_dir, total_table_num))
total_table_num += 1
head_num += 1
JIANCHA_XINGXI_DIR = os.path.join(muban_dir,"checkinfo.docx")
JIANCHA_XINGXI = list(list("" for i in range(4)) for j in range(9))
JIANCHA_XINGXI[0][1] = ""#检查人员,需要完善
JIANCHA_XINGXI[1][1] = Jiancha_date.split(' ')[0]
JIANCHA_XINGXI[1][3] = jizu_bianhao
JIANCHA_XINGXI[2][1] = "风力发电机组" + baogao_name
JIANCHA_XINGXI[2][3] = "".join(jiancha)
JIANCHA_XINGXI[3][2] = partManufacturer
JIANCHA_XINGXI[4][1] = '叶片型号:' + jizu_xinghao
JIANCHA_XINGXI[5][1] = Y_Code[0] if len(Y_Code) > 0 else ""
JIANCHA_XINGXI[6][1] = Y_Code[1] if len(Y_Code) > 1 else ""
JIANCHA_XINGXI[7][1] = Y_Code[2] if len(Y_Code) > 2 else ""
JIANCHA_XINGXI[8][0] = "本次" + "".join(_ for _ in jiancha) + f"检查,采集叶片图片{normal_picture_num}张,内容覆盖" + ";".join(_ for _ in neirong)
#新建检查信息表
output_doc, message = await add_table_to_document(output_dir, JIANCHA_XINGXI_DIR,9,4,total_table_num ,JIANCHA_XINGXI,False)
print(message)
print(await search_and_replace(output_dir, jiegou_xuhao, num_to_chinese[head_num]))
head_num += 1
total_table_num += 1
# 添加成果递交表
CHENGGUO_DIJIAO_DIR = os.path.join(muban_dir,"chengguo_sub.docx")
CHENGGUO_DIJIAO = list(list("" for i in range(4)) for j in range(6))
CHENGGUO_DIJIAO[0][1] = "" #Jiancha_renyuan
CHENGGUO_DIJIAO[1][1] = jia_fuzeren
CHENGGUO_DIJIAO[2][1] = Jiancha_date.split(' ')[0]
CHENGGUO_DIJIAO[3][1] = "" #data_process
CHENGGUO_DIJIAO[4][1] = baogao_bianzhi
CHENGGUO_DIJIAO[5][1] = baogao_shenghe
CHENGGUO_DIJIAO[2][3] = Jiancha_date.split(' ')[1]
CHENGGUO_DIJIAO[3][3] = baogao_date.split(' ')[0]
CHENGGUO_DIJIAO[4][3] = baogao_date.split(' ')[0]
CHENGGUO_DIJIAO[5][3] = "" #shenghe_date.split(' ')[0]
output_doc, message = await add_table_to_document(output_dir, CHENGGUO_DIJIAO_DIR,5,5,total_table_num,CHENGGUO_DIJIAO,True,0.04)
print(message)
print(await search_and_replace(output_dir, jiegou_xuhao, num_to_chinese[head_num]))
head_num += 1
total_table_num += 1
#检查情况汇总表(文字信息)
try:
#获取缺陷信息
"""
需要获取
Y1Y2Y3叶片的缺陷数量缺陷字典{图片名图片路径}
和数据库连接需要的新增异常处理
目前逻辑为找到图片后找缺陷类型的图片通过图片id查找对应缺陷记录
如果没有找到要返回对应异常即已选出标注缺陷图但未填写对应缺陷信息的异常返回
最后将无异常的图片入队缺陷字典同数量有异常的图片再另存
"""
Y1_quexian_num, Y1_quexian_dict = 0, {}
Y2_quexian_num, Y2_quexian_dict = 0, {}
Y3_quexian_num, Y3_quexian_dict = 0, {}
weak_num_Y1 = f"{Y_Code[0] if len(Y_Code) > 0 else ''}叶片共发现缺陷{Y1_quexian_num}"
weak_num_Y2 = f"{Y_Code[1] if len(Y_Code) > 1 else ''}叶片共发现缺陷{Y2_quexian_num}"
weak_num_Y3 = f"{Y_Code[2] if len(Y_Code) > 2 else ''}叶片共发现缺陷{Y3_quexian_num}"
except Exception as e:
print(f"缺陷图获取失败:{e}")
return
#添加检查情况汇总表
JIANCHA_HUIZONG_DIR = os.path.join(muban_dir,"total_check.docx")
JIANCHA_HUIZONG = list(list("" for i in range(3)) for j in range(4))
JIANCHA_HUIZONG[1][0] = weak_num_Y1
JIANCHA_HUIZONG[2][0] = weak_num_Y2
JIANCHA_HUIZONG[3][0] = weak_num_Y3
JIANCHA_HUIZONG[1][1] = ""#Y1_jiancha
JIANCHA_HUIZONG[2][1] = ""#Y2_jiancha
JIANCHA_HUIZONG[3][1] = ""#Y3_jiancha
JIANCHA_HUIZONG[1][2] = "/n".join([f"{i+1}.{name}" for i, (name, path) in enumerate(Y1_quexian_dict.items())]) if Y1_quexian_num else '未发现明显影响风力发电机组正常运行的缺陷'
JIANCHA_HUIZONG[2][2] = "/n".join([f"{i+1}.{name}" for i, (name, path) in enumerate(Y2_quexian_dict.items())]) if Y2_quexian_num else '未发现明显影响风力发电机组正常运行的缺陷'
JIANCHA_HUIZONG[3][2] = "/n".join([f"{i+1}.{name}" for i, (name, path) in enumerate(Y3_quexian_dict.items())]) if Y3_quexian_num else '未发现明显影响风力发电机组正常运行的缺陷'
output_doc, message = await add_table_to_document(output_dir, JIANCHA_HUIZONG_DIR,4,3,total_table_num,JIANCHA_HUIZONG,False,ALIGMENT='LEFT')
print(message)
print(await search_and_replace(output_dir, jiegou_xuhao, num_to_chinese[head_num]))
total_table_num += 1
head_num += 1
#主要部位图片展示表/检查内容表
#获取典型图信息
search_file_list = ["外汇总","内汇总","防汇总"]
try:
picture_Y1_num, Y1_dict = collect_defect_data(Y1, Picture_dir, ifwaibu, ifneibu, search_file_list, iffanglei)
picture_Y2_num, Y2_dict = collect_defect_data(Y2, Picture_dir, ifwaibu, ifneibu, search_file_list, iffanglei)
picture_Y3_num, Y3_dict = collect_defect_data(Y3, Picture_dir, ifwaibu, ifneibu, search_file_list, iffanglei)
except Exception as e:
print(f"获取缺陷图失败:{e}")
print(f"图片、文字数量:{picture_Y1_num} {picture_Y2_num} {picture_Y3_num}")
JIANCHA_NEIRONG_TOTAL_NUM = picture_Y1_num+ picture_Y2_num + picture_Y3_num
col ,row = 3, 0
JIANCHA_NEIRONG_PICTURES_TABLE = os.path.join(muban_dir,"check2.docx")
JIANCHA_NEIRONG_Y1_DIR = os.path.join(muban_dir,"check_content.docx")
JIANCHA_NEIRONG_Y1 = list(list("" for _ in range(3)) for j in range(1))
JIANCHA_NEIRONG_Y1[0][0] = f"叶片1{Y1}检查内容"
print(f"Y1标题内容{JIANCHA_NEIRONG_Y1}")
JIANCHA_NEIRONG_Y2_DIR = os.path.join(muban_dir,"check3.docx")
JIANCHA_NEIRONG_Y2 = list(list("" for _ in range(3)) for j in range(1))
JIANCHA_NEIRONG_Y2[0][0] = f"叶片2{Y2}检查内容"
print(f"Y2标题内容{JIANCHA_NEIRONG_Y2}")
JIANCHA_NEIRONG_Y3_DIR = os.path.join(muban_dir,"check3.docx")
JIANCHA_NEIRONG_Y3 = list(list("" for _ in range(3)) for j in range(1))
JIANCHA_NEIRONG_Y3[0][0] = f"叶片3{Y3}检查内容"
print(f"Y3标题内容{JIANCHA_NEIRONG_Y3}")
print(f"当前表格序号为 {total_table_num}")
print(key_words)
output_doc, message = await add_table_to_document(output_dir, JIANCHA_NEIRONG_Y1_DIR,1,3,total_table_num,JIANCHA_NEIRONG_Y1,True, 1)
print(message)
total_table_num += 1
total_table_num = await process_images_table(Y1_dict, output_dir, total_table_num, JIANCHA_NEIRONG_PICTURES_TABLE, key_words)
output_doc, message = await add_table_to_document(output_dir, JIANCHA_NEIRONG_Y2_DIR,1,3,total_table_num,JIANCHA_NEIRONG_Y2,True, 1)
print(message)
total_table_num += 1
total_table_num = await process_images_table(Y2_dict, output_dir, total_table_num, JIANCHA_NEIRONG_PICTURES_TABLE, key_words)
output_doc, message = await add_table_to_document(output_dir, JIANCHA_NEIRONG_Y3_DIR,1,3,total_table_num,JIANCHA_NEIRONG_Y3,True, 1)
print(message)
total_table_num += 1
total_table_num = await process_images_table(Y3_dict, output_dir, total_table_num, JIANCHA_NEIRONG_PICTURES_TABLE, key_words)
print(await search_and_replace(output_dir, jiegou_xuhao, num_to_chinese[head_num]))
head_num += 1
# #缺陷详情
# QUEXIAN_XIANGQING_DIR = os.path.join(muban_dir,"check_check.docx")
# QUEXIAN_XIANGQING_TITLE_DIR = os.path.join(muban_dir,"check_check_title.docx")
# Y_tables = [Y1_quexian_dict,Y2_quexian_dict,Y3_quexian_dict]
# Y1_table_list = []
# Y2_table_list = []
# Y3_table_list = []
# table_lists = [Y1_table_list, Y2_table_list, Y3_table_list]
# for i, (table_list, Y_dict) in enumerate(zip(table_lists, Y_tables)):
# for image_name, image_path in Y_dict.items():
# # 从图片名解析各个字段
# parts = image_name.split('_')
# if len(parts) >= 8: # 确保有7个部分
# defect_type = parts[1]
# defect_location = parts[2]
# defect_size = parts[3]
# visibility = parts[4]
# urgency = parts[5]
# severity = parts[6]
# repair_suggestion = parts[7]
# print(f"获取第{i+1}个叶片的缺陷图: {image_path}")
# table_list.append({
# "QueXianLeiXing": defect_type,
# "QueXianWeiZhi": defect_location,
# "QueXianChiCun": defect_size,
# "WeiZongDengJi": severity,
# "Tupian_Dir": image_path,
# "visibility": visibility,
# "urgency": urgency,
# "repair_suggestion": repair_suggestion.split('.')[0], # 新增维修建议字段
# })
# else:
# table_list.append({
# "QueXianLeiXing": "图片命名有误",
# "QueXianWeiZhi": "图片命名有误",
# "QueXianChiCun": "图片命名有误",
# "WeiZongDengJi": "图片命名有误",
# "Tupian_Dir": image_path,
# "visibility": "图片命名有误",
# "urgency": "图片命名有误",
# "repair_suggestion": "图片命名有误", # 新增维修建议字段
# })
# Y1_TABLES, Y1_TABLES_PICTURES = fill_tables(table_lists[0],4,5,len(table_lists[0]),Y1)
# Y2_TABLES, Y2_TABLES_PICTURES = fill_tables(table_lists[1],4,5,len(table_lists[1]),Y2)
# Y3_TABLES, Y3_TABLES_PICTURES = fill_tables(table_lists[2],4,5,len(table_lists[2]),Y3)
# print(add_documents(output_dir, QUEXIAN_XIANGQING_TITLE_DIR))
# print(await search_and_replace(output_dir, jiegou_xuhao, num_to_chinese[head_num]))
# head_num += 1
# table_num = 0
# Xu_Hao = 0
# total_table_num,table_num,Xu_Hao = await add_dynamic_table(output_doc,output_dir,table_num,Y1_TABLES,QUEXIAN_XIANGQING_DIR,Y1_TABLES_PICTURES,4,5,total_table_num,False,xuhao=Xu_Hao)
# total_table_num,table_num,Xu_Hao = await add_dynamic_table(output_doc,output_dir,table_num,Y2_TABLES,QUEXIAN_XIANGQING_DIR,Y2_TABLES_PICTURES,4,5,total_table_num,False,xuhao=Xu_Hao)
# total_table_num,table_num,Xu_Hao = await add_dynamic_table(output_doc,output_dir,table_num,Y3_TABLES,QUEXIAN_XIANGQING_DIR,Y3_TABLES_PICTURES,4,5,total_table_num,False,xuhao=Xu_Hao)
#总结
ZONG_JIE_DIR = os.path.join(muban_dir,"result.docx")
ZONG_JIE_BEFORE = "result"
ZONG_JIE = baogao_info['baogao_zongjie']
print(add_documents(output_dir, ZONG_JIE_DIR))
print(await search_and_replace(output_dir, ZONG_JIE_BEFORE, ZONG_JIE))
print(await search_and_replace(output_dir, 'company_yi', Yi_company))
print(await search_and_replace(output_dir, 'baogao_date', baogao_date.split(' ')[0]))
print(await search_and_replace(output_dir, jiegou_xuhao, num_to_chinese[head_num]))
from tools.argtool import (load_config, parse_arguments, get_default_config,
merge_configs)
from Dt_report import generate_dt_report
from Jf_report import generate_jf_report
def main():
json_data1 = {
"turbine_id" : "183463dbf40d9278549a76b82b175dd9",
}
json_data2 = {
'shengcheng_dir': r".\output",
'muban_dir': r".\muban",
# "shigong_fangan_enum" : { #可能放入标准信息库?但不论如何,修改了这个枚举的话,报告生成逻辑也要修改,待优化!
# "SHIGONG_FANGAN_ENUM": {
# "WAIBU": {
# "GONGZUO_NEIRONG": "无人机叶片外观巡检",
# "RENYUAN_PEIZHI": "1人主检飞手1人",
# "SHEBEI_PEIZHI": "1、大疆无人机1台M350rtkM300rtkM30TM30精灵4PRO2、大疆精灵4PRO+索尼A7R2机身+索尼200-600mm镜头/适马150-600mm镜头",
# "SHIGONG_FANGAN": None
# },
# "NEIBU": {
# "GONGZUO_NEIRONG": "叶片内部检查",
# "RENYUAN_PEIZHI": "2人轮毂作业检查2人",
# "SHEBEI_PEIZHI": "1、人工检查照明设备2套视频记录手机2台含氧量监测仪1台电动扳手2套卷尺1个。2、爬壁机器人检查无人作业校车+视频图传1套照明设备2套含氧量监测仪1台电动扳手2套卷尺1个。",
# "SHIGONG_FANGAN": None
# },
# "FANGLEI": {
# "YEPIAN": {
# "GONGZUO_NEIRONG": "无人机叶片防雷导通测试",
# "RENYUAN_PEIZHI": "2人主检飞手1人副检抄表1人",
# "SHEBEI_PEIZHI": "1四轴电阻无人机1套电子微欧计1台视频记录手机1台",
# "SHIGONG_FANGAN": None
# },
# "DIAOLAN": {
# "GONGZUO_NEIRONG": "无人吊篮叶片导通测试(含机舱设备、)",
# "RENYUAN_PEIZHI": "3人轮毂机舱作业1人揽风绳作业1人无人设备操作员及抄表1人",
# "SHEBEI_PEIZHI": "无人吊篮系统1套爬绳器+接触平台、电子微欧计1套视频记录手机1台对讲机2台",
# "SHIGONG_FANGAN": None
# },
# "SHESHI": {
# "GONGZUO_NEIRONG": "风机基础、办公楼、变电站防雷接地检测及浪涌保护器测试",
# "RENYUAN_PEIZHI": "1人抄表人员1人检测人员1人监护1人。",
# "SHEBEI_PEIZHI": "接地电阻测试仪1套、SPD测试仪1套、对讲机2个、",
# "SHIGONG_FANGAN": None
# },
# "FEISHOURENYUAN_PEIZHI": "1人主检飞手1人",
# "LUNGUZUOYERENYUAN_PEIZHI": "2人轮毂作业检查2人"
# }
# }
# },
}
asyncio.run(generate_report(json_data1,json_data2))
args = parse_arguments()
default_cfg = get_default_config()
# 读取配置文件(若存在)
file_cfg = {}
if Path(args.config_file).exists():
try:
file_cfg = load_config(args.config_file)
except Exception as e:
print(f'警告:加载配置文件失败 {e},使用默认配置')
merged = merge_configs(default_cfg, file_cfg, args)
# 补全必要目录
if not merged['json2']['shengcheng_dir']:
merged['json2']['shengcheng_dir'] = str(Path.cwd() / 'output')
Path(merged['json2']['shengcheng_dir']).mkdir(parents=True, exist_ok=True)
if merged['json2']['choose_template'] == 'DT':
asyncio.run(generate_dt_report(merged['json1'], merged['json2']))
elif merged['json2']['choose_template'] == 'JF':
asyncio.run(generate_jf_report(merged['json1'], merged['json2']))
else:
print('指定了不存在的模板,请检查配置文件')
print('文档生成完毕')
if __name__ == '__main__':
main()

425
Jf_report.py Normal file
View File

@ -0,0 +1,425 @@
# 文档处理工具
from tools.document_tools import (
create_document, add_documents,add_table_and_replace,
add_table_to_document,add_dynamic_table,add_section,
process_server_images_table,add_header,add_landscape_section,
merge_documents,add_table,add_defect_info_table,add_table_title,
add_title,change_heading,add_auto_toc_at_end,add_jf_picture_table
)
# 内容处理工具
from tools.content_tools import (
search_and_replace,add_heading
)
from tools.get_pictures import (
process_picture_data,get_records_with_pic
)
from tools.Get_Json import (
get_project_info,get_jizu_info,
get_jizu_shigong_info,get_defect_detail,
get_part_picture,get_yepian_xiangqing,
check_pic_url,get_full_picture_url,
get_defect_record_list
)
from tools.dataproccess import (
caculate_work_days,get_year_month,
merge_info,get_defect_str,get_defect_json,
safe_get,get_resource_path,get_defedct_info,
tree_dict_to_table_data,merge_dicts,
defect_list_addtitle
)
from tools.json_to_docx import json_to_docx, list_to_json_with_merges
from core.tables import fill_tables
from tools.defines import *
import os, re, datetime
async def generate_jf_report(base_info, baogao_info):
#获取模板编号、模板名称
num_to_chinese = {1 : '', 2 : '', 3 : '', 4 : '', 5 : '', 6 : '', 7 : '', 8 : '', 9 : '', 10 : '', 11 : '十一', 12 : '十二'}
cover_encode = "encode"
cover_project = "project"
baogao_name1 = "baogaoname1"
baogao_name2 = "baogaoname2"
company_name_yi = "company_name_yi"
cover_date = "time"
TITLE_OF_REPORT = "companyencode"
jiegou_xuhao = 'num'
print(f"获取到参数:基本信息:{base_info}\n\n报告信息:{baogao_info}")
try:
base_info = merge_info(base_info, DEFAULT_BASE_INFO)
turbine_id = base_info['turbine_id']
jizu_data = get_jizu_info(turbine_id)
project_data = get_project_info(jizu_data['projectId'])
shigong_data = get_jizu_shigong_info(turbine_id)
fengchang_name = project_data['farmName']
Yi_company = project_data['inspectionUnit']
yi_fuzeren = project_data['inspectionContact']
yi_phone = project_data['inspectionPhone']
fengchang_location = project_data['farmAddress']
Jia_company = project_data['client']
jia_fuzeren = project_data['clientContact']
jia_phone = project_data['clientPhone']
jizu_num = project_data['scale']
jizu_xinghao = project_data['turbineModel']
jizu_manufacture = project_data.get('turbineManufacturer', "")
project_name = project_data['projectName']
jizu_bianhao = jizu_data["turbineName"]
start_date = project_data['startDate']
end_date = project_data['endDate']
cover_url = project_data['coverUrl']
gongqi = caculate_work_days(start_date, end_date)
except Exception as e:
print(f"数据库的项目-机组基本信息获取失败:{e}")
return
try:
baogao_date = datetime.datetime.now().strftime("%Y年%m月%d%H:%M") #现在的时间
date_year_month = get_year_month(baogao_date)
#前端信息
baogao_info = merge_info(baogao_info, DEFAULT_BAOGAO_INFO)
key_words= re.compile('|'.join(map(re.escape, baogao_info['key_words'].split(',')))) #关键字
shengcheng_dir = baogao_info['shengcheng_dir'] #路径
if shengcheng_dir == "":
print("未配置生成路径,请检查配置")
return
if_waibu = baogao_info["if_waibu"]
if_neibu = baogao_info["if_neibu"]
if_fanglei = baogao_info["if_fanglei"]
quexian_type = baogao_info['quexian_enum']
dianxing_type = baogao_info['dianxing_enum']
other_type = baogao_info["other_enum"]
jiancha_renyuan = baogao_info['jiancha_renyuan'] #检查人员,目前是从命令行参数获取,需要完善
check_date = baogao_info['check_date']
if check_date == None:
check_date = "未获取"
baogao_bianzhi = baogao_info["userName"]
baogao_shenghe = baogao_info["baogaoCheck"]
Jiancha_date = baogao_info["check_date"]
data_processor = baogao_info["data_processor"]
coverurl = baogao_info["coverurl"]
#数据库拉取信息
# Jiancha_date = shigong_data["startTime"].replace("T", " ") #检查日期
# image_count = shigong_data['imageCount'] #从施工方案获取的图片数量,待定!!!
# temperature = shigong_data['temperature'] #温度
# wind_speed = shigong_data['windSpeed'] #风速
# weather = get_weather(shigong_data["weatherCode"]) #天气 不从此接口获取,待定!!!
#拉取部件、图片数据
part_data, picture_data1, picture_data2, Yepians = get_part_picture(turbine_id)
picture_data = merge_dicts(picture_data1, picture_data2)
print(Yepians)
Y1_info = get_yepian_xiangqing(Yepians[0]["partId"])
Y_Code = [yepian["partCode"] for yepian in Yepians]
partType = Y1_info["partType"]
partManufacturer = Y1_info["partManufacturer"]
print(f"找到叶片号{Y_Code},厂商{partManufacturer}")
image_source_to_find = []
baogao_label = []
renyuan_peizhi = []
gongzuo_neirong = []
shigong_fangan = []
shebei_peizhi = []
beizhu = []
jiancha = []
neirong = []
neirong2 = []
use_tool_table = []
table_index = 1
typical_picture_description = []
#获取对应枚举字段
if if_waibu:
baogao_label.append("外观")
image_source_to_find.append(baogao_info['waibu_enum'])
use_tool_table.extend(
[ [str(idx)] + item
for idx, item in enumerate(USE_TOOL_ENUM.OUT.LIST, start=table_index) ]
)
table_index += len(USE_TOOL_ENUM.OUT.LIST)
if baogao_info["shigong_fangan"] == None:
print("未传入施工方案,使用已有枚举")
renyuan_peizhi.append(SHIGONG_FANGAN_ENUM.WAIBU.RENYUAN_PEIZHI)
gongzuo_neirong.append(SHIGONG_FANGAN_ENUM.WAIBU.GONGZUO_NEIRONG)
shebei_peizhi.append(SHIGONG_FANGAN_ENUM.WAIBU.SHEBEI_PEIZHI)
shigong_fangan.append(SHIGONG_FANGAN_ENUM.WAIBU.SHIGONG_FANGAN)
else:
pass #待添加如果从平台传入枚举,但目前没必要,如果这种枚举标准信息库有更好的优化则可以实现
jiancha.append("无人机外观检查")
neirong.append(f"".join(Y_Code) + f"{len(Y_Code)}支叶片的前缘、后缘、迎风面、背风面。")
neirong2.append("前缘、后缘、迎风面、背风面。")
typical_picture_description.extend(TEMPLATE_HEADER.JINFENG_HEADER.WAIBU.TYPICAL_LIST)
if if_neibu:
baogao_label.append("内部")
image_source_to_find.append(baogao_info['neibu_enum'])
use_tool_table.extend(
[ [str(idx)] + item
for idx, item in enumerate(USE_TOOL_ENUM.IN.LIST, start=table_index) ]
)
table_index += len(USE_TOOL_ENUM.IN.LIST)
if baogao_info["shigong_fangan"] == None:
renyuan_peizhi.append(SHIGONG_FANGAN_ENUM.NEIBU.RENYUAN_PEIZHI)
gongzuo_neirong.append(SHIGONG_FANGAN_ENUM.NEIBU.GONGZUO_NEIRONG)
shebei_peizhi.append(SHIGONG_FANGAN_ENUM.NEIBU.SHEBEI_PEIZHI)
shigong_fangan.append(SHIGONG_FANGAN_ENUM.NEIBU.SHIGONG_FANGAN)
else:
pass
jiancha.append("叶片内部检查")
neirong.append(f"".join(Y_Code) + f"{len(Y_Code)}支叶片的内部导雷卡、腹板、透光、人孔盖版、叶根盖板...")
neirong2.append("内部导雷卡、腹板、透光、人孔盖版、叶根盖板...")
typical_picture_description.extend(TEMPLATE_HEADER.JINFENG_HEADER.NEIBU.TYPICAL_LIST)
if if_fanglei:
baogao_label.append("防雷")
image_source_to_find.append(baogao_info['fanglei_enum'])
use_tool_table.extend(
[ [str(idx)] + item
for idx, item in enumerate(USE_TOOL_ENUM.LIGHT.LIST, start=table_index) ]
)
table_index += len(USE_TOOL_ENUM.LIGHT.LIST)
if baogao_info["shigong_fangan"] == None:
renyuan_peizhi.append(SHIGONG_FANGAN_ENUM.FANGLEI.YEPIAN.RENYUAN_PEIZHI)
gongzuo_neirong.append(SHIGONG_FANGAN_ENUM.FANGLEI.YEPIAN.GONGZUO_NEIRONG)
shebei_peizhi.append(SHIGONG_FANGAN_ENUM.FANGLEI.YEPIAN.SHEBEI_PEIZHI)
shigong_fangan.append(SHIGONG_FANGAN_ENUM.FANGLEI.YEPIAN.SHIGONG_FANGAN)
else:
pass
jiancha.append("叶片导通测试")
neirong.append(f"轮毂至塔基导通、内部导线线阻、外部导线线阻...")
neirong2.append("轮毂至塔基导通、内部导线线阻、外部导线线阻...")
typical_picture_description.extend(TEMPLATE_HEADER.JINFENG_HEADER.FANGLEI.TYPICAL_LIST)
#获取缺陷图列表和典型图列表
filtered_picture_data, total_picture_num = process_picture_data(picture_data, image_source_to_find)
#获取所有缺陷记录
defect_records = get_defect_record_list()
#获取缺陷记录中对应图片的记录
defect_records_with_pic, error_pic_with_no_record = get_records_with_pic(defect_records, filtered_picture_data, quexian_type)
if len(error_pic_with_no_record) > 0:
print(f"!!!有部分缺陷图片没有对应的缺陷记录,将不会生成这些图片的缺陷表:{error_pic_with_no_record} ")
print(f"对应缺陷图的缺陷记录列表:{defect_records_with_pic}")
except Exception as e:
print(f"报告基本信息获取失败:{e}")
return
#检查参数合法性
if not if_fanglei or not if_neibu or not if_waibu:
print("请至少选择一种检查项目")
return
if not os.path.exists(shengcheng_dir):
print(f"生成路径{shengcheng_dir}不存在")
return
baogao_name = "叶片" + "".join(baogao_label) + "检查报告"
output_dir = os.path.normpath(f"{shengcheng_dir}/{project_name}项目{baogao_name}{jizu_bianhao}{baogao_date.split(' ')[0]}版.docx")
version = 1
while os.path.exists(output_dir):
if version != 1:
output_dir = output_dir.replace(f"{version - 1}",f"{version}")
else:
output_dir = output_dir.replace("",f"{version}")
version += 1
add_list = [
get_resource_path(MUBAN_DIR + '/jf_fengmian.png'),
get_resource_path(MUBAN_DIR + '/jf_fengmian1.png'),
get_resource_path(MUBAN_DIR + '/jinfeng_fengmian_name.docx'),
get_resource_path(MUBAN_DIR + '/jf_fengmian2.png'),
get_resource_path(MUBAN_DIR + '/jinfeng_fengmian_renyuan.docx'),
get_resource_path(MUBAN_DIR + '/jf_fengmian2.png'),
get_resource_path(MUBAN_DIR + '/jinfeng_fengmian_riqi.docx'),
get_resource_path(MUBAN_DIR + '/jf_fengmian1.png'),
get_resource_path(MUBAN_DIR + '/jinfeng_fengmian_luokuan.docx'),
get_resource_path(MUBAN_DIR + '/jf_fengmian3.png'),
]
print(await create_document(output_dir, section_args={
"page_height" : JF_HEIGHT,
"page_width" : JF_WIDTH,
"left_margin" : JF_L_MARGIN,
"right_margin" : JF_R_MARGIN,
"top_margin" : JF_T_MARGIN,
"bottom_margin" : JF_B_MARGIN,
}))
#标题样式
change_heading(output_dir, "Heading 1")
change_heading(output_dir, "Heading 2", HEADING_2_CONFIG)
change_heading(output_dir, "Heading 3", HEADING_3_CONFIG)
#加封面
merge_documents(output_dir, add_list)
#页眉
add_header(output_dir, TEMPLATE_HEADER.JINFENG_HEADER.ENUM)
#项目机组信息表格
print(add_documents(output_dir, get_resource_path(MUBAN_DIR + '/jf_table_title.docx')))
total_table_num = 0
print(add_table(output_dir, get_resource_path(MUBAN_DIR + '/jinfeng_table.docx'), REPORT_ENUM=TEMPLATE_HEADER.JINFENG_HEADER.ENUM))
total_picture_num += 1
list_to_replace = {
'jia_company_name' : Jia_company,
'fengchang_name' : fengchang_name,
'jizu_hao' : jizu_bianhao,
'bianzhi_renyuan': baogao_bianzhi,
'bianzhi_riqi': baogao_date,
'fengchang_name' : fengchang_name,
'jizu_hao' : jizu_bianhao,
'fengchang_location' : fengchang_location,
'kehu_company' : Yi_company,
'xiangmuguige' : jizu_num,
'kehu_fuzeren' : yi_fuzeren,
'yezhu_phone' : yi_phone,
'shigong_company' : JINFENG_COMPANY,
'shigong_date' : "未填写",#shigong_date,
'shigong_fuzeren' : "未填写",
'shigong_phone' : "未填写",
'zhengji_changjia' : jizu_manufacture,
'yepian_changjia' : partManufacturer,
'jizu_type' : jizu_xinghao,
'yepian_type' : partType,
'jiancha_fangshi' : ''.join(jiancha),
'jiancha_renyuan' : jiancha_renyuan
}
for find_text, replace_text in list_to_replace.items():
print(search_and_replace(output_dir, find_text, replace_text))
print(f"静态内容生成完毕,开始生成动态内容")
#缺陷信息表格1
defect_info, defect_part_type_list = get_defedct_info(defect_records_with_pic)
total_picture_num = add_defect_info_table(output_dir, defect_info, get_resource_path(MUBAN_DIR + '/quexian_liebiao.docx'), total_table_num,TEMPLATE_HEADER.JINFENG_HEADER.ENUM)
#缺陷信息表格2
table_list = tree_dict_to_table_data(defect_part_type_list) # 服务器获取的树形结构转化为表格数据
table_list = defect_list_addtitle(table_list, jizu_bianhao) # 增加每列标题,机组
add_table_title(output_dir, DEFECT_TABLE_TITLE) # 增加表格标题
total_table_num += 1
table_json = list_to_json_with_merges(table_list, style_config=STYLE_CONFIG, merge_columns=2) #将list转换为json形式前两列检查相同合并
json_to_docx(table_json, output_dir).save(output_dir) #jsontodocx
total_table_num += 1
#使用器具记录表
add_table_title(output_dir, USE_TOOL_TABLE_TITLE)
total_table_num += 1
json_to_docx(list_to_json_with_merges(use_tool_table, style_config=STYLE_CONFIG, detect_merges=False),output_dir).save(output_dir)
total_table_num += 1
#添加目录节
add_section(output_dir).save(output_dir)
#添加目录
#add_auto_toc_at_end(output_dir)
#添加横向节(展示图片)
add_landscape_section(output_dir).save(output_dir)
print(add_heading(output_dir, f"1. 机组号:{jizu_bianhao}", 1))
print(add_heading(output_dir, f"1.1 叶片编号:{Y_Code[0]}", 2))
#add_header(output_dir, TEMPLATE_HEADER.JINFENG_HEADER.ENUM, if_section=False, text = " " + TEMPLATE_HEADER.JINFENG_HEADER.PARA)
"""
typical_picture_list: #每列有两种情况
[
[str(图片描述), int(n), ...],
[str(图片地址), str(缺陷类型), ...]
]
defect_picture_list: #只有一种情况
[
[str(图片描述), str(图片描述), ...],
[str(图片地址), str(图片地址), ...]
]
"""
typical_picture_list = []
defect_picture_list = []
typical_picture_list = [
[
"外观检查迎、背风面是否有漆面脱落、裂纹等1",
"外观检查前、后缘如前缘漆面脱落、合模缝开裂等2",
"外观检查迎、背风面是否有漆面脱落、裂纹等3",
"外观检查前、后缘如前缘漆面脱落、合模缝开裂等4",
"外观检查迎、背风面是否有漆面脱落、裂纹等5",
"外观检查前、后缘如前缘漆面脱落、合模缝开裂等6",
"外观检查迎、背风面是否有漆面脱落、裂纹等7",
"外观检查前、后缘如前缘漆面脱落、合模缝开裂等8",
"外观检查迎、背风面是否有漆面脱落、裂纹等9",
"外观检查前、后缘如前缘漆面脱落、合模缝开裂等10",
],
[
"/home/dtyx/桌面/yhh/Report_Generate_Server/muban/wechat_2025-07-29_123404_038.png",
2,
"/home/dtyx/桌面/yhh/Report_Generate_Server/muban/wechat_2025-07-29_123404_038.png",
"/home/dtyx/桌面/yhh/Report_Generate_Server/muban/wechat_2025-07-29_123404_038.png",
2,
"/home/dtyx/桌面/yhh/Report_Generate_Server/muban/wechat_2025-07-29_123404_038.png",
"/home/dtyx/桌面/yhh/Report_Generate_Server/muban/wechat_2025-07-29_123404_038.png",
"/home/dtyx/桌面/yhh/Report_Generate_Server/muban/wechat_2025-07-29_123404_038.png",
2,
2,
],
[
"",
"图层脱落",
"",
"",
"图层脱落",
"",
"",
"",
"图层脱落",
"图层脱落",
]
]
defect_picture_list = [
[
"1",
"2",
"3456",
"456",
"56",
"6",
"12",
"123",
],
[
"/home/dtyx/桌面/yhh/Report_Generate_Server/muban/wechat_2025-07-29_123404_038.png",
"/home/dtyx/桌面/yhh/Report_Generate_Server/muban/wechat_2025-07-29_123404_038.png",
"/home/dtyx/桌面/yhh/Report_Generate_Server/muban/wechat_2025-07-29_123404_038.png",
"/home/dtyx/桌面/yhh/Report_Generate_Server/muban/wechat_2025-07-29_123404_038.png",
"/home/dtyx/桌面/yhh/Report_Generate_Server/muban/wechat_2025-07-29_123404_038.png",
"/home/dtyx/桌面/yhh/Report_Generate_Server/muban/wechat_2025-07-29_123404_038.png",
"/home/dtyx/桌面/yhh/Report_Generate_Server/muban/wechat_2025-07-29_123404_038.png",
"/home/dtyx/桌面/yhh/Report_Generate_Server/muban/wechat_2025-07-29_123404_038.png",
]
]
total_table_num = add_jf_picture_table(
typical_picture_list,
defect_picture_list,
get_resource_path(MUBAN_DIR + '/typical_picture_table.docx'),
get_resource_path(MUBAN_DIR + '/defect_picture_table.docx'),
output_dir,
total_table_num
)

View File

@ -1 +1,3 @@
报告生成接入服务器的测试开发调用服务器的api测试
# 报告生成接入服务器的测试开发调用服务器的api测试
## 在dist目录查看编译好的文件和使用文档

Binary file not shown.

Binary file not shown.

44
build.spec Normal file
View File

@ -0,0 +1,44 @@
# -*- mode: python ; coding: utf-8 -*-
block_cipher = None
a = Analysis(
['Generate_Report.py'], # 替换为你的脚本文件名
pathex=[],
binaries=[],
datas=[('./muban/*.docx', 'muban'),
('./muban/*.jpg', 'muban'),
('./muban/*.png', 'muban')],
hiddenimports=[],
hookspath=[],
hooksconfig={},
runtime_hooks=[],
excludes=[],
win_no_prefer_redirects=False,
win_private_assemblies=False,
cipher=block_cipher,
noarchive=False,
)
pyz = PYZ(a.pure, a.zipped_data, cipher=block_cipher)
exe = EXE(
pyz,
a.scripts,
a.binaries,
a.zipfiles,
a.datas,
[],
name='report_generator', # 可执行文件名称
debug=False,
bootloader_ignore_signals=False,
strip=False,
upx=True,
upx_exclude=[],
runtime_tmpdir=None,
console=True, # 显示控制台窗口
disable_windowed_traceback=False,
argv_emulation=False,
target_arch=None,
codesign_identity=None,
entitlements_file=None,
)

Binary file not shown.

Binary file not shown.

View File

@ -103,9 +103,7 @@ def apply_table_style(table, has_header_row=False, border_style=None, shading=No
except Exception:
return False
def copy_table(source_table, target_doc, ifadjustheight=True, height = 1):
def copy_table(source_table, target_doc, ifadjustheight=True, height = 1, if_merge = True, REPORT_ENUM = 'DT'):
"""
Copy a table from one document to another.
@ -118,6 +116,20 @@ def copy_table(source_table, target_doc, ifadjustheight=True, height = 1):
"""
# Create a new table with the same dimensions
new_table = target_doc.add_table(rows=len(source_table.rows), cols=len(source_table.columns))
# 获取表格属性
tbl_pr = new_table._tbl.tblPr
# 设置表格边框
tbl_borders = OxmlElement('w:tblBorders')
for border_name in ('top', 'left', 'bottom', 'right', 'insideH', 'insideV'):
border = OxmlElement(f'w:{border_name}')
border.set(qn('w:val'), 'single') # 边框类型
border.set(qn('w:sz'), '4') # 边框粗细(1-968代表1磅)
border.set(qn('w:space'), '0') # 边框间距
border.set(qn('w:color'), 'auto') # 边框颜色
tbl_borders.append(border)
tbl_pr.append(tbl_borders)
# Try to apply the same style
try:
@ -130,8 +142,9 @@ def copy_table(source_table, target_doc, ifadjustheight=True, height = 1):
except:
pass
from docx.enum.table import WD_TABLE_ALIGNMENT
from docx.enum.table import WD_ALIGN_VERTICAL
from docx.enum.table import WD_ALIGN_VERTICAL, WD_ROW_HEIGHT_RULE
from docx.shared import Pt, Inches, Cm, RGBColor
# Copy cell contents
for i, row in enumerate(source_table.rows):
for j, cell in enumerate(row.cells):
@ -144,18 +157,24 @@ def copy_table(source_table, target_doc, ifadjustheight=True, height = 1):
new_table.cell(i,j).paragraphs[0].runs[0]._element.rPr.rFonts.set(qn('w:eastAsia'), '仿宋') #设置中文字体
new_table.cell(i,j).paragraphs[0].paragraph_format.alignment = WD_TABLE_ALIGNMENT.CENTER
new_table.cell(i,j).vertical_alignment = WD_ALIGN_VERTICAL.CENTER
#new_table.cell(i,j).width = cell.width
if REPORT_ENUM == 'JF':
new_table.cell(i,j).paragraphs[0].runs[0]._element.rPr.rFonts.set(qn('w:eastAsia'), '宋体') #设置中文字体
new_table.cell(i,j).paragraphs[0].runs[0].font.size = Pt(8) # 字体大小
"""
待添加如何让表格自适应大小autofit目前不知为何没有作用
"""
if ifadjustheight:
new_table.rows[i].height = Cm(height)
#new_table.rows[i].height = row.height
#new_table.rows[i].height_rule = WD_ROW_HEIGHT_RULE.EXACTLY
if not ifadjustheight:
new_table.auto_fit = True
if if_merge:
try:
new_table = merge_tables(new_table)
except Exception as e:
print(f"合并表格失败:{e}")
from docx.shared import Inches
return new_table
return target_doc
from collections import deque

124
dist/README.md vendored Normal file
View File

@ -0,0 +1,124 @@
# 报告生成工具使用文档
## 1. 功能简介
本工具用于根据风机巡检数据自动生成 Word 报告。
支持 **配置文件****命令行参数** 两种输入方式,命令行可覆盖配置文件中的同名字段。
## 2. 准备
1. 获取可执行程序。
2. 准备输出目录(默认 `./output`)。
3. 获取要生成报告的机组id。
4. 获取要生成的报告包含的检查来源(外部、内部、防雷)。
## 3. 配置文件启动
在项目根目录新建 `config.json`(或任意文件名,但需要在命令行指定),格式可如下,必填必须填写,选填则键也不需要,否则会覆盖默认值,下面是默认的配置文件:
```json
{
"json1": {
"turbine_id": "183463dbf40d9278549a76b82b175dd9" //机组id 必填
},
"json2": {
"shengcheng_dir": "./output", //输出目录 必填
//选填项
"if_waibu": true, //是否包含外部检测图片即对应信息
"if_neibu": true, //是否包含内部检测图片即对应信息
"if_fanglei": true, //是否包含防雷检测图片即对应信息
"userName": "admin", //报告编制人
"baogaoCheck": "未审核", //报告审核人
"key_words": "缺,损,裂,脱,污", //关键词(英文逗号分隔),用于汇总表标红
"data_processor": "未获取", //数据处理人
"jiancha_renyuan": "未获取", //检查人员
"check_date": null, //检查日期,日和时必须用空格分隔 YYYY-MM-DD hh:mm:ss
"coverurl": null, //封面图片 URL不指定则获取project表中的封面url
"conclusion": "未填写总结", //报告总结文字
//报告结构定制
"if_docx_fengmian": true, //是否包含封面
"if_docx_project_overview": true, //是否包含项目概览表
"if_docx_inspection_method": true, //是否包含检查方案表
"if_docx_inspection_info": true, //是否包含检查信息表
"if_docx_chengguo_sub": true, //是否包含成果递交表
"if_docx_inspection_text": true, //是否包含检查汇总文字表
"if_docx_inspection_picture": true, //是否包含检查汇总图片表
"if_docx_defect_picture": true, //是否包含缺陷图片表
"if_docx_conclusion": true, //是否包含报告总结
//下面为枚举字段,程序通过这些字段在数据库中筛选、分类获取到的图片
"dianxing_enum": "TYPICAL", //典型图片类型枚举
"quexian_enum": "DEFECT", //缺陷图片类型枚举
"other_enum": "OTHER", //其他图片类型枚举 (目前程序不会操作其它图片)
"waibu_enum": "out-work", //外部图片来源枚举
"neibu_enum": "in-work", //内部图片来源枚举
"fanglei_enum": "lightning-protection-work", //防雷图片来源枚举
}
}
```
## 4. 命令行参数
| 参数 | 说明 | 示例 |
|------|------|------|
| `--config` / `-c` | 指定配置文件 | `--config prod.json` |
| `--turbine_id` / `--id` | 风机 ID | `--turbine_id 123456` |
| `--output_dir` / `--out` | 报告输出目录 | `--output_dir ./result` |
| `--template_dir` / `--tpl` | 模板目录 | `--template_dir ./tpl` |
| `--if_waibu` | 是否包含外部作业图片及信息 | `true` / `false` |
| `--if_neibu` | 是否包含内部作业图片及信息 | `true` / `false` |
| `--if_fanglei` | 是否包含防雷作业图片及信息 | `true` / `false` |
| `--userName` | 报告编制人 | `--userName 张三` |
| `--baogaoCheck` | 报告审核人 | `--baogaoCheck 已审核` |
| `--key_words` | 关键词(英文逗号分隔) | `--key_words 缺,损,裂` |
| `--data_processor` | 数据处理人 | `--data_processor 李四` |
| `--jiancha_renyuan` | 检查人员 | `--jiancha_renyuan 王五` |
| `--check_date` | 检查日期YYYY-MM-DD hh:mm:ss | `--check_date 2024-06-01 12:00:00` |
| `--coverurl` | 封面图片 URL | `--coverurl /stastic/path/to/pic` |
| `--conclusion` | 报告总结 | `--conclusion 整体良好` |
## 5. 使用示例
### 5.1 仅使用配置文件
根目录存放config.json后会加载config.json或者使用命令行指定配置文件名和路径
#### Linux|bash
```bash
./report_generator
```
#### Windows|cmd
```cmd
./report_generator.exe
```
### 5.2 完全命令行
#### Linux|bash
```bash
./report_generator \
--turbine_id 123456 \
--output_dir ./my_out \
--if_waibu false \
--userName Alice \
--check_date 2024-06-01 \
--conclusion "检查完毕,无异常"
```
#### Windows|cmd
```cmd
./report_generator.exe \
--turbine_id 123456 \
--output_dir ./my_out \
--if_waibu false \
--userName Alice \
--check_date 2024-06-01 \
--conclusion "检查完毕,无异常"
```
### 5.3 混合方式
#### Linux|bash
```bash
./report_generator --config config.json --userName Bob --if_fanglei false
```

BIN
dist/report_generator vendored Executable file

Binary file not shown.

BIN
dist/report_generator.exe vendored Executable file

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
muban/dt_header.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.1 KiB

Binary file not shown.

Binary file not shown.

BIN
muban/jf_fengmian.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

BIN
muban/jf_fengmian1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 197 B

BIN
muban/jf_fengmian2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 138 B

BIN
muban/jf_fengmian3.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 64 KiB

BIN
muban/jf_header.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

BIN
muban/jf_hengxiang.doc Normal file

Binary file not shown.

BIN
muban/jf_table_title.docx Normal file

Binary file not shown.

BIN
muban/jfempty.docx Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
muban/jinfeng_table.docx Normal file

Binary file not shown.

BIN
muban/quexian_liebiao.docx Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 104 KiB

Binary file not shown.

BIN
report_generator Executable file

Binary file not shown.

117
test.py Normal file
View File

@ -0,0 +1,117 @@
from typing import Dict
import os
from docx import Document
from tools.document_tools import add_picture_to_table, add_table_to_document
def add_jf_picture_table(
typical_picture_dict: Dict[str, str],
defect_picture_dict: Dict[str, str],
TYPICAL_MUBAN_DIR: str,
DEFECT_MUBAN_DIR: str,
output_dir: str,
):
"""添加金风版本的图片展示表格"""
# 初始化文档对象
doc = Document()
target_filename = os.path.join(output_dir, "output.docx")
# 处理典型图
typical_data = []
defect_keys = [] # 记录有缺陷的典型图key
# 准备典型图数据
for key, value in typical_picture_dict.items():
if "损伤" in key or "损伤" in value:
# 情况二:有缺陷
defect_count = int(value.split("损伤类型")[1].split("")[0])
typical_data.append([key, "损伤{}处,详见下表".format(defect_count), value])
defect_keys.append((key, defect_count))
else:
# 情况一:正常图片
typical_data.append([key, value, ""]) # 第三行留空
# 添加典型图表格
if typical_data:
# 将数据转换为适合表格的格式3行×n列
# 需要将数据从行优先转为列优先
cols = len(typical_data)
table_data = [[], [], []]
for col_data in typical_data:
for i in range(3):
table_data[i].append(col_data[i] if i < len(col_data) else "")
# 添加典型图表格
add_table_to_document(
target_filename=target_filename,
source_filename=TYPICAL_MUBAN_DIR,
rows=3,
cols=cols,
table_num=0,
data=table_data
)
# 添加典型图中的图片
doc = Document(target_filename)
for col_idx, (key, value) in enumerate(typical_picture_dict.items()):
if "损伤" not in key and "损伤" not in value: # 只有正常图片才添加
add_picture_to_table(
target_doc=doc,
target_filename=target_filename,
row=1, # 第二行是图片
col=col_idx,
image_path=value
)
doc.save(target_filename)
# 处理缺陷图
if defect_keys:
# 遍历所有有缺陷的典型图
for typical_key, defect_count in defect_keys:
# 计算需要多少个缺陷图表格每表5列
table_count = (defect_count + 4) // 5 # 向上取整
for table_idx in range(table_count):
# 计算当前表格的列数
cols_in_table = min(5, defect_count - table_idx * 5)
# 准备缺陷图数据
defect_data = []
for i in range(cols_in_table):
defect_idx = table_idx * 5 + i
defect_key = list(defect_picture_dict.keys())[defect_idx]
defect_value = list(defect_picture_dict.values())[defect_idx]
defect_data.append([defect_key, defect_value])
# 将数据转换为适合表格的格式2行×n列
table_data = [[], []]
for col_data in defect_data:
for i in range(2):
table_data[i].append(col_data[i] if i < len(col_data) else "")
# 添加缺陷图表格
add_table_to_document(
target_filename=target_filename,
source_filename=DEFECT_MUBAN_DIR,
rows=2,
cols=cols_in_table,
table_num=0,
data=table_data
)
# 添加缺陷图中的图片
doc = Document(target_filename)
for col_idx in range(cols_in_table):
defect_idx = table_idx * 5 + col_idx
defect_value = list(defect_picture_dict.values())[defect_idx]
add_picture_to_table(
target_doc=doc,
target_filename=target_filename,
row=1, # 第二行是图片
col=col_idx,
image_path=defect_value
)
doc.save(target_filename)
return target_filename
add_jf_picture_table({""})

86
tools/1.md Normal file
View File

@ -0,0 +1,86 @@
```python
def tree_dict_to_table_data(tree: Dict) -> List[List[str]]:
"""
将树状字典转换为二维表格数据
参数:
tree: 树状字典结构为dict[dict[...[list]]]
返回:
二维列表表示的表格数据
"""
if not tree:
return []
# 首先确定树的深度
depth = 0
current = tree
while isinstance(current, dict):
depth += 1
# 获取第一个子节点来继续探测深度
if current:
current = next(iter(current.values()))
else:
break
# 收集所有路径和叶子节点
paths = []
def traverse(node, current_path):
if isinstance(node, dict):
for key, value in node.items():
traverse(value, current_path + [str(key)])
elif isinstance(node, list):
paths.append((current_path, node))
else:
paths.append((current_path, [str(node)]))
traverse(tree, [])
# 确定最大深度(处理可能的不平衡树)
max_depth = max(len(path) for path, _ in paths) if paths else 0
max_leaf_length = max(len(leaf) for _, leaf in paths) if paths else 0
# 填充路径到最大深度
filled_paths = []
for path, leaf in paths:
# 填充路径
filled_path = path.copy()
while len(filled_path) < max_depth:
filled_path.append("") # 用空字符串填充不足的深度
# 填充叶子节点
filled_leaf = leaf.copy()
while len(filled_leaf) < max_leaf_length:
filled_leaf.append("") # 用空字符串填充不足的叶子长度
filled_paths.append((filled_path, filled_leaf))
# 构建表格数据
table_data = []
# 添加路径部分的行
for i in range(max_depth):
row = []
for path, leaf in filled_paths:
row.append(path[i])
table_data.append(row)
# 添加叶子部分的行
for i in range(max_leaf_length):
row = []
for path, leaf in filled_paths:
row.append(leaf[i] if i < len(leaf) else "")
table_data.append(row)
# 转置表格,使每个路径+叶子成为一列
if table_data:
# 获取最大列数
max_cols = max(len(row) for row in table_data) if table_data else 0
# 统一每行的列数
table_data = [row + [""] * (max_cols - len(row)) for row in table_data]
# 转置
table_data = list(map(list, zip(*table_data)))
return table_data
```

View File

@ -6,3 +6,5 @@ GETWEATHERINFO = "/weather-type/{weatherCode}"
GETPICTURELIST = "/image/list"
GETPARTLIST = "/part/list"
GETYEPIANINFO = "/part/detail/{partId}"
DEFECTRECORDLIST = "/defect/list"
DEFECTDETAIL = "/defect/detail/{defectId}"

View File

@ -10,7 +10,6 @@ def get_data(url : str, data_type : str = "data", params : dict = None ) -> dict
response = requests.get(url, headers=headers, params=params)
if response.status_code == 200:
data = json.loads(response.text)
print(f"获取到数据:{data}")
return data[data_type]
else:
print(f"请求数据失败,状态码:{response.status_code}")
@ -41,7 +40,19 @@ def get_weather(weatherid : str) -> dict:
weatherurl = DTURL + GETWEATHERINFO.format(weatherCode=weatherid)
return get_data(weatherurl, "data")
def get_defect_record_list() -> list[dict]:
url = DTURL + DEFECTRECORDLIST
return get_data(url)
def get_part_list(turbineId : str) -> dict:
"""获取对应机组所有部件信息
Args:
turbineId (str): 机组ID
Return:
list: 包含所有部件完整信息的列表
list: 包含所有部件id的列表
"""
parturl = DTURL + GETPARTLIST
params = {
"turbineId" : turbineId
@ -50,57 +61,42 @@ def get_part_list(turbineId : str) -> dict:
print(f"获取到部件{result}")
return result, [item["partId"] for item in result]
def get_part_picture(turbineId : str) -> tuple[list[dict], list[dict]]:
def get_part_picture(turbineId : str) -> tuple[list[dict], dict[list], list[str]]:
"""获取对应机组所有图片
Args:
turbineId (str): 机组ID
Return:
list: 包含所有对应机组的部件信息的列表
list: 包含所有图片信息的列表(按部件分list)
[
{
"imageId": "图片id",
"imageName": "图片名",
"imagePath": "图片路径",
"partId": "部件id",
"partName": "部件名",
"imageResolution": "字符串",
"focalDistance": "字符串/None",
"shootingTime": "字符串/None",
"cameraManufacturer": "字符串/None",
"cameraModel": "字符串/None",
"weather": "字符串/None",
"weatherLabel": "字符串/None",
"humidness": "数值/None",
"temperature": "字符串/None",
"windLevel": "数值/None",
"shootingMethod": "字符串/None",
"shootingMethodLabel": "字符串/None",
"shootingDistance": "数值/None",
"collectorName": "字符串/None",
"imageType": "None",
"imageTypeLabel": "None",
"audioList": "None",
"gps": "字符串/None"
},
...
]
dict[list]: 包含所有图片信息的列表(按部件分list)
Yepians: 包含所有叶片信息的列表
"""
picturerul = DTURL + GETPICTURELIST
part_data, part_list = get_part_list(turbineId)
result = []
for part_id in part_list:
params = {
"partId" : part_id,
}
for images in get_data(picturerul, params = params):
result.append(images)
print(f"图片数据获取成功:{result}")
return part_data, result
#先按顺序找叶片123
Yepians = []
yepian_to_find = ["叶片1","叶片2","叶片3"]
#依次获取叶片123的信息未考虑多个同叶片的信息情况正常情况下不会发生
try:
part_result = {}
yepian_part_result = {}
for name in yepian_to_find:
# 找到第一个匹配的部件并添加到列表中
for part in part_data:
if part['partName'] == name:
Yepians.append(part)
for image in get_data(picturerul, params = {"partId" : part["partId"]}):
if part["partName"] not in yepian_part_result:
yepian_part_result[part["partName"]] = []
yepian_part_result[part["partName"]].append(image)
elif part['partName'] not in part_result and part['partName'] not in yepian_to_find:
for image in get_data(picturerul, params = {"partId" : part["partId"]}):
if part["partName"] not in part_result:
part_result[part["partName"]] = []
part_result[part["partName"]].append(image)
except Exception as e:
print(f"获取叶片信息失败,异常:{e}")
return None, None, None
return part_data, yepian_part_result, part_result, Yepians
def get_yepian_xiangqing(yepian_id : str) -> dict:
url = GETYEPIANINFO.format(partId=yepian_id)
@ -112,4 +108,60 @@ def find_defect_record(defect_pictures : list[dict]):
Args:
defect_pictures (list[dict]): 缺陷图列表
"""
for pic in defect_pictures:
pic_id = pic["imageId"]
def get_full_picture_url(pic_url : str) -> str:
"""获取完整的图片URL
Args:
pic_url (str): 图片URL
Returns:
str: 完整的图片URL
"""
return DTURL + pic_url
def check_pic_url(pic_url: str, timeout: int = 5) -> bool:
"""检查图片URL是否有效
仅验证了头部和Content-Type
Args:
pic_url: 图片URL
timeout: 请求超时时间
Returns:
bool: True 如果是有效图片否则 False
"""
print(f"检查图片URL{pic_url}")
if pic_url is None:
return False
pic_url = get_full_picture_url(pic_url)
try:
# 发起HEAD请求更快仅获取头部信息
response = requests.head(pic_url, timeout=timeout, allow_redirects=True)
if response.status_code != 200:
print(f"请求图片失败,状态码:{response.status_code}")
return False
# 检查Content-Type是否是图片类型
content_type = response.headers.get("Content-Type", "").lower()
if not content_type.startswith("image/"):
print(f"图片类型错误:{content_type}")
return False
return True
except Exception as e:
print(f"请求图片失败, 未知错误{e}")
return False
def get_defect_detail(defectId : str) -> dict:
"""获取缺陷详情
Args:
defectId (str): 缺陷ID
Returns:
dict: 缺陷详情
"""
url = DTURL + DEFECTDETAIL.format(defectId=defectId)
return get_data(url, "data")

20
tools/Spiretool.py Normal file
View File

@ -0,0 +1,20 @@
from spire.doc import *
from spire.doc.common import *
# 加载第一个文档
doc1 = Document()
doc1.LoadFromFile("./muban/jingfeng_1.docx")
# 加载第二个文档
doc2 = Document()
doc2.LoadFromFile("./muban/jf_hengxiang.doc")
# 遍历 doc2 的所有节(使用 GetSections()
for i in range(doc2.Sections.Count):
section = doc2.Sections.get_Item(i) # 使用 GetItem 获取 Section
doc1.Sections.Add(section.Clone()) # 克隆并添加到 doc1
# 保存合并后的文档
doc1.SaveToFile("./output/MergedDocument.docx", FileFormat.Docx2019)
doc1.Close()
doc2.Close()

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

109
tools/argtool.py Normal file
View File

@ -0,0 +1,109 @@
from typing import Dict, Any
import json
import argparse
def load_config(config_path: str) -> Dict[str, Any]:
"""从JSON配置文件加载配置"""
with open(config_path, 'r', encoding='utf-8') as f:
return json.load(f)
def parse_arguments():
parser = argparse.ArgumentParser(description='报告生成工具')
# 添加配置文件参数
parser.add_argument('--config', '-c', dest='config_file',
default='config.json',
help='配置文件路径默认为当前目录下的config.json')
# 保留一些关键参数作为命令行可覆盖选项
parser.add_argument('--turbine_id', '--id', dest='turbine_id',
help='覆盖配置文件中的风机ID')
parser.add_argument('--output_dir', '--out', dest='output_dir',
help='覆盖配置文件中的输出目录')
parser.add_argument('--if_waibu', type=lambda x: x.lower() in ('true', '1', 'yes'),
help='是否包含外部作业章节')
parser.add_argument('--if_neibu', type=lambda x: x.lower() in ('true', '1', 'yes'),
help='是否包含内部作业章节')
parser.add_argument('--if_fanglei', type=lambda x: x.lower() in ('true', '1', 'yes'),
help='是否包含防雷作业章节')
parser.add_argument('--userName', dest='userName',
help='报告编制人')
parser.add_argument('--baogaoCheck', dest='baogaoCheck',
help='报告审核状态')
parser.add_argument('--key_words', dest='key_words',
help='关键词,英文逗号分隔')
parser.add_argument('--data_processor', dest='data_processor',
help='数据处理人')
parser.add_argument('--jiancha_renyuan', dest='jiancha_renyuan',
help='检查人员')
parser.add_argument('--check_date', dest='check_date',
help='检查日期,格式 YYYY-MM-DD')
parser.add_argument('--coverurl', dest='coverurl',
help='封面图片 URL')
parser.add_argument('--conclusion', dest='conclusion',
help='报告总结')
parser.add_argument('--choose_template', '--ct', dest='choose_template',
help='选择模板,默认为迪特模板,目前支持枚举参数DTJF')
return parser.parse_args()
def merge_configs(default_cfg: Dict[str, Any],
file_cfg: Dict[str, Any],
cli: argparse.Namespace) -> Dict[str, Dict[str, Any]]:
"""合并默认配置、文件配置和命令行参数"""
merged = {
'json1': {**default_cfg['json1'], **file_cfg.get('json1', {})},
'json2': {**default_cfg['json2'], **file_cfg.get('json2', {})}
}
# 高频覆盖项
if cli.turbine_id:
merged['json1']['turbine_id'] = cli.turbine_id
if cli.output_dir:
merged['json2']['shengcheng_dir'] = cli.output_dir
for key in ('if_waibu', 'if_neibu', 'if_fanglei', 'userName',
'baogaoCheck', 'key_words', 'data_processor',
'jiancha_renyuan', 'check_date', 'coverurl', 'conclusion'
'choose_template'):
val = getattr(cli, key, None)
if val is not None:
merged['json2'][key] = val
return merged
def get_default_config() -> Dict[str, Dict[str, Any]]:
"""获取默认配置"""
return {
'json1': {
"turbine_id": "183463dbf40d9278549a76b82b175dd9",
},
'json2': {
'shengcheng_dir': "",
"dianxing_enum": "TYPICAL",
"quexian_enum": "DEFECT",
"other_enum": "OTHER",
"waibu_enum": "out-work",
"neibu_enum": "in-work",
"fanglei_enum": "lightning-protection-work",
"if_waibu": True,
"if_neibu": True,
"if_fanglei": True,
"userName": "admin",
"baogaoCheck": "未审核",
'key_words': '缺,损,裂,脱,污',
"data_processor": "未获取",
"shigong_fangan": None,
'jiancha_renyuan': '未获取',
"check_date": None,
"coverurl": None,
"conclusion": "未填写总结",
"if_docx_fengmian": True,
"if_docx_project_overview": True,
"if_docx_inspection_method": True,
"if_docx_inspection_info": True,
"if_docx_chengguo_sub": True,
"if_docx_inspection_text": True,
"if_docx_inspection_picture": True,
"if_docx_defect_picture": True,
"if_docx_conclusion": True,
"choose_template" : "DT"
}
}

View File

@ -138,7 +138,7 @@ def split_table_by_row_content(
return f"处理表格时出错: {str(e)}"
async def add_heading(filename: str, text: str, level: int = 1) -> str:
def add_heading(filename: str, text: str, level: int = 1) -> str:
"""对文档增加标题
Args:
@ -179,10 +179,10 @@ async def add_heading(filename: str, text: str, level: int = 1) -> str:
doc.save(filename)
return f"Heading '{text}' (level {level}) added to {filename}"
except Exception as style_error:
print("style-based approach fails, use direct formatting")
# If style-based approach fails, use direct formatting
paragraph = doc.add_paragraph(text)
paragraph.style = doc.styles['Normal']
run = paragraph.runs[0]
paragraph = doc.add_paragraph()
run = paragraph.add_run(text)
run.bold = True
rPr = run.element.get_or_add_rPr()
rFonts = rPr.get_or_add_rFonts()
@ -307,149 +307,174 @@ async def add_table(filename: str, rows: int, cols: int, data: Optional[List[Lis
except Exception as e:
return f"Failed to add table: {str(e)}"
async def add_picture_to_table(target_doc: Document, target_filename: str, row: int, col: int, image_path: str,table_num: int = -1, width: Optional[float] = None) -> str:
"""向文档中对应表格添加图片
Args:
target_doc: 目标文档
target_filename: 目标文档保存路径
row: 表格行数
col: 表格列数
image_path: 图片路径
table_num: 表格序号默认为-1即最后一个表格
width: 图片宽度默认为None表示使用原始图片大小
"""
def add_picture_to_table(
target_doc: Document,
target_filename: str,
row: int,
col: int,
image_path: str,
table_num: int = -1,
width: Optional[float] = None,
height: Optional[float] = None
) -> str:
from PIL import Image
from io import BytesIO
import requests
is_url = image_path.startswith(("http://", "https://"))
image_bytes = None
try:
# 1. 获取图片数据
if is_url:
response = requests.get(image_path, timeout=30)
response.raise_for_status()
image_bytes = BytesIO(response.content)
else:
if not os.path.exists(image_path):
return f"Image file not found: {image_path}"
return f"Image not found: {image_path}"
with open(image_path, 'rb') as f:
image_bytes = BytesIO(f.read())
# Check image file size
try:
image_size = os.path.getsize(image_path) / 1024 # Size in KB
if image_size <= 0:
return f"Image file appears to be empty: {image_path} (0 KB)"
elif image_size > 9126:
# Create the output directory if it doesn't exist
output_dir = os.path.join(os.path.dirname(image_path), "压缩图片")
if not os.path.exists(output_dir):
os.makedirs(output_dir)
# 2. 准备图片数据(关键步骤)
img = Image.open(image_bytes)
final_bytes = BytesIO()
# Define the output path for the compressed image
image_name = os.path.basename(image_path)
output_path = os.path.join(output_dir, image_name)
# 转换为Word兼容的最佳格式
if img.mode == 'RGBA':
img.save(final_bytes, format='PNG')
else:
img.save(final_bytes, format='JPEG', quality=85)
# Compress the image
while image_size > 9126:
print(f"压缩图片:{image_path} ({image_size:.2f} KB) -> {output_path} (9126 KB)")
with Image.open(image_path) as img:
img.save(output_path, optimize=True, quality=85)
image_size = os.path.getsize(output_path) / 1024 # Size in KB
final_bytes.seek(0) # ⚠️ 必须重置指针!
# Update the image path to the compressed image path
image_path = output_path
except Exception as size_error:
return f"Error checking image file: {str(size_error)}"
try:
# 3. 添加到文档
table = target_doc.tables[table_num]
# Add the picture to the cell
cell = table.cell(row, col)
if len(cell.text) == 1: cell.text = ""
paragraph = cell.paragraphs[-1]
# 彻底清除单元格
for paragraph in cell.paragraphs:
paragraph.clear()
paragraph = cell.add_paragraph()
run = paragraph.add_run()
# 添加图片(带异常捕获)
try:
if width:
run.add_picture(image_path, width=Inches(width))
else:
run.add_picture(image_path)
except Exception as e:
# 如果添加图片时出现问题尝试将图片转换为PNG格式
try:
print(f"正常添加失败,尝试转换图片后添加:{image_path}")
# 打开图片
img = Image.open(image_path)
# 转换为PNG格式
temp_image_path = os.path.splitext(image_path)[0] + '.png'
img.save(temp_image_path, 'PNG')
# 尝试添加转换后的图片
if width:
run.add_picture(temp_image_path, width=Inches(width))
run.add_picture(final_bytes, width=Inches(width), height=Inches(height))
except:
run.add_picture(final_bytes, width=Inches(width))
else:
run.add_picture(temp_image_path)
run.add_picture(final_bytes)
except Exception:
final_bytes.seek(0) # 再次重置指针
if width:
run.add_picture(final_bytes, width=Inches(width), height=Inches(height))
else:
run.add_picture(final_bytes)
# 添加完成后删除转换后的图片
os.remove(temp_image_path)
except Exception as e:
# 如果转换或添加转换后的图片时出现问题,返回错误信息
return f"调用add_picture函数出现问题: {str(e)}"
from docx.enum.table import WD_TABLE_ALIGNMENT
from docx.enum.table import WD_ALIGN_VERTICAL
cell.paragraphs[0].paragraph_format.alignment = WD_TABLE_ALIGNMENT.CENTER
# 4. 设置对齐并保存
from docx.enum.table import WD_TABLE_ALIGNMENT, WD_ALIGN_VERTICAL
paragraph.paragraph_format.alignment = WD_TABLE_ALIGNMENT.CENTER
cell.vertical_alignment = WD_ALIGN_VERTICAL.CENTER
target_doc.save(target_filename)
return f"Picture {image_path} added to table {table_num} cell ({row},{col})"
except Exception as e:
return f"Failed to add picture to table: {str(e)}"
# 确保保存到新文件(避免内存文档与文件不同步)
temp_filename = target_filename.replace('.docx', '_temp.docx')
target_doc.save(temp_filename)
async def add_picture(filename: str, image_path: str, width: Optional[float] = None) -> str:
"""添加一个图片到文档中
# 验证文档有效性
try:
Document(temp_filename) # 尝试读取
os.replace(temp_filename, target_filename)
except Exception:
os.remove(temp_filename)
return "Failed to generate valid document"
return "Picture added successfully"
except Exception as e:
return f"Error: {str(e)}"
import requests
from io import BytesIO
from PIL import Image
from docx.enum.text import WD_ALIGN_PARAGRAPH
def add_picture(filename: str, image_path: str, width: Optional[float] = None, height: Optional[float] = None, is_center: Optional[bool] = False) -> str:
"""添加一个图片到文档中(支持本地路径或 URL
Args:
filename: 文档路径
image_path: 图片路径
width: 图片大小
image_path: 图片路径本地路径或 URL
width: 图片大小英寸
"""
filename = ensure_docx_extension(filename)
# Validate document existence
# 检查文档是否存在
if not os.path.exists(filename):
return f"Document {filename} does not exist"
# Get absolute paths for better diagnostics
abs_filename = os.path.abspath(filename)
abs_image_path = os.path.abspath(image_path)
# Validate image existence with improved error message
if not os.path.exists(abs_image_path):
return f"Image file not found: {abs_image_path}"
# Check image file size
try:
image_size = os.path.getsize(abs_image_path) / 1024 # Size in KB
if image_size <= 0:
return f"Image file appears to be empty: {abs_image_path} (0 KB)"
except Exception as size_error:
return f"Error checking image file: {str(size_error)}"
# Check if file is writeable
is_writeable, error_message = check_file_writeable(abs_filename)
if not is_writeable:
return f"Cannot modify document: {error_message}. Consider creating a copy first or creating a new document."
is_url = image_path.startswith(("http://", "https://"))
try:
doc = Document(abs_filename)
# Additional diagnostic info
diagnostic = f"Attempting to add image ({abs_image_path}, {image_size:.2f} KB) to document ({abs_filename})"
para = doc.add_paragraph()
run = para.add_run()
# 处理 URL 图片
if is_url:
try:
response = requests.get(image_path, timeout=10)
response.raise_for_status() # 检查请求是否成功
image_bytes = BytesIO(response.content)
# 验证图片有效性(可选)
Image.open(image_bytes).verify()
image_bytes.seek(0) # 重置指针
# 添加到文档
if width:
run.add_picture(image_bytes, width=Inches(width), height=Inches(height))
else:
run.add_picture(image_bytes)
if is_center:
para.alignment = WD_ALIGN_PARAGRAPH.CENTER
doc.save(abs_filename)
return f"Picture from URL {image_path} added to {filename}"
except Exception as url_error:
return f"Failed to download/add URL image: {str(url_error)}"
# 处理本地图片
else:
abs_image_path = os.path.abspath(image_path)
if not os.path.exists(abs_image_path):
return f"Image file not found: {abs_image_path}"
# 检查文件大小和可读性(原逻辑)
try:
image_size = os.path.getsize(abs_image_path) / 1024
if image_size <= 0:
return f"Image file is empty: {abs_image_path}"
except Exception as size_error:
return f"Error checking image file: {str(size_error)}"
# 添加到文档
try:
if width:
doc.add_picture(abs_image_path, width=Inches(width))
run.add_picture(abs_image_path, width=Inches(width), height=Inches(height))
else:
doc.add_picture(abs_image_path)
run.add_picture(abs_image_path)
if is_center:
para.alignment = WD_ALIGN_PARAGRAPH.CENTER
doc.save(abs_filename)
return f"Picture {image_path} added to {filename}"
except Exception as inner_error:
# More detailed error for the specific operation
error_type = type(inner_error).__name__
error_msg = str(inner_error)
return f"Failed to add picture: {error_type} - {error_msg or 'No error details available'}\nDiagnostic info: {diagnostic}"
return f"Failed to add picture: {str(inner_error)}"
except Exception as outer_error:
# Fallback error handling
error_type = type(outer_error).__name__
error_msg = str(outer_error)
return f"Document processing error: {error_type} - {error_msg or 'No error details available'}"
return f"Document processing error: {str(outer_error)}"
async def add_page_break(filename: str) -> str:
@ -603,7 +628,7 @@ async def delete_paragraph(filename: str, paragraph_index: int) -> str:
return f"Failed to delete paragraph: {str(e)}"
async def search_and_replace(filename: str, find_text: str, replace_text: str) -> str:
def search_and_replace(filename: str, find_text: str, replace_text: str) -> str:
"""替换所有find_text为replace_text
Args:
@ -634,4 +659,3 @@ async def search_and_replace(filename: str, find_text: str, replace_text: str) -
return f"No occurrences of '{find_text}' found."
except Exception as e:
return f"Failed to search and replace: {str(e)}"

View File

@ -1,6 +1,6 @@
from tools.content_tools import add_picture_to_table
from tools.document_tools import add_table_to_document,search_and_replace
from tools.get_pictures import resize_and_reduce_quality
import sys, os
from tools.Get_Json import get_defect_detail
from tools.defines import *
def caculate_work_days(start_date : str, end_date : str) -> str:
"""根据起止日期计算工期
@ -21,46 +21,7 @@ def caculate_work_days(start_date : str, end_date : str) -> str:
return (end_date - start_date).days
async def add_dynamic_table(output_doc, output_dir, table_num, TABLES, JIANCHA_XIANGQING_DIR, PICTURES, row, col, i, FLAG, xuhao):
"""创建动态表
Args:
output_doc (Document): 文档对象
output_dir (str): 输出目录
table_num (int): 表格序号
TABLES (list): 表格数据
JIANCHA_XIANGQING_DIR (str): 检查详情表目录
PICTURES (dict): 图片数据字典键为表索引值为图片路径列表
row (int): 行数
col (int): 列数
i (int): 表格序号
FLAG: 其他标志
Returns:
tuple: (i, table_num) 更新后的表格序号和表格数量
"""
for table_idx, Table in enumerate(TABLES):
print(Table)
output_doc, message = await add_table_to_document(output_dir, JIANCHA_XIANGQING_DIR, row, col, i, Table, FLAG)
print(message)
# 获取当前表格对应的图片
current_table_pictures = PICTURES.get(table_idx, [])
print(f"开始处理图片列表: {current_table_pictures}")
for picturedir in current_table_pictures:
try:
print(f"添加 {picturedir} {type(picturedir)}到表格{table_idx}")
resize_and_reduce_quality(picturedir, picturedir)
await add_picture_to_table(output_doc, output_dir, 4, 0, picturedir, i, 4.7232)
except Exception as e:
print(f"添加图片失败:{e}")
print(await search_and_replace(output_dir, 'tupian_xuhao', f'{xuhao}'))
table_num += 1
i += 1
xuhao += 1
return i, table_num, xuhao
def get_year_month(date):
"""根据格式化date字符串获取年月 'date': '二〇二一年十二月十日 9:00'
@ -80,7 +41,7 @@ def get_year_month(date):
def merge_info(frontend_info, default_info):
"""
合并前端传入的 info 和默认 info
规则如果前端传入的值为空None 或空字符串则使用默认值
新规则以default_info为基础字典用frontend_info完全覆写取两者的并集
Args:
frontend_info: 前端传入的字典
@ -91,16 +52,371 @@ def merge_info(frontend_info, default_info):
if not isinstance(frontend_info, dict) or frontend_info is None:
return default_info.copy()
merged_info = {}
for key, default_value in default_info.items():
# 获取前端传入的值
frontend_value = frontend_info.get(key)
# 判断前端值是否为空None 或空字符串)
if frontend_value is None or frontend_value == "":
merged_info[key] = default_value
else:
merged_info[key] = frontend_value
# 先复制默认字典
merged_info = default_info.copy()
# 用前端字典完全覆写
merged_info.update(frontend_info)
return merged_info
def merge_dicts(dict1, dict2):
# 创建一个新的字典来存储合并结果
merged_dict = {}
# 遍历第一个字典
for key, value_list in dict1.items():
if key in dict2:
# 如果键在第二个字典中存在,合并两个列表
merged_dict[key] = value_list + dict2[key]
else:
# 如果键在第二个字典中不存在,直接使用第一个字典的值列表
merged_dict[key] = value_list
# 遍历第二个字典
for key, value_list in dict2.items():
if key not in dict1:
# 如果键在第一个字典中不存在,直接使用第二个字典的值列表
merged_dict[key] = value_list
return merged_dict
def get_defect_str(Y_defect_list : list[dict] ) -> list:
"""将叶片缺陷信息转换为一条条描述信息
Args:
Y_defect_list (list):[
{
'record' : {'defectId': '02d892a178a82561bb565559102c7a58', 'imageId': '41543f531be24522b7bec741b9a483a2', 'defectName': '手动添加的缺陷1', 'defectCode': None, 'partName': '叶片1', 'defectTypeLabel': '表面裂纹', 'defectType': 'bmlw', 'defectLevelLabel': '轻微缺陷', 'defectLevel': 'SLIGHT', 'defectPosition': '', 'description': '手动添加的缺陷,请填写详细描述', 'repairIdea': '建议进行进一步检查', 'labelInfo': None, 'markInfo': {'label': None, 'clsId': None, 'bbox': None, 'confidence': 0.0}}
'imagePath' : '/image/path'
},
....
]
Returns:
result (list):
[
"叶片1表面裂纹轻微缺陷位于{defectPosition},建议进行进一步检查",
],
...
"""
result = []
for item in Y_defect_list:
record = item['record']
defect_type_label = record.get('defectTypeLabel', '未知类型')
defect_level_label = record.get('defectLevelLabel', '未知等级')
defect_position = record.get('defectPosition', '未知位置')
repair_idea = record.get('repairIdea', '无建议')
defect_description = f"{defect_type_label}{defect_level_label}缺陷,位于{defect_position}{repair_idea}"
result.append(defect_description)
return result
def safe_get(data, *keys, default=None):
"""
递归安全访问嵌套字典的键如果中间键不存在则返回默认值
Args:
data (dict): 要访问的字典
*keys: 要访问的键可以是多个 "叶片1", "裂纹"
default: 如果键不存在返回的默认值默认None
Returns:
如果所有键都存在返回对应的值否则返回 default
"""
if not keys or data is None:
return data if data is not None else default
current_key = keys[0]
if isinstance(data, dict):
return safe_get(data.get(current_key), *keys[1:], default=default)
else:
return default
def is_frozen():
return hasattr(sys, 'frozen') or hasattr(sys, '_MEIPASS')
def get_resource_path(relative_path):
""" 获取资源的路径 """
if is_frozen():
base_path = sys._MEIPASS
else:
base_path = os.path.abspath(".")
return os.path.join(base_path, relative_path)
def get_defedct_info(defect_dict):
"""获取按缺陷类型分类的缺陷信息
Args:
defect_dict (dict):
{'叶片1':
{'DEFECT':[{'record':
{
'defectId': '02d892a178a82561bb565559102c7a58',
'imageId': '41543f531be24522b7bec741b9a483a2',
'defectName': '手动添加的缺陷1',
'defectCode': None,
'partName': '叶片1',
'defectTypeLabel': '表面裂纹',
'defectType': 'bmlw',
'defectLevelLabel': '轻微缺陷',
'defectLevel': 'SLIGHT',
'defectPosition': '',
'description': '手动添加的缺陷,请填写详细描述',
'repairIdea': '建议进行进一步检查',
'labelInfo': None,
'markInfo': {'label': None, 'clsId': None, 'bbox': None, 'confidence': 0.0},
'imagePath': '/static/image/temp/out-work/12bc30fb209f3af3bf530541c5b062bd/F02_机组10060_叶片叶片PS面距叶根15.5米处轴向裂纹轴向260mm弦向20mm。DJI_20250526090227_0052_Z.png'
},
...
},]
}
},
...
Returns:
result (dict):
{
'叶片1': { #按叶片分记录
'表面裂纹': { 'SLIGHT': #按类型分等级,等级从'defectLevel'获取
[ #按等级分记录
{
record:{...}
}, #一张图片一个记录
...
]
...
}
}
}
result2 (dict):
{
'叶片1': { #按叶片分记录
'表面裂纹': [ #按类型分记录
"""
result = {} #缺陷等级数量列表
result2 = {} #缺陷树状列表
for part_name, part_info in defect_dict.items():
if part_name not in result:
result[part_name] = {}
result2[part_name] = {}
for defect_type, defect_list in part_info.items():
for defect_dict in defect_list:
record = defect_dict['record']
type_label = record['defectTypeLabel']
level = record['defectLevel']
if type_label not in result[part_name]:
result2[part_name][type_label] = []
if type_label not in result[part_name]:
result[part_name][type_label] = {}
if level not in result[part_name][type_label]:
result[part_name][type_label][level] = []
result[part_name][type_label][level].append(defect_dict)
p = record['defectPosition']
t = record['defectTypeLabel']
l = record['defectLevelLabel']
d = record['description']
did = record['defectId']
iid = record['imageId']
detail = get_defect_detail(did)
a = detail.get('axial', '')
c = detail.get('chordwise', '')
area = f"轴向:{a}mm弦向{c}mm"
record_info = [f"{p}{t}{l}{d}", area, iid]
result2[part_name][type_label].append(record_info)
return result, result2
def get_defect_json(defect_info: dict, Y_code: list[str], turbine_code: str) -> dict:
"""将缺陷信息转换为表格形式的JSON格式
Args:
defect_info: 结构为 {叶片1/2/3: {缺陷类型: [{'record': {...}}, ...]}}
Y_code: 叶片编号列表 ['1001', '1002', '1003']
turbine_code: 机组编号 'G01'
Returns:
{
"type": "table",
"content": {
"rows": 总行数,
"cols": 4,
"merged_cells": [合并信息],
"cells": [表格内容]
}
}
"""
# 初始化表格
cells = []
merged_cells = []
# 默认设置
default_border = {
"top": {"style": "single", "size": "4", "color": "000000"},
"left": {"style": "single", "size": "4", "color": "000000"},
"bottom": {"style": "single", "size": "4", "color": "000000"},
"right": {"style": "single", "size": "4", "color": "000000"}
}
# 1. 计算总行数(先计算再创建表头)
total_defect_rows = sum(len(defects) for blade_defects in defect_info.values()
for defects in blade_defects.values())
total_rows = 1 + total_defect_rows # 表头 + 数据行
# 2. 创建表头行第0行
header_row = [
create_cell(0, 0, "机组号", default_border),
create_cell(0, 1, "叶片号", default_border),
create_cell(0, 2, "损坏描述", default_border),
create_cell(0, 3, "缺陷面积", default_border)
]
cells.append(header_row)
# 3. 填充数据行从第1行开始
current_data_row = 1
# 合并机组号(如果有多行数据)
if total_defect_rows > 1:
merged_cells.append({
"start_row": 1, # 第2行(0-based中的1)
"start_col": 0,
"end_row": total_rows - 1, # 最后一行(0-based)
"end_col": 0
})
# 填充机组号(仅第一行数据行)
if total_defect_rows > 0:
first_data_row = [
create_cell(1, 0, turbine_code, default_border,
is_merged=total_defect_rows>1, is_primary=True),
None, None, None
]
cells.append(first_data_row)
# 按叶片顺序填充数据
for blade_idx, (blade_key, blade_defects) in enumerate(defect_info.items(), 1):
blade_code = Y_code[blade_idx - 1]
blade_defect_count = sum(len(defects) for defects in blade_defects.values())
if blade_defect_count > 1:
merged_cells.append({
"start_row": current_data_row, # 当前行(0-based)
"start_col": 1,
"end_row": current_data_row + blade_defect_count - 1, # 结束行(0-based)
"end_col": 1
})
# 填充缺陷数据
for defect_type, records in blade_defects.items():
for record in records:
# 确保不超过总行数
if current_row >= total_rows:
break
description = record.get('record', {}).get('损坏描述', '待填写')
area = record.get('record', {}).get('缺陷面积', '待填写')
is_primary = (not merged_cells or
current_row == merged_cells[-1]["start_row"])
row_data = [
None, # 机组号(已合并)
create_cell(current_row, 1, blade_code, default_border,
is_merged=blade_defect_count>1, is_primary=is_primary),
create_cell(current_row, 2, str(description), default_border),
create_cell(current_row, 3, str(area), default_border)
]
cells.append(row_data)
current_row += 1
# 填充空单元格
for row_idx, row in enumerate(cells):
for col_idx in range(4):
if row[col_idx] is None:
row[col_idx] = create_cell(row_idx, col_idx, "", default_border)
# 最终行数检查(调试用)
print(f"调试信息: 预期行数={total_rows}, 实际行数={len(cells)}")
print(f"缺陷信息统计: 叶片数={len(defect_info)}, 总缺陷数={total_defect_rows}")
return [{
"type": "table",
"content": {
"rows": len(cells), # 使用实际行数而非计算行数
"cols": 4,
"merged_cells": merged_cells,
"cells": cells
}
}]
def create_cell(row, col, text, border, is_merged=False, is_primary=False):
"""创建标准化的单元格结构"""
return {
"row": row,
"col": col,
"is_merged": is_merged,
"alignment": "center",
"border": border,
"merge_info": {"is_primary": is_primary},
"content": [{
"alignment": "center",
"runs": [{
"text": text,
"font": {
"name": "宋体",
"size": 12,
"bold": False,
"italic": False,
"underline": False,
"color": {"r": 0, "g": 0, "b": 0}
}
}]
}]
}
from typing import Dict, List, Any
def tree_dict_to_table_data(tree: Dict) -> List[List[Any]]:
"""
将树状字典转换为二维表格数据保留原始类型
参数:
tree: 树状字典结构为dict[dict[...[list]]]
返回:
二维列表每个子列表代表一条路径元素类型与输入一致确保最内层列表被展开
"""
result = []
def traverse(node: Dict, path: List[Any]) -> None:
for key, value in node.items():
current_path = path + [key] # 保留键的原始类型
if isinstance(value, dict):
traverse(value, current_path)
elif isinstance(value, list):
for item in value:
if isinstance(item, list):
# 如果列表项本身是列表,则展开它
result.append(current_path + item)
else:
# 否则作为单个元素添加
result.append(current_path + [item])
else:
result.append(current_path + [value]) # 保留值的原始类型
traverse(tree, [])
return result
def defect_list_addtitle(list_data: List[List[str]], turbine_code: str):
"""
给缺陷列表添加标题行
"""
list_data.insert(0, DEFECT_TABLE)
for i,l1 in enumerate(list_data):
if i == 0:
continue
l1.insert(0, turbine_code)
return list_data

View File

@ -1,22 +1,10 @@
"""
缺陷图目录格式
缺陷图期望格式 _隔开
外部内部命名格式都如下
图片名xuhao_缺陷类型_缺陷位置_缺陷尺寸_可见程度_紧急程度_危重等级_维修建议
涂层损伤_叶片ps面距叶根3m处_缺陷尺寸弦向100mm轴向800mm_轻微_紧急_重要_建议打磨维修
每个的选项见我发的图
防雷
轮毂至塔基导通阻值_169mΩ
缺陷例轮毂至塔基未导通 #即标明未导通即可
"""
DEFAULT_BASE_INFO = { #项目基本信息
"turbine_id" : None,
}
DEFAULT_BAOGAO_INFO = {
#目录
'shengcheng_dir': "", #报告生成的路径
'muban_dir': "", #文档模板存放路径
"dianxing_enum" : "TYPICAL",
"quexian_enum" : "DEFECT",
@ -31,15 +19,11 @@ DEFAULT_BAOGAO_INFO = {
"baogaoCheck" : "未审核",
'key_words': '缺,损,裂,脱,污', #关键字,用于汇总图的名字包含缺陷时标红,匹配逻辑为正则匹配单个字则为红 后续可优化
"shigong_fangan" : "None",
"shigong_fangan" : None,
'jiancha_renyuan': '张三',
"check_date" : None,
}
class JIANCHA_ENUM :
class WAIBU:
PART = "无人机外部高精度飞行"
#NEIRONG =
class SHIGONG_FANGAN_ENUM :
class WAIBU:
GONGZUO_NEIRONG = "无人机叶片外观巡检"
@ -70,100 +54,192 @@ class SHIGONG_FANGAN_ENUM :
FEISHOURENYUAN_PEIZHI = "1人主检飞手1人"
LUNGUZUOYERENYUAN_PEIZHI = "2人轮毂作业检查2人"
DEFAULT_BASE_INFO = { #项目基本信息
#项目概况
'jituan_jianxie': '甲方集团',
'jia_Company': '甲方公司名',
'jizu_num': '项目规格(台)',
'fengchang_name': '风场名称',
'fengchang_location': '风场位置',
'jizu_xinghao': '机组型号', #机组的型号
#乙方信息
'yi_Company': '乙方公司名',
'fuzeren': '甲方负责人(吴明洲)',
'phone_fuzeren': '联系电话18807109269 ',
}
oneproject = {
"status": 200,
"data": {
"projectId": "96e0debf78187300f144d7f3450a2477",
"projectName": "三峡能源阿城万兴风电场防雷通道检测项目",
"coverUrl": "",
"farmName": "三峡能源阿城万兴风电场",
"farmAddress": "哈尔滨市阿城区",
"client": "辽宁信达检测有限公司",
"clientContact": "李经理",
"clientPhone": "13504783720",
"inspectionUnit": "武汉市迪特影像科技有限公司",
"inspectionContact": "吴名州",
"inspectionPhone": "18807109269",
"scale": "",
"turbineModel": "",
"constructorIds": "5709ccfece2685090ff700a3469f2539,a76d78f1325deda1790a12bdad4aad4e",
"auditorId": "ca37c4337df8673a5c045b6c25acf74a",
"qualityOfficerId": "862e027910c2562d2b67d88ec33d77ba",
"projectManagerId": "fbaa9e0aecf2ce287138c38a4b654085",
"constructionTeamLeaderId": "",
"status": 0,
"startDate": "",
"endDate": "",
"constructorName": "",
"auditorName": "李四",
"qualityOfficerName": "辛奇",
"projectManagerName": "张三",
"constructionTeamLeaderName": "",
"statusLabel": "待施工"
from docx.oxml.ns import qn
from docx.shared import Pt
class TEMPLATE_HEADER:
class JINFENG_HEADER:
ENUM = 'JF'
PIC_DIR = './muban/jf_header.png'
PARA =' Q/GW 3LC-FJFW21-2022-BY14'
QN = qn('w:eastAsia')
FONT = 'Arial'
PT = Pt(9)
class FANGLEI:
TYPICAL_LIST = [
"防雷导通测试\n叶尖至塔基测试阻值({Resistance}",
"防雷导通测试\n叶尖与无人吊篮平台接触良好",
"防雷导通测试\n叶尖至塔基检测导线线组值"
]
class WAIBU:
TYPICAL_LIST = [
"外观检查(迎、背风面是否有漆面脱落、裂纹等)",
"外观检查(前、后缘;如前缘漆面脱落、合模缝开裂等)",
"叶片防雨环检查",
"叶尖接闪器、排水孔检查,如接闪器损伤、雷击熔融;流水孔堵塞",
]
class NEIBU:
TYPICAL_LIST = [
"叶片铭牌",
"根部检查(挡板、盖板检查是否损坏、金属件是否丢失等)",
"避雷系统检查(避雷线是否断裂、雷电记录卡是否缺失等)",
"前后缘检查(前缘粘接及补强是否有开裂、裂纹)",
"上下蒙皮检查(蒙皮是否有褶皱、发白、折痕、分层、裂纹等)",
"腹板检查(腹板是否变形、偏移,腹板粘接是否开裂、裂纹等)",
]
class DT_HEADER:
ENUM = 'DT'
PIC_DIR = './muban/dt_header.png'
PARA = ''
QN = qn('w:eastAsia')
FONT = '宋体(中文正文)'
PT = Pt(9)
MUBAN_DIR = 'muban'
JF_HEIGHT = 297
JF_WIDTH = 210
JF_L_MARGIN = 20
JF_R_MARGIN = 20
JF_T_MARGIN = 10
JF_B_MARGIN = 10
JINFENG_COMPANY = "金风科技股份有限公司"
#json表格样式宏定义
STYLE_CONFIG = {
"alignment": "center",
"font": {
"name": "宋体",
"size": 8,
"bold": False,
},
"msg": "",
"code": 200,
"success": True
}
onejizu = {
"status": 200,
"data": [
{
"turbineId": "183463dbf40d9278549a76b82b175dd9",
"projectId": "96e0debf78187300f144d7f3450a2477",
"projectName": "三峡能源阿城万兴风电场防雷通道检测项目",
"turbineName": "一期012号",
"turbineCode": "00000",
"turbineDesc": "一期012号全新设备",
"turbineManufacturer": "",
"turbineModel": "",
"turbineCoverUrl": ""
}
],
"msg": "",
"code": 200,
"success": True
}
yepian = {
"status": 200,
"data": [
{
"partId": "12bc30fb209f3af3bf530541c5b062bc",
"projectId": "96e0debf78187300f144d7f3450a2477",
"projectName": "三峡能源阿城万兴风电场防雷通道检测项目",
"turbineId": "183463dbf40d9278549a76b82b175dd9",
"turbineName": "一期012号",
"partName": "叶片2",
"partCode": "0001",
"partType": "VANE-2",
"partTypeLabel": "叶片2"
"border": {
"top": {"style": "single", "size": "4", "color": "000000"},
"bottom": {"style": "single", "size": "4", "color": "000000"},
"left": {"style": "single", "size": "4", "color": "000000"},
"right": {"style": "single", "size": "4", "color": "000000"}
},
{
"partId": "12bc30fb209f3af3bf530541c5b062bd",
"projectId": "96e0debf78187300f144d7f3450a2477",
"projectName": "三峡能源阿城万兴风电场防雷通道检测项目",
"turbineId": "183463dbf40d9278549a76b82b175dd9",
"turbineName": "一期012号",
"partName": "叶片1",
"partCode": "0000",
"partType": "VANE-1",
"partTypeLabel": "叶片1"
"shading": {
"color": "FFFFFF"
}
],
"msg": "",
"code": 200,
"success": True
}
TITLE_STYLE_CONFIG = {
"alignment": "center",
"font": {
"name": "宋体",
"size": 9,
"bold": False,
},
"border": {
"top": {"style": "single", "size": "4", "color": "000000"},
"bottom": {"style": "single", "size": "4", "color": "000000"},
"left": {"style": "single", "size": "4", "color": "000000"},
"right": {"style": "single", "size": "4", "color": "000000"}
},
"shading": {
"color": "FFFFFF"
}
}
DEFECT_TABLE_TITLE = '叶片故障信息表'
DEFECT_TABLE = ['机组号', '叶片编号', '损伤名称', '损坏描述', '面积/S', '备注']
USE_TOOL_TABLE_TITLE = '使用工器具记录'
USE_TOOL_TABLE = ['序号', '工器具名称', '型号规格', '数量', '用途', '备注']
class USE_TOOL_ENUM:
class OUT:
LIST = [
["无人机", "大疆 M30", "1", "叶片外观检查观测"]
]
class LIGHT:
LIST = [
["智能无人悬吊系统", "DT01", "1", "叶片导通测试"],
["检测导线", "/", "3", "叶片导通测试"],
["电阻仪", "S1950", "1", "叶片导通测试"]
]
class IN:
LIST = [
["头灯", "/", "2", "轮毂叶片内照明"],
["含氧量检测仪", "/", "1", "叶片内部环境检测"],
["活动扳手", "17-19", "2", "拆卸·禁锢盖板螺栓"],
["手机", "/", "2", "拍照记录"],
["叶片内部爬壁机器人", "DT02", "1", "拍照记录"]
]
from enum import Enum
from docx.shared import RGBColor
class DocxColors(Enum):
"""常用颜色枚举"""
BLACK = RGBColor(0, 0, 0)
WHITE = RGBColor(255, 255, 255)
RED = RGBColor(255, 0, 0)
GREEN = RGBColor(0, 255, 0)
BLUE = RGBColor(0, 0, 255)
YELLOW = RGBColor(255, 255, 0)
PURPLE = RGBColor(128, 0, 128)
ORANGE = RGBColor(255, 165, 0)
GRAY = RGBColor(128, 128, 128)
DARK_RED = RGBColor(139, 0, 0)
DARK_GREEN = RGBColor(0, 100, 0)
DARK_BLUE = RGBColor(0, 0, 139)
LIGHT_BLUE = RGBColor(173, 216, 230)
PINK = RGBColor(255, 192, 203)
BROWN = RGBColor(165, 42, 42)
# 微软Office常用颜色
MS_BLUE = RGBColor(46, 116, 181)
MS_ORANGE = RGBColor(247, 150, 70)
MS_GREEN = RGBColor(80, 175, 74)
MS_RED = RGBColor(217, 83, 79)
MS_GRAY = RGBColor(166, 166, 166)
# 获取颜色值
@property
def rgb(self):
return self.value
DT_EADING_1_CONFIG = {
"alignment": "left",
"font" : {
"name" : "宋体",
"size" : Pt(14),
"bold" : True,
"color" : DocxColors.BLACK.rgb
},
}
HEADING_1_CONFIG = {
"alignment": "left",
"font" : {
"name" : "宋体",
"size" : Pt(11),
"bold" : True,
"color" : DocxColors.BLACK.rgb
},
}
HEADING_2_CONFIG = {
"alignment": "left",
"font" : {
"name" : "宋体",
"size" : Pt(10),
"bold" : True,
"color" : DocxColors.BLACK.rgb
},
}
HEADING_3_CONFIG = {
"alignment": "left",
"font" : {
"name" : "宋体",
"size" : Pt(9),
"bold" : True,
"color" : DocxColors.BLACK.rgb
},
}

View File

@ -6,14 +6,21 @@ import json, re
from typing import Dict, List, Optional, Any
from docx import Document
from core.tables import copy_table
from utils.file_utils import check_file_writeable, ensure_docx_extension, create_document_copy
from utils.document_utils import get_document_properties, extract_document_text, get_document_structure
from utils.document_utils import get_document_properties, extract_document_text, get_document_structure, clear_header
from core.styles import ensure_heading_style, ensure_table_style
from docx.oxml.shared import qn
from docx.oxml import OxmlElement
from tools.content_tools import search_and_replace,add_picture_to_table
from docx.oxml import OxmlElement, parse_xml
from tools.content_tools import search_and_replace,add_picture_to_table,add_picture
from tools.Get_Json import get_full_picture_url
from tools.get_pictures import resize_and_reduce_quality, get_template_pic
from tools.defines import *
from docx.enum.section import WD_SECTION
from tools.json_to_docx import list_to_json_with_merges, json_to_docx
async def create_document(filename: str, title: Optional[str] = None, author: Optional[str] = None) -> str:
async def create_document(filename: str, title: Optional[str] = None, author: Optional[str] = None, section_args: Optional[Dict[str, Any]] = None) -> str:
"""创建一个包含可选元数据的新Word文档。
参数:
@ -43,6 +50,15 @@ async def create_document(filename: str, title: Optional[str] = None, author: Op
# 更改纸张大小为A4
from docx.shared import Mm, Inches
sections = doc.sections
if section_args:
for section in sections:
section.page_height = Mm(section_args.get('page_height', 297))
section.page_width = Mm(section_args.get('page_width', 210))
section.left_margin = Mm(section_args.get('left_margin', 20))
section.right_margin = Mm(section_args.get('right_margin', 20))
section.top_margin = Mm(section_args.get('top_margin', 10))
section.bottom_margin = Mm(section_args.get('bottom_margin', 10))
else:
for section in sections:
section.page_height = Mm(297)
section.page_width = Mm(210)
@ -150,13 +166,13 @@ def add_documents(target_filename: str, source_filename: str) -> str:
target_doc = Document(target_filename)
source_filename = ensure_docx_extension(source_filename)
source_doc = Document(source_filename)
for source_paragraph in source_doc.paragraphs:
new_paragraph = target_doc.add_paragraph(source_paragraph.text)
new_paragraph.style = target_doc.styles['Normal'] # Default style
#获取合并等样式2025427
new_paragraph.alignment = source_paragraph.alignment
print(f"Source paragraph alignment: {source_paragraph.alignment}")
# Try to match the style if possible
try:
@ -192,7 +208,7 @@ def add_documents(target_filename: str, source_filename: str) -> str:
def write_table(target_filename: str, rows: int, cols: int, table_num: int, data: Optional[List[List[str]]] = None, ifadjustheight: Optional[bool] = True, height: Optional[float] = 1, key_words: re.Pattern[str] = None, ALIGMENT: Optional[str] = 'CENTER') -> Document:
def write_table(target_filename: str, rows: int, cols: int, table_num: int, data: Optional[List[List[str]]] = None, ifadjustheight: Optional[bool] = True, height: Optional[float] = None, key_words: re.Pattern[str] = None, ALIGMENT: Optional[str] = 'CENTER', width: Optional[float] = None) -> Document:
"""填写word文档里的表格返回填写后的文档
Args:
@ -203,6 +219,7 @@ def write_table(target_filename: str, rows: int, cols: int, table_num: int, data
data: 表格数据二维列表每个单元格为字符串
ifadjustheight: bool为真则表格行高自动调整
"""
table_num = -1
target_filename = ensure_docx_extension(target_filename)
# Check if target file is writeable
is_writeable, error_message = check_file_writeable(target_filename)
@ -235,46 +252,53 @@ def write_table(target_filename: str, rows: int, cols: int, table_num: int, data
for j, cell_text in enumerate(row_data):
if j >= cols + 1:
break
if cell_text is not None and cell_text.endswith((".png", ".jpg", ".jpeg")):
target_doc.save(target_filename)
target_doc.tables[table_num].cell(i,j).text = ""
print(f"在[{i},{j}]处添加图片{cell_text}")
if os.path.exists(cell_text):
if height:
target_doc.tables[table_num].cell(i,j).add_paragraph().add_run().add_picture(cell_text, height=Inches(height), width=Inches(width))
else:
target_doc.tables[table_num].cell(i,j).add_paragraph().add_run().add_picture(cell_text,)
continue
else:
print(f"本地没有图片{cell_text},从服务器下载图片")
if height:
add_picture_to_table(target_doc, target_filename, i, j, cell_text, height=Inches(height), width=Inches(width))
else:
add_picture_to_table(target_doc, target_filename, i, j, get_full_picture_url(cell_text))
continue
if str(cell_text) == "": continue
print(f"在[{i},{j}]处写入{str(cell_text)}")
print(f"在的[{i},{j}]处写入{str(cell_text)}")
target_doc.tables[table_num].cell(i,j).text = str(cell_text)
print(key_words, cell_text)
if key_words and key_words.search(str(cell_text)):
print(f'{cell_text}包含关键之,已置红')
print(f'{cell_text}包含关键,已置红')
target_doc.tables[table_num].cell(i,j).paragraphs[0].runs[0].font.color.rgb = RGBColor(255, 0, 0)
target_doc.tables[table_num].cell(i,j).paragraphs[0].runs[0].font.name = "Times New Roman" #设置英文字体
target_doc.tables[table_num].cell(i,j).paragraphs[0].runs[0].font.size = Pt(10.5) # 字体大小
target_doc.tables[table_num].cell(i,j).paragraphs[0].runs[0]._element.rPr.rFonts.set(qn('w:eastAsia'), '仿宋') #设置中文字体
target_doc.tables[table_num].cell(i,j).paragraphs[0].runs[0].font.size = Pt(9) # 字体大小
target_doc.tables[table_num].cell(i,j).paragraphs[0].runs[0]._element.rPr.rFonts.set(qn('w:eastAsia'), '宋体') #设置中文字体
if ALIGMENT == 'CENTER':
target_doc.tables[table_num].cell(i,j).paragraphs[0].paragraph_format.alignment = WD_TABLE_ALIGNMENT.CENTER
elif ALIGMENT == 'LEFT':
target_doc.tables[table_num].cell(i,j).paragraphs[0].paragraph_format.alignment = WD_TABLE_ALIGNMENT.LEFT
target_doc.tables[table_num].cell(i,j).vertical_alignment = WD_ALIGN_VERTICAL.CENTER
if ifadjustheight:
target_doc.tables[table_num].rows[i].height = Cm(height)
except Exception as e:
print(f"写入{target_filename}tables.cell({i},{j})失败:{str(e)}")
print("表格写入完成")
return target_doc
def set_document_para(target_doc: Document) -> Document:
"""设置文档的段落格式
"""
paragraphs_to_remove = []
for i, paragraph in enumerate(target_doc.paragraphs):
if i <= 11:
continue
if not paragraph.text.strip():
paragraphs_to_remove.append(paragraph)
for paragraph in paragraphs_to_remove:
p = paragraph._element
p.getparent().remove(p)
return target_doc
async def add_table_to_document(target_filename: str, source_filename: str, rows: int, cols: int, table_num: int, data: Optional[List[List[str]]] = None, ifadjustheight: Optional[bool] = True, height: Optional[float] = 1, key_words: re.Pattern[str] = None, ALIGMENT: Optional[str] = 'CENTER') -> str:
def add_table_to_document(target_filename: str, source_filename: str, rows: int, cols: int, table_num: int, data: Optional[List[List[str]]] = None, ifadjustheight: Optional[bool] = True, height: Optional[float] = None, key_words: re.Pattern[str] = None, ALIGMENT: Optional[str] = 'CENTER', if_merge : Optional[bool] = True, Report_Enum = 'DT', if_para : Optional[bool] = True, width : Optional[float] = None) -> str:
"""复制源文件中的文字与表格(先文字后表格格式)到目标文档
Args:
target_filename: 目标文档路径
@ -289,13 +313,14 @@ async def add_table_to_document(target_filename: str, source_filename: str, rows
target_filename = ensure_docx_extension(target_filename)
source_filename = ensure_docx_extension(source_filename)
source_doc = Document(source_filename)
target_doc = Document(target_filename)
if if_para:
try:
# Copy all paragraphs
for paragraph in source_doc.paragraphs:
# Create a new paragraph with the same text and style
new_paragraph = target_doc.add_paragraph(paragraph.text)
new_paragraph.style = target_doc.styles['Normal'] # Default style
#获取合并等样式2025427
new_paragraph.alignment = paragraph.alignment
@ -325,56 +350,66 @@ async def add_table_to_document(target_filename: str, source_filename: str, rows
# 检查 run.font.name 是否为 None
if run.font.name is None:
# 设置默认的中文字体名称
run.font.name = '宋体(中文正文)' # 或者使用其他你喜欢的中文字体
run.font.name = '宋体(中文正文)'
rFonts.set(qn('w:eastAsia'), run.font.name)
new_run.font.color.rgb = run.font.color.rgb
# Font size if specified
if run.font.size:
new_run.font.size = run.font.size
# 复制分页符处理w:br标签
for element in run._element:
if element.tag.endswith('br'):
br_type = element.get(qn('type'), '')
if br_type == 'page':
new_br = OxmlElement('w:br')
new_br.set(qn('type'), 'page')
new_run._element.append(new_br)
except Exception as e:
print(f"添加表格前文章失败:{str(e)}")
try:# Copy all tables
from core.tables import copy_table
copy_table(source_doc.tables[0], target_doc, ifadjustheight, height)
copy_table(source_doc.tables[0], target_doc, ifadjustheight, height, if_merge, REPORT_ENUM=Report_Enum)
except Exception as e:
print(f"添加表格失败:{str(e)}")
print(f"{target_doc}写入表格{source_doc.tables[0]}成功")
target_doc = set_document_para(target_doc)
target_doc.save(target_filename)
target_doc = Document(target_filename)
print(f"{target_doc}写入表格{source_doc.tables[0]}成功")
if data:
try:
target_doc = write_table(target_filename, rows, cols, table_num, data, ifadjustheight, height, key_words, ALIGMENT)
target_doc = write_table(target_filename, rows, cols, table_num, data, ifadjustheight, height, key_words, ALIGMENT, width)
except Exception as e:
print(f"{target_filename}写入{data}失败:{str(e)}")
target_doc.save(target_filename)
return target_doc,f"{target_filename}添加表格{source_doc}成功"
async def add_table_and_replace(target_filename: str, source_filename: str, ifadjustheight: Optional[bool] = True, list_to_replace: dict = {}, height: Optional[float] = 1):
from docx.document import Document as Document_
def add_table(target_filename: str, source_filename : str | Document_, if_merge = True, REPORT_ENUM = 'DT'):
if isinstance(source_filename, str):
output_doc = copy_table(Document(source_filename).tables[0], Document(target_filename), True, if_merge=if_merge, REPORT_ENUM = REPORT_ENUM)
output_doc.save(target_filename)
else:
try:
output_doc = copy_table(source_filename.tables[0], Document(target_filename), True, if_merge=if_merge, REPORT_ENUM = REPORT_ENUM)
output_doc.save(target_filename)
except Exception as e:
print(f"添加表格失败:{str(e)}")
return f"{target_filename}添加表格{source_filename}成功"
def add_table_and_replace(
target_filename: str,
source_filename: str,
ifadjustheight: Optional[bool] = True,
list_to_replace: dict = {},
height: Optional[float] = 1,
no_para : Optional[bool] = False,
REPORT_ENUM = 'DT'):
"""复制源文件中的文字与表格(先文字后表格格式)到目标文档
Args:
target_filename: 目标文档路径
source_doc: 源文档路径
ifadjustheight: bool为真则表格行高自动调整
list_to_replace: dict, 待替换内容和替换内容
height: float, 表格行高
no_para: bool, 无段落
"""
target_filename = ensure_docx_extension(target_filename)
source_filename = ensure_docx_extension(source_filename)
source_doc = Document(source_filename)
target_doc = Document(target_filename)
if not no_para:
try:
# Copy all paragraphs
for paragraph in source_doc.paragraphs:
@ -416,24 +451,16 @@ async def add_table_and_replace(target_filename: str, source_filename: str, ifad
if run.font.size:
new_run.font.size = run.font.size
# 复制分页符处理w:br标签
for element in run._element:
if element.tag.endswith('br'):
br_type = element.get(qn('type'), '')
if br_type == 'page':
new_br = OxmlElement('w:br')
new_br.set(qn('type'), 'page')
new_run._element.append(new_br)
except Exception as e:
print(f"添加表格前文章失败:{str(e)}")
try:# Copy all tables
from core.tables import copy_table
copy_table(source_doc.tables[0], target_doc, ifadjustheight, height)
copy_table(source_doc.tables[0], target_doc, ifadjustheight, height, REPORT_ENUM=REPORT_ENUM)
target_doc.save(target_filename)
except Exception as e:
print(f"添加表格失败:{str(e)}")
for find_text, replace_text in list_to_replace.items():
print(await search_and_replace(target_filename, find_text, replace_text))
print(search_and_replace(target_filename, find_text, replace_text))
async def merge_documents(target_filename: str, source_filenames: List[str], add_page_breaks: bool = True) -> str:
"""合并文档(文本) 表格会添加到最后
@ -557,6 +584,51 @@ async def right_align_last_three_para(target_filename: str) -> str:
except Exception as e:
return f"Failed to right align paragraphs: {str(e)}"
async def add_dynamic_table(output_doc, output_dir, table_num, TABLES, JIANCHA_XIANGQING_DIR, PICTURES, row, col, i, FLAG, xuhao):
"""创建动态表
Args:
output_doc (Document): 文档对象
output_dir (str): 输出目录
table_num (int): 表格序号
TABLES (list): 表格数据
JIANCHA_XIANGQING_DIR (str): 检查详情表目录
PICTURES (dict): 图片数据字典键为表索引值为图片路径列表
row (int): 行数
col (int): 列数
i (int): 表格序号
FLAG: 其他标志
Returns:
tuple: (i, table_num) 更新后的表格序号和表格数量
"""
for table_idx, Table in enumerate(TABLES):
print(Table)
output_doc, message = add_table_to_document(output_dir, JIANCHA_XIANGQING_DIR, row, col, i, Table, FLAG, if_para=False)
print(message)
# 获取当前表格对应的图片
current_table_pictures = PICTURES.get(table_idx, [])
print(f"开始处理图片列表: {current_table_pictures}")
for picturedir in current_table_pictures:
if picturedir is None:
print(f"图片路径为空,跳过")
continue
try:
print(f"添加 {picturedir} {type(picturedir)}到表格{table_idx}")
resize_and_reduce_quality(picturedir, picturedir)
add_picture_to_table(output_doc, output_dir, 4, 0, picturedir, i, 4.7232)
except Exception as e:
print(f"添加图片失败:{e}")
try:
print(search_and_replace(output_dir, 'tupian_xuhao', f'{xuhao}'))
except Exception as e:
print(f"替换图片序号失败:{e}")
table_num += 1
i += 1
xuhao += 1
return i, table_num, xuhao
async def process_images_table(data_dict, output_dir, start_i, JIANCHA_NEIRONG_PICTURES_TABLE, key_words = None):
"""添加对应表格且填写图片名与插入图片
@ -588,22 +660,456 @@ async def process_images_table(data_dict, output_dir, start_i, JIANCHA_NEIRONG_P
line_index += 1
print(f"当前待插入表格: {JIANCHA_NEIRONG_TEXT}")
print(f"当前表格序号为 {i}")
output_doc, message = await add_table_to_document(
output_doc, message = add_table_to_document(
output_dir, JIANCHA_NEIRONG_PICTURES_TABLE, 1, 3, i, JIANCHA_NEIRONG_TEXT, False, None, key_words
)
i += 1
else:
# 图片行(从 items 取图片路径)
print(f"当前表格序号为 {i}")
output_doc, message = await add_table_to_document(
output_doc, message = add_table_to_document(
output_dir, JIANCHA_NEIRONG_PICTURES_TABLE, 1, 3, i, None, False
)
for k in range(3):
if picture_index < picture_num:
pic_path = items[picture_index][1] # 图片路径
print(f"当前为图片表格,在(0,{k})位置插入图片: {pic_path}")
print(await add_picture_to_table(output_doc, output_dir, 0, k, pic_path, i, 1.8898))
print(add_picture_to_table(output_doc, output_dir, 0, k, pic_path, i, 1.8898))
picture_index += 1
i += 1
print(message)
return i # 返回最后使用的表格序号
async def process_server_images_table(data_list, image_source_list, output_dir, start_i, JIANCHA_NEIRONG_PICTURES_TABLE, key_words=None):
"""从服务器获取的图片数据添加对应表格且填写图片名与插入图片
逻辑按来源排序添加
Args:
data_list (list): 图片数据列表每个元素为图片路径
[
{'imageId': '41543f531be24522b7bec741b9a483a2', 'imageName': 'F02_机组10060_叶片叶片PS面距叶根15.5米处轴向裂纹轴向260mm弦向20mm。DJI_20250526090227_0052_Z.png', 'imagePath': '/static/image/temp/out-work/12bc30fb209f3af3bf530541c5b062bd/F02_机组10060_叶片叶片PS面距叶根15.5米处轴向裂纹轴向260mm弦向20mm。DJI_20250526090227_0052_Z.png', 'partId': '12bc30fb209f3af3bf530541c5b062bd', 'partName': '叶片1', 'imageResolution': '8000x6000', 'focalDistance': None, 'shootingTime': None, 'cameraManufacturer': None, 'cameraModel': None, 'weather': None, 'weatherLabel': None, 'humidness': None, 'temperature': None, 'windLevel': None, 'shootingMethod': 'UAV', 'shootingMethodLabel': '无人机航拍', 'shootingDistance': None, 'collectorName': '程启谦', 'imageSource': 'out-work', 'imageSourceLabel': '外部工作', 'imageType': 'DEFECT', 'imageTypeLabel': '缺陷影像', 'audioList': None, 'preImagePath': None, 'preTreatment': False, 'projectId': None, 'gps': None},
...
]
image_source_list (list): 来源列表每个元素为来源名称
['out-work', 'in-work', 'in-field'] (来源可能会少于3个pop出没有给出对应来源的图片)
output_dir (str): 输出路径
start_i (int): 总表格数量
JIANCHA_NEIRONG_PICTURES_TABLE (str): 二维表模板路径
key_words (re)
Returns:
int: 最后使用的表格序号
"""
# 按来源对图片数据进行分组和排序
sorted_data = {source: [] for source in image_source_list}
# 将图片数据按来源分组
for item in data_list:
source = item['imageSource']
if source in sorted_data:
# 存储图片名和图片路径的元组与非server版本保持一致
sorted_data[source].append((item['imageTypeLabel'], item['imagePath']))
i = start_i
# 按照image_source_list的顺序处理每个来源的图片
for source in image_source_list:
items = sorted_data[source]
if not items:
continue # 如果该来源没有图片则跳过
print(f"处理来源: {source}")
picture_num = len(items)
line_index = 0
picture_index = 0
for content_row in range(((picture_num + 2) // 3) * 2):
if content_row % 2 == 1:
# 文字行(从 items 取图片名)
JIANCHA_NEIRONG_TEXT = [["" for _ in range(3)] for _ in range(1)] # 1行3列
for k in range(1): # 只有1行
for l in range(3):
if line_index >= picture_num:
break
JIANCHA_NEIRONG_TEXT[k][l] = items[line_index][0] # 图片名
print(f'当前为文字表格,在({k},{l})位置插入文字: {items[line_index][0]}')
line_index += 1
print(f"当前待插入表格: {JIANCHA_NEIRONG_TEXT}")
print(f"当前表格序号为 {i}")
output_doc, message = add_table_to_document(
output_dir, JIANCHA_NEIRONG_PICTURES_TABLE, 1, 3, i, JIANCHA_NEIRONG_TEXT, False, None, key_words
)
i += 1
else:
# 图片行(从 items 取图片路径)
print(f"当前表格序号为 {i}")
output_doc, message = add_table_to_document(
output_dir, JIANCHA_NEIRONG_PICTURES_TABLE, 1, 3, i, None, False
)
for k in range(3):
if picture_index < picture_num:
pic_path = items[picture_index][1] # 图片路径
print(f"当前为图片表格,在(0,{k})位置插入图片: {pic_path}")
print(add_picture_to_table(output_doc, output_dir, 0, k, get_full_picture_url(pic_path), i, 1.8898))
picture_index += 1
i += 1
print(message)
return i # 返回最后使用的表格序号
def add_header(target_dir : str, report_enum : str, if_clear_header : Optional[bool] = True, if_section : Optional[bool] = True, text : str = None):
"""添加页眉,添加封面后调用此函数,会分离页面和后续页面的节。
Args:
target_dir (str): 目标目录
start_section (int): 开始节
end_section (int): 结束节
report_enum (str): 报告类型(DT或JF)
"""
document = Document(target_dir) # 打开文档
if if_clear_header:
for section in document.sections: # 遍历所有节的页眉
clear_header(section) # 清除页眉的段落
if if_section:
print(f"文档节数:{len(document.sections)},开始往当前节后添加页眉")
document.sections[0].header.is_linked_to_previous = False # 取消页眉与上一页关联
document.add_section(WD_SECTION.NEW_PAGE)
header = document.sections[1].header
header.is_linked_to_previous = False # 取消页眉与上一页关联
else:
header = document.sections[-1].header
header.is_linked_to_previous = False # 取消页眉与上一页关联
paragraph = header.paragraphs[0] # 获取页眉的第一个段落
run = paragraph.add_run()
if report_enum == 'JF':
print("添加金风模板的页眉")
pic = run.add_picture(get_template_pic(TEMPLATE_HEADER.JINFENG_HEADER.PIC_DIR))
if text:
run = paragraph.add_run(text)
else:
run = paragraph.add_run(TEMPLATE_HEADER.JINFENG_HEADER.PARA)
run.font.name = TEMPLATE_HEADER.JINFENG_HEADER.FONT
run.font.size = TEMPLATE_HEADER.JINFENG_HEADER.PT
elif report_enum == 'DT':
print("添加迪特模板的页眉")
pic = run.add_picture(get_template_pic(TEMPLATE_HEADER.DT_HEADER.PIC_DIR))
if text:
run = paragraph.add_run(text)
else:
run = paragraph.add_run(TEMPLATE_HEADER.DT_HEADER.PARA)
run.font.name = TEMPLATE_HEADER.DT_HEADER.FONT
run.font.size = TEMPLATE_HEADER.DT_HEADER.PT
else:
print("未知模板,不添加页眉")
# 定义边框的 XML 字符串
border_xml = """
<w:pBdr xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main">
<w:bottom w:val="single" w:sz="8" w:space="1" w:color="000000"/>
</w:pBdr>
"""
# 解析 XML 并添加到段落属性
pPr = paragraph._element.get_or_add_pPr()
pBdr = parse_xml(border_xml)
pPr.append(pBdr)
print(f"文档节数:{len(document.sections)}")
document.save(target_dir) # 保存文档
from docx.enum.section import WD_ORIENT
from docx.enum.text import WD_BREAK
from docx.document import Document as Document_
def add_landscape_section(target_dir : str):
# 添加横向节
doc = Document(target_dir)
section = doc.add_section()
section.orientation = WD_ORIENT.LANDSCAPE
section.page_width, section.page_height = section.page_height, section.page_width
print(f"文档:{target_dir},添加了新横向节,页宽:{section.page_width}, 页高:{section.page_height}")
return doc
def add_section(target_dir : str) -> Document_:
doc = Document(target_dir)
section = doc.add_section()
return doc
def add_title(target_dir : str, title : str, style_config : dict = {}):
doc = Document(target_dir)
para = doc.add_paragraph()
run = para.add_run(title)
font_config = style_config.get('font', {})
run.font.name = font_config.get('name', "宋体")
run.element.get_or_add_rPr().get_or_add_rFonts().set(qn('w:eastAsia'), font_config.get('name', "宋体"))
run.font.size = font_config.get('size', Pt(14))
run.bold = font_config.get('bold', False)
para.alignment = style_config.get('alignment', WD_ALIGN_PARAGRAPH.CENTER)
print(f"添加新标题:{title},字体:{run.font.name},大小:{run.font.size},是否加粗:{run.bold}")
return doc
def merge_documents(target_dir : str, source_dirs : List[str]):
"""合并多个文档、图片
Args:
target_dir (str): 目标目录
source_dirs (List[str]): 源目录列表
"""
# 打开目标文档
document = Document(target_dir)
for dir in source_dirs:
if dir.endswith('.docx'):
print(add_documents(target_dir, dir))
if dir.endswith('.jpg') or dir.endswith('.png'):
print(add_picture(target_dir, dir, is_center=True))
def add_defect_info_table(output_dir, defect_info, MUBAN_DIR, total_table_num, REPORT_ENUM):
"""将defectinfo写入word文档
Args:
output_dir: 输出目录
defect_info: 缺陷信息
{
'叶片1': { #按叶片分记录
'表面裂纹': { 'SLIGHT': #按类型分等级,等级从'defectLevel'获取
[ #按等级分记录
{
record:{...}
}, #一张图片一个记录
...
]
...
}
}
total_table_num: 总表格数量
Returns:
table_num: 表格数量
功能表头分别为序号缺陷类型等级损伤数量处理建议备注是否完成处理 默认否
总体就按等级分类但损伤数量需要说明为n支m处n为有这个缺陷和等级的叶片数m为有这个缺陷和等级的所有叶片加起来的缺陷数
处理建议从record[0]的值中获取
"""
table_data = [] # 存储所有表格数据的列表,每个元素是一个字典 # 添加新记录(使用字典格式)
table_data.append({
"xuhao" : "序号", # 序号
"defecttype" : "损伤名称", # 缺陷类型
"level" : "损伤等级", # 等级
"defectnuminfo" : "损伤数量", # 损伤数量
"suggestion" : "处理建议", # 处理建议
"remark" : "备注(是否处理完成)" # 备注(是否完成处理)
})
# 遍历缺陷信息,整理数据
for blade_name, defects in defect_info.items():
for defect_type, levels in defects.items():
for level, records in levels.items():
if records: # 如果有记录
# 计算损伤数量n支m处
blade_count = 1 # 当前叶片就是1支
defect_count = len(records) # 当前叶片的缺陷数量
# 查找是否已有同类型同等级的数据
found = False
for item in table_data:
if item["defecttype"] == defect_type and item["level"] == level:
# 更新已有记录
current_n = item["defectnuminfo"]
parts = current_n.split("")
existing_blades = int(parts[0])
existing_defects = int(parts[1].split("")[0])
item["defectnuminfo"] = f"{existing_blades + blade_count}{existing_defects + defect_count}"
found = True
break
if not found:
# 获取处理建议(假设第一条记录中有处理建议)
suggestion = records[0].get('record', {}).get('处理建议', '')
# 添加新记录(使用字典格式)
table_data.append({
"xuhao" : str(len(table_data)), # 序号
"defecttype" : defect_type, # 缺陷类型
"level" : level, # 等级
"defectnuminfo" : f"{blade_count}{defect_count}", # 损伤数量
"suggestion" : suggestion, # 处理建议
"remark" : "" # 备注(是否完成处理)
})
# 添加空行
for _ in range(2):
table_data.append({
"xuhao" : "", # 序号
"defecttype" : "", # 缺陷类型
"level" : "", # 等级
"defectnuminfo" : "", # 损伤数量
"suggestion" : "", # 处理建议
"remark" : "" # 备注(是否完成处理)
})
for row in table_data:
add_table_and_replace(output_dir, MUBAN_DIR, list_to_replace=row, no_para = True, ifadjustheight=True, REPORT_ENUM=REPORT_ENUM)
total_table_num += 1
return total_table_num
from docx.enum.text import WD_ALIGN_PARAGRAPH
from docx.shared import RGBColor
def add_table_title(output_dir, TITLE):
doc = Document(output_dir)
table = doc.add_table(rows=1, cols=6, style='Table Grid')
table.cell(0,0).merge(table.cell(0,5))
para = table.cell(0,0).paragraphs[0]
run = para.add_run(TITLE)
run.font.name = "宋体"
run.font.size = Pt(9)
para.alignment = WD_ALIGN_PARAGRAPH.CENTER
print(f"添加了标题表格:{TITLE}")
doc.save(output_dir)
def change_heading(output_dir, style_name, style_config = {}):
doc = Document(output_dir)
heading_style = doc.styles[style_name]
font_config = style_config.get('font', {})
heading_style.font.name = font_config.get('name', "宋体")
heading_style.element.get_or_add_rPr().get_or_add_rFonts().set(qn('w:eastAsia'), font_config.get('name', "宋体"))
heading_style.font.size = font_config.get('size', Pt(11))
heading_style.bold = font_config.get('bold', True)
heading_style.font.color.rgb = font_config.get('color', DocxColors.BLACK.rgb)
print(f"修改了标题样式:{style_name},字体:{heading_style.font.name},大小:{heading_style.font.size},是否加粗:{heading_style.bold}")
doc.save(output_dir)
from docxtpl import DocxTemplate
def add_auto_toc_at_end(doc_path):
"""使用 python-docx-template 在末尾自动生成目录"""
doc = DocxTemplate(doc_path)
# 渲染模板r.toc=True 会触发目录生成)
context = {
'r': {
'toc': True # 自动生成目录
}
}
doc.render(context)
doc.save(doc_path)
def add_jf_picture_table(
typical_picture_list: list,
defect_picture_list: list,
TYPICAL_MUBAN_DIR: str,
DEFECT_MUBAN_DIR: str,
output_dir: str,
total_table_num: int,
):
"""添加金风版本的图片展示表格
Args:
typical_picture_list: 每列有两种情况
[
[str(图片描述), str(图片描述), ...],
[str(图片地址), int(n), ...],
[str(""), str(缺陷类型), ...]
]
defect_picture_list: 只有一种情况
[
[str(图片描述), str(图片描述), ...],
[str(图片地址), str(图片地址), ...]
]
TYPICAL_MUBAN_DIR: 典型图模板路径
DEFECT_MUBAN_DIR: 缺陷图模板路径
"""
def add_table(table_type, data, rows, cols):
nonlocal total_table_num
template = TYPICAL_MUBAN_DIR if table_type == "typical" else DEFECT_MUBAN_DIR
key_words = re.compile(r'损伤|处') if table_type == "typical" else re.compile(r'损伤')
print(f"添加{table_type}表格:{data}")
add_table_to_document(
output_dir,
template,
rows, cols, total_table_num,
data,
Report_Enum='JF',
if_merge=False,
height=1.4173,
width=1.8898,
if_para=False,
key_words=key_words
)
total_table_num += 1
return list(list("" for _ in range(5)) for _ in range(rows)) # 返回新表格
print(f"获得典型图列表:{typical_picture_list}")
print(f"获得缺陷图列表:{defect_picture_list}")
# 初始化表格
typical_table = [[""] * 5 for _ in range(3)] # 三行五列
defect_table = [[""] * 5 for _ in range(2)] # 二行五列
typical_col_idx = 0
defect_count = 0 # 需要添加的缺陷图数量
pending_defects = False
# 处理典型图
while all(typical_picture_list) and all(len(lst) > 0 for lst in typical_picture_list):
desc = typical_picture_list[0].pop(0)
content = typical_picture_list[1].pop(0)
defect_type = typical_picture_list[2].pop(0)
# 检查是否需要新建表格
if typical_col_idx >= 5:
typical_table = add_table("typical", typical_table, 3, 5)
typical_col_idx = 0
# 如果有待处理的缺陷图,先处理
if pending_defects:
defect_col_idx = 0
while defect_picture_list[0] and defect_picture_list[1] and defect_count > 0:
if defect_col_idx >= 5:
defect_table = add_table("defect", defect_table, 2, 5)
defect_col_idx = 0
defect_table[0][defect_col_idx] = defect_picture_list[0].pop(0)
defect_table[1][defect_col_idx] = defect_picture_list[1].pop(0)
defect_col_idx += 1
defect_count -= 1
if defect_col_idx > 0:
defect_table = add_table("defect", defect_table, 2, 5)
pending_defects = False
# 填充典型图表
if isinstance(content, int):
typical_table[0][typical_col_idx] = desc
typical_table[1][typical_col_idx] = f"损伤有{content}处,详见下表"
typical_table[2][typical_col_idx] = f"{defect_type}{content}"
defect_count += content
pending_defects = True
else:
typical_table[0][typical_col_idx] = desc
typical_table[1][typical_col_idx] = content
typical_col_idx += 1
# 添加剩余的典型图表
if typical_col_idx > 0:
add_table("typical", typical_table, 3, 5)
# 处理剩余的缺陷图
if pending_defects and defect_picture_list[0] and defect_picture_list[1]:
defect_col_idx = 0
while defect_picture_list[0] and defect_picture_list[1] and defect_count > 0:
if defect_col_idx >= 5:
defect_table = add_table("defect", defect_table, 2, 5)
defect_col_idx = 0
defect_table[0][defect_col_idx] = defect_picture_list[0].pop(0)
defect_table[1][defect_col_idx] = defect_picture_list[1].pop(0)
defect_col_idx += 1
defect_count -= 1
if defect_col_idx > 0:
add_table("defect", defect_table, 2, 5)
return total_table_num

View File

@ -1,7 +1,8 @@
from content_tools import add_picture_to_table, search_and_replace
from content_tools import add_picture_to_table, search_and_replace,add_picture
from get_pictures import resize_and_reduce_quality
from document_tools import add_table_to_document
from document_tools import add_table_to_document,add_documents
from docx import Document
from typing import List
def fill_tables(Y_table_list, row, col, Y_Table_num, Y):
"""根据前端返回json块填写表格list并实时跟进已填写表格数量
目前只支持固定的缺陷图的填写
@ -90,7 +91,7 @@ async def process_images_table(data_dict, output_dir, start_i, JIANCHA_NEIRONG_P
if picture_index < picture_num:
pic_path = items[picture_index][1] # 图片路径
print(f"当前为图片表格,在(0,{k})位置插入图片: {pic_path}")
print(await add_picture_to_table(output_doc, output_dir, 0, k, pic_path, i, 1.8898))
print(add_picture_to_table(output_doc, output_dir, 0, k, pic_path, i, 1.8898))
picture_index += 1
i += 1
print(message)
@ -127,7 +128,7 @@ async def add_dynamic_table(output_doc, output_dir, table_num, TABLES, JIANCHA_X
try:
print(f"添加 {picturedir} {type(picturedir)}到表格{table_idx}")
resize_and_reduce_quality(picturedir, picturedir)
await add_picture_to_table(output_doc, output_dir, 4, 0, picturedir, i, 4.7232)
add_picture_to_table(output_doc, output_dir, 4, 0, picturedir, i, 4.7232)
except Exception as e:
print(f"添加图片失败:{e}")
@ -179,4 +180,3 @@ def merge_info(frontend_info, default_info):
merged_info[key] = frontend_value
return merged_info

View File

@ -2,6 +2,7 @@ import os
import math
from PIL import Image
from concurrent.futures import ThreadPoolExecutor
from tools.dataproccess import get_resource_path
def resize_and_reduce_quality(image_path, output_path, target_width = None):
try:
@ -233,33 +234,108 @@ def create_thumbnail(file_path: str, size: tuple) -> Image:
print(f"图片处理有问题:{e}")
return None
def process_picture_data(picture_data : list[dict],
image_source_to_find : list[str],
quexian_type : str,
dianxing_type : str,
other_type : str
) -> tuple[list[str], list[str]]:
def process_picture_data(picture_data : dict[list[dict]],
image_source_to_find : list[str]
) -> tuple[dict[dict[list[dict]]],int]:
"""处理从数据库获取的图片数据
Args:
picture_data (list[dict]): 图片数据
picture_data (dict[list[dict]]: 图片数据(按部件分类返回的列表也要按部件分类)
image_source_to_find (list[str]): 要查找的图片来源枚举(外内防雷)
quexian_type (str): 缺陷类型枚举值
dianxing_type (str): 典型类型枚举值
other_type (str): 其他类型枚举值
Returns:
tuple(
defct_pictures, 缺陷图列表
dianxing_pictures, 典型图列表
other_pictures, 其他图列表
filtered_picture_data, 按部件缺陷类型典型类型其他类型层层分类的图片数据
total_num 总图片数量
)
"""
#过滤目标来源的图片数据
picture_data = [pic for pic in picture_data if pic['imageSource'] in image_source_to_find]
#分别择出缺陷图和典型图
return ([pic for pic in picture_data if pic['imageType'] == quexian_type],
[pic for pic in picture_data if pic['imageType'] == dianxing_type],
[pic for pic in picture_data if pic['imageType'] == other_type],
len(picture_data))
# 过滤目标来源的图片数据
filtered_picture_data = {}
total_num = 0
for partName, values in picture_data.items():
total_num = total_num + len(values)
for pic in values:
if pic['imageSource'] in image_source_to_find:
if partName not in filtered_picture_data:
filtered_picture_data[partName] = {}
if pic['imageType'] not in filtered_picture_data[partName]:
filtered_picture_data[partName][pic['imageType']] = []
filtered_picture_data[partName][pic['imageType']].append(pic)
return filtered_picture_data, total_num
def print_data(filtered_picture_data : dict[dict[list[dict]]]) -> None:
"""打印图片数据"""
for partName, types in filtered_picture_data.items():
print(f"Part Name: {partName}")
for imageType, pictures in types.items():
print(f" Image Type: {imageType}, Number of Pictures: {len(pictures)}")
def get_records_with_pic(records: list[dict], filtered_picture_data: dict[str, dict[str, list[dict]]], defect_enum: str) -> tuple[dict[str, dict[str, list[dict]]], dict[str, list[dict]]]:
"""获取指定图片ID的记录
Args:
records (list[dict]): 记录列表每条记录包含部件名缺陷类型等信息
filtered_picture_data (dict[str, dict[str, list[dict]]]): 图片数据最外层的字典的键是部件名内层的字典的键是缺陷类型值是图片信息列表
defect_enum (str): 指定的缺陷类型
Returns:
tuple(
records_with_defect_pic: 按部件名分类的缺陷记录映射到图片的imagePath
defect_pic_with_no_records: 对应缺陷类型的图片没有记录的部件名映射
)
"""
records_with_defect_pic = {}
defect_pic_with_no_records = {}
# 收集所有filtered_picture_data中的imageId
all_image_ids = set()
for partName, types in filtered_picture_data.items():
defect_pictures = types.get(defect_enum, [])
for pic in defect_pictures:
all_image_ids.add(pic.get('imageId'))
# 过滤掉records中imageId不在all_image_ids中的记录
filtered_records = [record for record in records if record.get('imageId') in all_image_ids]
# 遍历每个部件
for partName, types in filtered_picture_data.items():
# 获取指定缺陷类型的图片列表
defect_pictures = types.get(defect_enum, [])
if defect_pictures:
# 初始化部件名的字典
if partName not in records_with_defect_pic:
records_with_defect_pic[partName] = {}
# 初始化缺陷类型的字典
records_with_defect_pic[partName][defect_enum] = []
# 遍历缺陷图片列表,找到对应的记录并添加到字典中
for pic in defect_pictures:
pic_id = pic.get('imageId')
matched_records = [record for record in filtered_records if record.get('imageId') == pic_id]
# 如果有匹配的记录,则添加到字典中
if matched_records:
for record in matched_records:
records_with_defect_pic[partName][defect_enum].append({
'record': record,
'imagePath': pic.get('imagePath')
})
else:
# 如果没有匹配的记录,则将图片信息添加到没有记录的字典中
if partName not in defect_pic_with_no_records:
defect_pic_with_no_records[partName] = []
defect_pic_with_no_records[partName].append({
'defect_enum': defect_enum,
'imagePath': pic.get('imagePath')
})
return records_with_defect_pic, defect_pic_with_no_records
def get_template_pic(pic_name : str):
"""获取模板图片路径"""
return get_resource_path(pic_name)

395
tools/json_to_docx.py Normal file
View File

@ -0,0 +1,395 @@
from docx import Document
from docx.shared import Pt, RGBColor
from docx.enum.text import WD_ALIGN_PARAGRAPH
from docx.oxml.shared import qn, OxmlElement
import json
def json_to_docx(json_data, output_dir = None):
print(f"\n开始转换JSON到DOCX文档")
if output_dir:
doc = Document(output_dir)
else:
doc = Document()
total_elements = len(json_data)
print(f"文档包含 {total_elements} 个元素(段落和表格)")
for i, element in enumerate(json_data, 1):
print(f"\n处理元素 {i}/{total_elements}: ", end="")
if element["type"] == "text":
print(f"段落 (长度: {len(element['content']['runs'])}个runs)")
add_paragraph_from_json(doc, element["content"])
elif element["type"] == "table":
rows = element["content"]["rows"]
cols = element["content"]["cols"]
merges = len(element["content"].get("merged_cells", []))
print(f"表格 ({rows}行×{cols}列, 包含 {merges} 个合并单元格)")
add_table_from_json(doc, element["content"], element.get("bold", False))
return doc
def add_paragraph_from_json(doc, para_json):
paragraph = doc.add_paragraph()
print(f" 添加段落 (对齐: {para_json['alignment']})")
# 设置段落对齐方式
alignment_map = {
"left": WD_ALIGN_PARAGRAPH.LEFT,
"center": WD_ALIGN_PARAGRAPH.CENTER,
"right": WD_ALIGN_PARAGRAPH.RIGHT,
"justify": WD_ALIGN_PARAGRAPH.JUSTIFY
}
paragraph.alignment = alignment_map.get(para_json["alignment"], WD_ALIGN_PARAGRAPH.LEFT)
# 添加文本运行(runs)
for run_idx, run_json in enumerate(para_json["runs"], 1):
run = paragraph.add_run(run_json["text"])
try:
if run_json["has_page_break"]:
import docx
run.add_break(docx.enum.text.WD_BREAK.PAGE)
except:
pass
font = run.font
print(f" 添加run {run_idx}: '{run_json['text']}' "
f"(字体: {run_json['font']['name']}, 大小: {run_json['font']['size']}, "
f"加粗: {run_json['font']['bold']}, 斜体: {run_json['font']['italic']})")
# 设置字体样式
if run_json["font"]["name"]:
font.name = run_json["font"]["name"]
run.element.rPr.rFonts.set(qn('w:eastAsia'), run_json["font"]["name"])
if run_json["font"]["size"]:
font.size = Pt(run_json["font"]["size"])
font.bold = run_json["font"]["bold"]
font.italic = run_json["font"]["italic"]
font.underline = run_json["font"]["underline"]
# 设置字体颜色
if run_json["font"]["color"]:
color = run_json["font"]["color"]
font.color.rgb = RGBColor(color["r"], color["g"], color["b"])
print(f" 设置颜色: RGB({color['r']}, {color['g']}, {color['b']})")
def add_table_from_json(doc, table_json, bold=False):
print(f" 创建表格: {table_json['rows']}× {table_json['cols']}")
table = doc.add_table(rows=table_json["rows"], cols=table_json["cols"])
table.autofit = True # 自动调整列宽和行高
# 设置表格样式为无网格线(我们将自定义边框)
table.style = 'Table Grid'
# 设置列宽
if "col_widths" in table_json and any(table_json["col_widths"]):
print(" 设置列宽...")
for col_idx, width in enumerate(table_json["col_widths"]):
if width is not None:
# 将英寸转换为Twips1英寸=1440 Twips
twips_width = int(width * 1440)
for cell in table.columns[col_idx].cells:
tc = cell._tc
tcPr = tc.get_or_add_tcPr()
tcW = tcPr.first_child_found_in("w:tcW")
if tcW is None:
tcW = OxmlElement('w:tcW')
tcPr.append(tcW)
tcW.set(qn('w:w'), str(twips_width))
tcW.set(qn('w:type'), 'dxa') # 使用绝对单位
# 设置行高
if "row_heights" in table_json and any(table_json["row_heights"]):
print(" 设置行高...")
for row_idx, height in enumerate(table_json["row_heights"]):
if height is not None:
# 将英寸转换为Twips1英寸=1440 Twips
twips_height = int(height * 1440)
tr = table.rows[row_idx]._tr
trPr = tr.get_or_add_trPr()
trHeight = OxmlElement('w:trHeight')
trHeight.set(qn('w:val'), str(twips_height))
trHeight.set(qn('w:hRule'), 'atLeast') # 或'exact'表示固定高度
trPr.append(trHeight)
# 处理合并单元格
for merge_idx, merge_info in enumerate(table_json.get("merged_cells", []), 1):
start_row = merge_info["start_row"]
start_col = merge_info["start_col"]
end_row = merge_info["end_row"]
end_col = merge_info["end_col"]
print(f" 合并单元格 #{merge_idx}: 从({start_row},{start_col})到({end_row},{end_col})")
start_cell = table.cell(start_row, start_col)
end_cell = table.cell(end_row, end_col)
start_cell.merge(end_cell)
# 填充表格内容
for row_idx, row_data in enumerate(table_json["cells"]):
for col_idx, cell_data in enumerate(row_data):
# 跳过被合并的非主单元格
if cell_data["is_merged"] and not cell_data["merge_info"]["is_primary"]:
print(f" 跳过被合并的单元格({row_idx},{col_idx})")
continue
cell = table.cell(cell_data["row"], cell_data["col"])
print(f" 处理单元格({row_idx},{col_idx}) - 对齐: {cell_data['alignment']}")
format_cell(cell, cell_data) # 统一设置单元格格式
def format_cell(cell, cell_data):
"""设置单元格完整格式"""
# 清空原有内容
for p in cell.paragraphs:
p._element.getparent().remove(p._element)
# 添加内容
for para in cell_data["content"]:
add_paragraph_from_json(cell, para)
# 设置对齐方式
set_cell_alignment(cell, cell_data)
# 设置边框
set_cell_border(cell, cell_data["border"])
# 设置背景色
if cell_data.get("shading"):
set_cell_shading(cell, cell_data["shading"])
# 设置边距
if cell_data.get("margins"):
set_cell_margins(cell, cell_data["margins"])
def set_cell_alignment(cell, cell_data):
"""设置单元格对齐(水平和垂直)"""
# 水平对齐
if cell.paragraphs:
align_map = {
"left": WD_ALIGN_PARAGRAPH.LEFT,
"center": WD_ALIGN_PARAGRAPH.CENTER,
"right": WD_ALIGN_PARAGRAPH.RIGHT,
"justify": WD_ALIGN_PARAGRAPH.JUSTIFY
}
cell.paragraphs[0].alignment = align_map.get(cell_data["alignment"], WD_ALIGN_PARAGRAPH.LEFT)
# 垂直对齐设置
tcPr = cell._tc.get_or_add_tcPr()
vAlign = OxmlElement('w:vAlign')
align_value = cell_data.get('vertical_align', 'center')
print(f" 设置垂直对齐: {align_value}")
# 确保使用有效的对齐值
valid_alignments = ['top', 'center', 'bottom']
if align_value not in valid_alignments:
align_value = 'center' # 默认值
vAlign.set(qn('w:val'), align_value)
tcPr.append(vAlign)
def set_cell_shading(cell, shading):
"""设置单元格背景色"""
tcPr = cell._tc.get_or_add_tcPr()
shd = OxmlElement('w:shd')
shd.set(qn('w:fill'), shading["color"])
if shading.get("theme"):
shd.set(qn('w:themeColor'), shading["theme"])
tcPr.append(shd)
def set_cell_margins(cell, margins):
"""设置单元格边距"""
tcPr = cell._tc.get_or_add_tcPr()
tcMar = OxmlElement('w:tcMar')
for side, margin in margins.items():
side_el = OxmlElement(f'w:{side}')
side_el.set(qn('w:w'), margin["w"])
side_el.set(qn('w:type'), margin["type"])
tcMar.append(side_el)
tcPr.append(tcMar)
def set_cell_border(cell, border_data):
"""
设置单元格边框
:param cell: 单元格对象
:param border_data: 边框数据
"""
tc = cell._tc
tcPr = tc.get_or_add_tcPr()
# 检查是否存在边框元素,不存在则创建
tcBorders = tcPr.first_child_found_in("w:tcBorders")
if tcBorders is None:
tcBorders = OxmlElement('w:tcBorders')
tcPr.append(tcBorders)
# 设置各边边框
for side in ['top', 'left', 'bottom', 'right']:
if side in border_data:
border = border_data[side]
border_el = OxmlElement(f'w:{side}')
border_el.set(qn('w:val'), border.get('style', 'single'))
border_el.set(qn('w:sz'), str(border.get('size', 4)))
border_el.set(qn('w:color'), border.get('color', '000000'))
tcBorders.append(border_el)
# 使用示例
if __name__ == "__main__":
# 假设我们已经有了之前生成的JSON数据
input_json = "output.json"
output_path = "restored.docx"
print(f"{input_json} 读取JSON数据...")
with open(input_json, "r", encoding="utf-8") as f:
json_data = json.load(f)
# 将JSON转换回DOCX
json_to_docx(json_data, output_path)
from typing import List, Dict, Any, Union
def list_to_json_with_merges(
table_data: List[List[str]],
style_config: Dict[str, Any] = None,
detect_merges: bool = True,
merge_columns: Union[int, List[int]] = None # 新增参数,控制合并哪些列
) -> Dict[str, Any]:
"""
将二维列表转换为表格JSON可选是否合并相邻相同单元格
参数:
table_data: 二维字符串列表表示的表格数据
style_config: 包含样式配置的字典可选
detect_merges: 是否检测并合并相邻相同单元格默认为True
merge_columns: 控制合并哪些列可以是
- None合并所有列默认
- int n只合并前n列
- List[int]只合并指定的列
返回:
符合表格JSON结构的字典
"""
if not table_data or not table_data[0]:
return {"type": "table", "content": {"rows": 0, "cols": 0, "cells": [], "merged_cells": []}}
rows = len(table_data)
cols = len(table_data[0])
result = {
"type": "table",
"content": {
"rows": rows,
"cols": cols,
"merged_cells": [],
"cells": [[None for _ in range(cols)] for _ in range(rows)]
}
}
# 处理merge_columns参数
columns_to_merge = set()
if merge_columns is not None:
if isinstance(merge_columns, int):
columns_to_merge = set(range(merge_columns)) # 前n列
elif isinstance(merge_columns, list):
columns_to_merge = set(merge_columns) # 指定列
for col in range(cols):
# 检查当前列是否需要合并
should_merge = detect_merges
if merge_columns is not None:
should_merge = should_merge and (col in columns_to_merge)
start_row = 0
while start_row < rows:
current_value = table_data[start_row][col]
end_row = start_row
if should_merge:
while end_row + 1 < rows and table_data[end_row + 1][col] == current_value:
end_row += 1
if should_merge and end_row > start_row:
merge_info = {
"start_row": start_row,
"start_col": col,
"end_row": end_row,
"end_col": col
}
result["content"]["merged_cells"].append(merge_info)
for row in range(start_row, end_row + 1):
cell_data = create_cell_data(
row=row,
col=col,
value=current_value,
style_config=style_config,
is_merged=True,
is_primary=(row == start_row),
merge_range=merge_info
)
result["content"]["cells"][row][col] = cell_data
else:
cell_data = create_cell_data(
row=start_row,
col=col,
value=current_value,
style_config=style_config,
is_merged=False
)
result["content"]["cells"][start_row][col] = cell_data
start_row = end_row + 1
return [result]
def create_cell_data(
row: int,
col: int,
value: str,
style_config: Dict[str, Any],
is_merged: bool = False,
is_primary: bool = False,
merge_range: Dict[str, int] = None
) -> Dict[str, Any]:
"""创建标准化单元格数据"""
cell = {
"row": row,
"col": col,
"is_merged": is_merged,
"content": create_cell_content(value, style_config),
"alignment": style_config.get("alignment", "center") if style_config else "center",
"border": style_config.get("border", {}) if style_config else {},
"shading": style_config.get("shading", {}) if style_config else {},
"margins": style_config.get("margins", {}) if style_config else {}
}
if is_merged:
cell["merge_info"] = {
"is_primary": is_primary,
"start_row": merge_range["start_row"],
"start_col": merge_range["start_col"],
"end_row": merge_range["end_row"],
"end_col": merge_range["end_col"]
}
return cell
def create_cell_content(text: str, style_config: Dict[str, Any] = None) -> List[Dict]:
"""创建单元格内容结构"""
font_config = style_config.get("font", {}) if style_config else {}
return [{
"alignment": style_config.get("alignment", "left") if style_config else "left",
"runs": [{
"text": text,
"font": {
"name": font_config.get("name", "Calibri"),
"size": font_config.get("size", 11),
"bold": font_config.get("bold", False),
"italic": font_config.get("italic", False),
"underline": font_config.get("underline", False),
"color": font_config.get("color", {"r": 0, "g": 0, "b": 0})
},
"has_page_break": False
}]
}]

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -165,3 +165,10 @@ def find_and_replace_text(doc, old_text, new_text):
count += 1
return count
def clear_header(section):
for para in section.header.paragraphs:
para.clear()