import gradio as gr import openpyxl import os from openpyxl.utils import get_column_letter from copy import copy TEMPLATE_FILE = "template.xlsx" def process_direct_merge(file_objs, template_height): if not file_objs: return None, "請先上傳檔案!" try: template_height = int(template_height) except: return None, "模板高度必須是數字!" files = file_objs if isinstance(file_objs, list) else [file_objs] # 檔案排序邏輯 (OF- > DF- > SF- > PF-) prefix_priority = {"OF-": 1, "DF-": 2, "SF-": 3, "PF-": 4} def sort_key(f): name = os.path.basename(f.name).upper() for prefix, weight in prefix_priority.items(): if name.startswith(prefix): return (weight, name) return (99, name) files.sort(key=sort_key) try: temp_wb = openpyxl.load_workbook(TEMPLATE_FILE) temp_ws = temp_wb.worksheets[0] out_wb = openpyxl.Workbook() out_ws = out_wb.active out_ws.title = "合併報表" # --- 頁面設置:水平方向與邊界調整 --- # 設定為水平 (Landscape) out_ws.page_setup.orientation = out_ws.ORIENTATION_LANDSCAPE # 設定邊界 (單位英吋): 2cm ≈ 0.787, 1cm ≈ 0.394 out_ws.page_margins.top = 0.787 # 上 2cm out_ws.page_margins.bottom = 0.787 # 下 2cm out_ws.page_margins.left = 0.394 # 左 1cm out_ws.page_margins.right = 0.394 # 右 1cm # 強制每一頁寬度符合一頁 (避免欄位溢出) out_ws.page_setup.fitToWidth = 1 out_ws.page_setup.fitToHeight = 0 # 1. 預先複製欄寬 for i in range(1, 31): col_l = get_column_letter(i) if col_l in temp_ws.column_dimensions: out_ws.column_dimensions[col_l].width = temp_ws.column_dimensions[col_l].width current_base_row = 1 for file_obj in files: wb = openpyxl.load_workbook(file_obj.name, data_only=True) file_funds_data = [] for sheet_name in wb.sheetnames: ws = wb[sheet_name] for row in ws.iter_rows(): first_val = str(row[0].value) if row[0].value else "" if "彙總表" in first_val or "近10年度" in first_val: fund_name = first_val.replace("近10年度", "").replace("收支概況彙總表", "").strip() records = [] start_row_idx = row[0].row + 4 for r_idx in range(start_row_idx, start_row_idx + 10): # 跳過原始檔 B 欄,讀取 A + C~U 欄 year_val = ws.cell(row=r_idx, column=1).value data_row = [ws.cell(row=r_idx, column=c).value for c in range(3, 22)] records.append([year_val] + data_row) file_funds_data.append({"name": fund_name, "data": records}) # 寫入資料與樣式 for fund in file_funds_data: for r in range(1, template_height + 1): target_row = current_base_row + r - 1 # 同步範本列高 if r in temp_ws.row_dimensions: out_ws.row_dimensions[target_row].height = temp_ws.row_dimensions[r].height for c in range(1, 26): s_cell = temp_ws.cell(row=r, column=c) t_cell = out_ws.cell(row=target_row, column=c) # 非數據區複製模板文字 if not (5 <= r <= 14): t_cell.value = s_cell.value if s_cell.has_style: t_cell.font = copy(s_cell.font) t_cell.border = copy(s_cell.border) t_cell.fill = copy(s_cell.fill) t_cell.alignment = copy(s_cell.alignment) t_cell.number_format = copy(s_cell.number_format) # 套用合併格 if temp_ws.merged_cells: for m_range in temp_ws.merged_cells.ranges: if m_range.min_row <= template_height: min_col, min_row, max_col, max_row = m_range.bounds out_ws.merge_cells( start_row=current_base_row + min_row - 1, start_column=min_col, end_row=current_base_row + max_row - 1, end_column=max_col ) # 填入內容 (標題與 10 年數據) out_ws.cell(row=current_base_row, column=1).value = f"{fund['name']}近10年度收支概況彙總表" for r_idx, row_vals in enumerate(fund['data']): for c_idx, val in enumerate(row_vals): out_ws.cell(row=current_base_row + 4 + r_idx, column=c_idx + 1).value = val current_base_row += template_height # 補空白列時同步範本列高 if len(file_funds_data) % 2 != 0: for r in range(1, template_height + 1): target_row = current_base_row + r - 1 if r in temp_ws.row_dimensions: out_ws.row_dimensions[target_row].height = temp_ws.row_dimensions[r].height current_base_row += template_height output_path = os.path.abspath("Final_Merged_Report.xlsx") out_wb.save(output_path) return output_path, f"合併成功!範本高度 {template_height} 列,水平列印且邊界已設定。" except Exception as e: return None, f"發生錯誤: {str(e)}" # Gradio UI with gr.Blocks() as app: gr.Markdown("# 基金報表合併系統 (18 列範本版)") with gr.Row(): f_in = gr.File(label="上傳 Excel", file_count="multiple") h_in = gr.Number(label="設定模板高度 (已設為新範本 18)", value=18) btn = gr.Button("🚀 開始一鍵生成", variant="primary") f_out = gr.File(label="下載結果") status = gr.Textbox(label="狀態") btn.click(process_direct_merge, [f_in, h_in], [f_out, status]) if __name__ == "__main__": app.launch(server_name="0.0.0.0", server_port=7860)