import os import random from typing import List from docx import Document from docx.enum.table import WD_ALIGN_VERTICAL from docx.enum.text import WD_ALIGN_PARAGRAPH from docx.oxml import OxmlElement from docx.shared import Pt, RGBColor, Mm, Cm, Inches from collections import defaultdict from docxtpl import DocxTemplate, InlineImage, Listing, R, RichText import jinja2 from tool.screen import cut_report_img, CutReportImgColumn # from tool.screen import * class PartDetail1_1: def __init__(self): self.time = '?' self.name = '?' self.num = '?' self.tp = '?' self.type = '?' self.stop_times = '?' self.result = '?' self.detail = '' self.devail_value = [] class PartDetail: def __init__(self): self.item1 = '?' self.item2 = '?' self.item3 = '?' self.item4 = '?' self.item5 = '?' self.item6 = '?' self.item7 = '?' self.item8 = '-' self.item9 = '?' self.item10 = '?' self.item11 = '?' self.item12 = '?' self.item13 = '?' self.item14 = '?' self.item15 = '?' self.item16 = '?' self.item17 = '?' self.item18 = '?' self.item19 = '?' self.item20 = '?' self.item21 = '?' self.item22 = '?' self.detail = '' self.detail_value = [] self.item_list = [] self.table = [] self.table_head = [] self.head = [] self.table1_head = [] self.table2_head = [] self.table1 = [] self.table2 = [] self.image = '' self.images = [] self.in_detail = '' self.in_detail_value = [] self.in_image = ImageDetail() self.out_detail = '' self.out_detail_value = [] self.out_image = ImageDetail() self.result = [] self.optimize = [] self.reason = [] self.cut_image = ImageDetail() class ImageDetail: def __init__(self): self.crossid = '' self.cross_name = '' self.time_list = [] self.tp = '' self.walk = 0 #人行横道 self.delay = 0 #延误 self.flow_in = 0 #分流 class DocDocumentContrastCtx: """路口优化对比报告""" def __init__(self, nodeid): self.nodeid = nodeid self.doc = DocxTemplate(self.get_template_file_path()) self.title = None self.cut_image = 0 self.area_id = None self.cut_image_userid = '15836903493' self.cut_image_role = 'manager' self.crossid = None self.cross_name = None self.time_list = None self.tp = None self.walk = 1 # 人行横道 self.delay = 1 # 延误 self.flow_in = 1 # 分流 self.tpl_data = { # 总体概述 'part1': self.tpl_paragraph_map['part1'], 'part1_1': PartDetail(), 'part1_key': 'part1', # 优化前现状分析 'part2': self.tpl_paragraph_map['part2'], 'part2_1': PartDetail(), # 用户传入 'part2_2': PartDetail(), # 用户传入 'part2_3': PartDetail(), # 用户传入 'part2_key': 'part2', # 诊断问题 'part3': self.tpl_paragraph_map['part3'], 'part3_1': PartDetail(), # 用户传入 'part3_key': 'part3', # 优化方案 'part4': self.tpl_paragraph_map['part4'], 'part4_1': PartDetail(), 'part4_2': PartDetail(), 'part4_key': 'part4', # 优化后效果对比 'part5': self.tpl_paragraph_map['part5'], 'part5_1': PartDetail(), 'part5_key': 'part5', } self.tpl_paragraph = { 'part1': {'visible': 0}, 'part2': {'visible': 0, 'part2_1': 0, 'part2_2': 0, 'part2_3': 0}, 'part3': {'visible': 0}, 'part4': {'visible': 0, 'part4_1': 0, 'part4_2': 0}, 'part5': {'visible': 0} } return def get_template_file_path(self): dir = os.path.dirname(os.path.abspath(__file__)) return f"{dir}/compare_report.docx" def get_static_file_path(self): dir = os.path.dirname(os.path.abspath(__file__)) return f"{dir}/../temp" tpl_paragraph_map = { 'part1': '总体概述', 'part2': '优化前现状分析', 'part2_1': '基本信息', 'part2_2': '延误分析', 'part2_3': '流量占比及分流转向比分析', 'part3': '诊断问题', 'part4': '优化方案', 'part4_1': '改善措施', 'part4_2': '配时方案', 'part5': '优化后效果对比', } def build_rich_text(self, detail, detail_value=None): item_rt = RichText('') item_details = detail.split('?') for k, v in enumerate(item_details): item_rt.add(f"{item_details[k]}") if k != len(item_details) - 1: item_rt.add(f"{detail_value[k]}", color="#FF0000", font=detail_value) continue return item_rt def build_rich_list(self, detail, detail_value): result = [] item_details = detail.split('?') for k, v in enumerate(item_details): item_rt = RichText('') item_rt.add(f"{item_details[k]}", color="#000000", size=18) result.append(item_rt) if k != len(item_details) - 1: item_rt = RichText('') item_rt.add(f"{detail_value[k]}", color="#ff0000", size=18) result.append(item_rt) continue return result def build_rich_list_bg(self, detail, detail_value): result = [] item_details = detail.split('?') for k, v in enumerate(item_details): item_rt = RichText('') item_rt.add(f"{item_details[k]}", color="#000000", size=18, style="background-color: #FFFF00;") result.append(item_rt) if k != len(item_details) - 1: item_rt = RichText('') item_rt.add(f"{detail_value[k]}", color="#ff0000", size=18, style="background-color: #FFFF00;") result.append(item_rt) continue return result def new_rich_text(self, text, color="#000000", font="仿宋_GB2312", size=None): rt = RichText('') if size: rt.add(f"{text}", color=color, font=font, size=size) if not size: rt.add(f"{text}", color=color, font=font) return rt def new_rich_text_list(self, texts): rt_list = [] for item_text in texts: rt = RichText('') rt.add(f"{item_text}", color="#ff0000") rt_list.append(rt) return rt_list def build_template(self, file_name): context = {} screen_image_list = [] context['title'] = self.title i = 0 for part_key, value in self.tpl_data.items(): match part_key: case 'part1': if self.tpl_paragraph[part_key]['visible'] == 0: continue i += 1 table_sort = 0 table_data = [] context[part_key] = table_data context['part1_key'] = str(i) context['part1_name'] = value item_data = { 'part_name': '', 'part_n': 'part1_1', 'detail': [], 'image': '', 'table_data': [], } item_data['detail'].append(self.tpl_data['part1_1'].item1) item_data['detail'].append(self.tpl_data['part1_1'].item2) item_data['detail'].append(self.tpl_data['part1_1'].item3) item_data['detail'].append(self.tpl_data['part1_1'].item4) # item_data['detail'].append(self.new_rich_text(self.tpl_data['part1_1'].item5, "#FF0000")) # item_data['detail'].append(self.build_rich_text(self.tpl_data['part1_1'].detail, self.tpl_data['part1_1'].detail_value) if self.tpl_data['part1_1'].detail != '' else '') if len(self.tpl_data['part1_1'].table) > 0: for item_table in self.tpl_data['part1_1'].table: item_data['table_data'].append(self.build_rich_text(item_table.detail, item_table.detail_value) if item_table.detail != '' else '') table_data.append(item_data) # for item_image in item_part_detail.images: # image_sort += 1 # if self.cut_image == 1: # screen_image_list.append(CutReportImgColumn( # wave_id=item_image.wave_id, # wave_name=item_image.wave_name, # time_list=item_image.time_list, # tp_name=item_image.tp, # high=item_image.high # )) # item_data_detail['images'].append({ # 'sort': image_sort, # 'name': item_image.wave_name, # 'image': len(screen_image_list) - 1, # }) # item_data['detail'].append(item_data_detail) # table_data.append(item_data) case 'part2': if self.tpl_paragraph[part_key]['visible'] == 0: continue i += 1 table_sort = 0 image_sort = 0 table_data = [] context[part_key] = table_data context['part2_key'] = str(i) context['part2_name'] = value if self.tpl_paragraph[part_key]['part2_1'] == 1: item_data = { 'part_name': self.tpl_paragraph_map['part2_1'], 'part_n': 'part2_1', 'image': "", 'detail': [], } item_data['detail'].append(self.tpl_data['part2_1'].item1) item_data['detail'].append(self.tpl_data['part2_1'].item2) item_data['detail'].append(self.tpl_data['part2_1'].item3) item_data['detail'].append(self.tpl_data['part2_1'].item4) table_data.append(item_data) if self.tpl_paragraph[part_key]['part2_2'] == 1: item_data = { 'part_name': self.tpl_paragraph_map['part2_2'], 'part_n': 'part2_2', 'image': '', 'detail': [], } for item in self.tpl_data['part2_2'].table: item_data['detail'].append( self.build_rich_text(item.detail, item.detail_value) if item.detail != '' else '') table_data.append(item_data) if self.tpl_paragraph[part_key]['part2_3'] == 1: item_data = { 'part_name': self.tpl_paragraph_map['part2_3'], 'part_n': 'part2_3', 'image': '', 'detail': [], } for item in self.tpl_data['part2_3'].table: item_data['detail'].append( self.build_rich_text(item.detail, item.detail_value) if item.detail != '' else '') table_data.append(item_data) case 'part3': if self.tpl_paragraph[part_key]['visible'] == 0: continue i += 1 table_data = [] context[part_key] = table_data context['part3_key'] = str(i) context['part3_name'] = value item_loop = 0 loop = 0 item_data = { 'part_name': "", 'part_n': 'part3_1', 'detail': [], } for item_data_list in self.tpl_data['part3_1'].table: item_data_detail = { 'title': item_data_list.item1, 'item_detail': [] } for item_data_table in item_data_list.table: item_item_data_detail = { 'title': item_data_table.item1, 'item_detail': [] } for item_data_table_value in item_data_table.table: item_item_data_detail['item_detail'].append( self.build_rich_text(item_data_table_value.detail, item_data_table_value.detail_value) if item_data_table_value.detail != '' else '') item_data_detail['item_detail'].append(item_item_data_detail) item_data['detail'].append(item_data_detail) table_data.append(item_data) case 'part4': if self.tpl_paragraph[part_key]['visible'] == 0: continue i += 1 table_sort = 0 table_sort += 1 table_data = [] context[part_key] = table_data context['part4_key'] = str(i) context['part4_name'] = value if self.tpl_paragraph[part_key]['part4_1'] == 1: item_data = { 'part_name': self.tpl_paragraph_map['part4_1'], 'part_n': 'part4_1', 'data': [], } table_data.append(item_data) if self.tpl_paragraph[part_key]['part4_2'] == 1: item_data = { 'part_name': self.tpl_paragraph_map['part4_2'], 'part_n': 'part4_1', 'data': [], } table_data.append(item_data) case 'part5': if self.tpl_paragraph[part_key]['visible'] == 0: continue i += 1 table_sort = 0 table_sort += 1 table_data = [] context[part_key] = table_data context['part5_key'] = str(i) context['part5_name'] = value item_data = { 'part_name': "", 'part_n': 'part5_1', 'detail': '', 'data': [], } item_data['detail'] = self.tpl_data['part5_1'].item1 for item_table in self.tpl_data['part5_1'].table: item_date_detail = { 'title': item_table.item1, 'item_detail': [] } for item_item_table in item_table.table: item_date_detail['item_detail'].append(self.build_rich_text(item_item_table.detail, item_item_table.detail_value) if item_item_table.detail != '' else '') item_data['data'].append(item_date_detail) table_data.append(item_data) cut_images = {} if self.cut_image == 1: cut_images, _ = cut_report_img([CutReportImgColumn( crossid=self.crossid, cross_name=self.cross_name, time_list=self.time_list, tp_name=self.tp, walk=1, delay=1, flow_in=1)], self.nodeid, self.area_id, self.cut_image_userid , self.cut_image_role) if context.get('part1') and cut_images.get('cross'): for item_part in context['part1']: if item_part['part_n'] == 'part1_1':item_part['image'] = InlineImage(self.doc, cut_images["cross"],width=Mm(90)) if context.get('part2'): for item_part in context['part2']: if item_part['part_n'] == 'part2_1' and cut_images.get('walk'): item_part['image'] = InlineImage(self.doc, cut_images["walk"],width=Mm(90)) if item_part['part_n'] == 'part2_2' and cut_images.get('delay'): item_part['image'] = InlineImage(self.doc, cut_images["delay"], width=Mm(90)) if item_part['part_n'] == 'part2_3' and cut_images.get('flow_in'): item_part['image'] = InlineImage(self.doc, cut_images["flow_in"], width=Mm(90)) self.doc.render(context, autoescape=True) if len(cut_images) > 0: for _, item_cut_images in cut_images.items(): os.remove(item_cut_images) # # 渲染模板 # for table in self.doc.tables: # for row in table.rows: # for cell in row.cells: # for para in cell.paragraphs: # set_font(para, para.text, '仿宋_GB2312', 30,font_color=(255, 0, 0), bg_color='FFFF00') # for para in self.doc.paragraphs: # set_font(para, para.text, '仿宋', 14) # self.doc.save(self.get_static_file_path() + f"/{file_name}.docx") # return f"/api/phase_template_download?nodeid={self.nodeid}&type=report&file_name={file_name}" def set_font(para, text_to_find, font_name='仿宋', font_size=12, font_color=(0, 0, 0), bg_color='FFFFFF'): for run in para.runs: run.text = text_to_find # if text_to_find in run.text: run.font.name = font_name run.font.size = Pt(font_size) run.font.color.rgb = RGBColor(*font_color) rPr = run._element.rPr if rPr is None: rPr = OxmlElement('w:rPr') run._element.append(rPr) rFonts = OxmlElement('w:rFonts') rFonts.set('{http://schemas.openxmlformats.org/wordprocessingml/2006/main}ascii', font_name) rFonts.set('{http://schemas.openxmlformats.org/wordprocessingml/2006/main}eastAsia', font_name) rFonts.set('{http://schemas.openxmlformats.org/wordprocessingml/2006/main}hAnsi', font_name) rPr.append(rFonts) shading_elm = OxmlElement('w:shd') shading_elm.set('{http://schemas.openxmlformats.org/wordprocessingml/2006/main}fill', bg_color) pPr = para._element.find('{http://schemas.openxmlformats.org/wordprocessingml/2006/main}pPr') if pPr is None: pPr = OxmlElement('w:pPr') para._element.insert(0, pPr) pPr.append(shading_elm) def doc_demo_contrast(): doc1 = DocDocumentContrastCtx('530100') #获取模版对象 doc1.tpl_paragraph['part1']['visible'] = 1 doc1.tpl_paragraph['part2']['visible'] = 1 doc1.tpl_paragraph['part2']['part2_1'] = 1 doc1.tpl_paragraph['part2']['part2_2'] = 1 doc1.tpl_paragraph['part2']['part2_3'] = 1 doc1.tpl_paragraph['part3']['visible'] = 1 doc1.tpl_paragraph['part4']['visible'] = 1 doc1.tpl_paragraph['part4']['part4_1'] = 1 doc1.tpl_paragraph['part4']['part4_2'] = 1 doc1.tpl_paragraph['part5']['visible'] = 1 doc1.title = '清盛大道与龙江路交叉口' #自动截图 doc1.cut_image = 1 #开启 doc1.area_id = 530101 doc1.crossid = "CR_10264777_2509810" doc1.cross_name = "玉峰路与陈家营路交叉口" doc1.time_list = [['2026-05-01', '2026-05-04'], ['2026-05-06', '2026-05-09']], doc1.tp = "02:00-03:00" # =========1总体概论=========== doc1.tpl_data['part1_1'].item1 = '2024年10月8日' doc1.tpl_data['part1_1'].item2 = '2025年10月8日' doc1.tpl_data['part1_1'].item3 = '清盛大道与龙江路交叉口' doc1.tpl_data['part1_1'].item4 = '09:00-17:00' detail1 = PartDetail() detail1.detail = "路口服务水平由?级提升为?级,提升?个等级;" detail1.detail_value = ['B', 'A', '1'] detail2 = PartDetail() detail2.detail = "路口拥堵指数由?下降为?,减少?,优化率为?;" detail2.detail_value = ['0.33', '0.56', '5.31', '3.21%'] detail3 = PartDetail() detail3.detail = "路口停车次数由?下降为?,减少?,优化率为?;" detail3.detail_value = ['2.331', '0.563', '2.31', '6.21%'] doc1.tpl_data['part1_1'].table = [detail1, detail2, detail3] #==========2.1优化前现状分析========== doc1.tpl_data['part2_1'].item1 = '清盛大道与龙江路交叉口' doc1.tpl_data['part2_1'].item2 = '东城区' doc1.tpl_data['part2_1'].item3 = '主干路与次干路相交的十字路口' doc1.tpl_data['part2_1'].item4 = '海信' #===========2.2延误分析============ detail1 = PartDetail() detail1.detail = "根据各进口道车辆延误时间分析,服务水平较低的进口道有?个。东南进口的延误时间为?s,西北进口的延误时间为?s。" detail1.detail_value = ['3', '5', '7'] detail2 = PartDetail() detail2.detail = "根据各进口道拥堵指数分析,服务水平有?个。东南进口的延误时间为?s,西北进口的延误时间为?s。" detail2.detail_value = ['2', '6', '9'] doc1.tpl_data['part2_2'].table = [detail1, detail2] #===========2.3延误分析============ detail1 = PartDetail() detail1.detail = "根据车辆分流转向比分析,西南进口道流量占比最大为?,东南进口道流量占比最小为?。路口的主要车流方向为东北直行和西南直行,东北进口道以直行车流为主,占比为?;西南进口道以直行车流为主,占比为?。" detail1.detail_value = ['5%', '9%', '8%', '12%'] detail2 = PartDetail() detail2.detail = "根据车辆分流转向比分析,东北进口道流量占比最大为?,东南进口道流量占比最小为?。路口的主要车流方向为东北直行和西南直行,东北进口道以直行车流为主,占比为?;西南进口道以直行车流为主,占比为?。" detail2.detail_value = ['5%', '7%', '8%', '12%'] doc1.tpl_data['part2_3'].table = [detail1, detail2] #==========3诊断问题============ detail1_1 = PartDetail() detail1_1.item1 = '多次排队' detail1_1_1 = PartDetail() detail1_1_1.detail = "西北进口道直行车辆多次停车率过大(?),车辆需要多次排队才能通过。" detail1_1_1.detail_value = ['16%'] detail1_1_2 = PartDetail() detail1_1_2.detail = "东北进口道直行车辆多次停车率过大(?),车辆需要多次排队才能通过。" detail1_1_2.detail_value = ['20%'] detail1_1.table = [detail1_1_1, detail1_1_2] detail1_2 = PartDetail() detail1_2.item1 = '停车较多' detail1_2_1 = PartDetail() detail1_2_1.detail = "路口停车次数较高(2.1),整体通行效率不高。" detail1_2_1.detail_value = ['2.1'] detail1_2.table = [detail1_2_1] detail1 = PartDetail() detail1.item1 = '运行效果' detail1.table = [detail1_1, detail1_2] detail2_1 = PartDetail() detail2_1.item1 = '多次排队1' detail2_1_1 = PartDetail() detail2_1_1.detail = "西北进口道直行车辆多次停车率过大(?),车辆需要多次排队才能通过。" detail2_1_1.detail_value = ['16%'] detail2_1_2 = PartDetail() detail2_1_2.detail = "东北进口道直行车辆多次停车率过大(?),车辆需要多次排队才能通过。" detail2_1_2.detail_value = ['20%'] detail2_1.table = [detail2_1_1, detail2_1_2] detail2_2 = PartDetail() detail2_2.item1 = '停车较多2' detail2_2_1 = PartDetail() detail2_2_1.detail = "路口停车次数较高(?),整体通行效率不高。" detail2_2_1.detail_value = ['2.1'] detail2_2.table = [detail2_2_1] detail2 = PartDetail() detail2.item1 = '均衡调控' detail2.table = [detail1_1, detail1_2] doc1.tpl_data['part3_1'].table = [detail1, detail2] #==========4优化方案============= #==========5优化后效果对比========== detail5_1_1 = PartDetail() detail5_1_1.detail = '路口服务水平由?级提升为?级,提升?个等级;' detail5_1_1.detail_value = ['A', 'B', '6'] detail5_1_2 = PartDetail() detail5_1_2.detail = '路口拥堵指数由?下降为?,减少?,优化率为?;' detail5_1_2.detail_value = ['3', '2', '1','5%'] detail5_1_3 = PartDetail() detail5_1_3.detail = '路口停车次数由?下降为?,减少?,优化率为?;' detail5_1_3.detail_value = ['3.13', '4.1334' , '3.13', '3.13%'] detail5_1 = PartDetail() detail5_1.item1 = '路口方面' detail5_1.table = [detail5_1_1, detail5_1_2,detail5_1_3] detail5_2_1 = PartDetail() detail5_2_1.detail = '路口服务水平由?级提升为?级,提升?个等级;' detail5_2_1.detail_value = ['A', 'B', '6'] detail5_2_2 = PartDetail() detail5_2_2.detail = '路口拥堵指数由?下降为?,减少?,优化率为?;' detail5_2_2.detail_value = ['3', '2', '1','5%'] detail5_2_3 = PartDetail() detail5_2_3.detail = '路口停车次数由?下降为?,减少?,优化率为?;' detail5_2_3.detail_value = ['3.13', '4.1334' , '3.13', '3.13%'] detail5_2 = PartDetail() detail5_2.item1 = 'X进口方面' detail5_2.table = [detail5_2_1, detail5_2_2,detail5_2_3] doc1.tpl_data['part5_1'].item1 = '方案下发后通过平台对路口优化效果进行对比,优化后路口及进口道效果提升较明显的指标如下:' doc1.tpl_data['part5_1'].table = [detail5_1, detail5_2] return doc1.build_template("对比报告") # 生成word if __name__ == '__main__': doc_demo_contrast()