-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
668d29b
commit 54be72f
Showing
11 changed files
with
438 additions
and
0 deletions.
There are no files selected for viewing
Binary file not shown.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,191 @@ | ||
from fastapi import FastAPI, UploadFile, File, HTTPException, Request, Form | ||
from fastapi.responses import FileResponse, HTMLResponse | ||
from fastapi.staticfiles import StaticFiles | ||
from fastapi.templating import Jinja2Templates | ||
import pandas as pd | ||
import json | ||
import os | ||
from datetime import datetime | ||
from typing import Dict, Any | ||
from openpyxl import load_workbook | ||
from openpyxl.styles import PatternFill, Font, Alignment, Border, Side | ||
from openpyxl.worksheet.table import Table, TableStyleInfo | ||
|
||
app = FastAPI( | ||
title="API Conversor JSON para Excel", | ||
description="API para converter arquivos JSON em Excel", | ||
version="1.0.0" | ||
) | ||
|
||
# Configurar arquivos estáticos e templates | ||
app.mount("/static", StaticFiles(directory="static"), name="static") | ||
templates = Jinja2Templates(directory="templates") | ||
|
||
# Rota principal - renderiza o template HTML | ||
@app.get("/", response_class=HTMLResponse) | ||
async def home(request: Request): | ||
return templates.TemplateResponse("index.html", {"request": request}) | ||
|
||
@app.post("/convert/file/") | ||
async def convert_file(json_file: UploadFile = File(...)): | ||
""" | ||
Converte um arquivo JSON enviado para Excel | ||
""" | ||
if not json_file.filename.endswith(".json"): | ||
raise HTTPException(status_code=400, detail="Por favor, envie um arquivo JSON.") | ||
|
||
try: | ||
content = await json_file.read() | ||
json_data = json.loads(content.decode('utf-8')) | ||
return await convert_json_to_excel(json_data, json_file.filename) | ||
|
||
except json.JSONDecodeError: | ||
raise HTTPException(status_code=400, detail="Arquivo JSON inválido") | ||
except Exception as e: | ||
raise HTTPException(status_code=500, detail=f"Erro ao processar arquivo: {str(e)}") | ||
|
||
@app.post("/convert/json/") | ||
async def convert_json(json_text: str = Form(...)): | ||
""" | ||
Converte JSON enviado diretamente no body para Excel | ||
""" | ||
try: | ||
# Tenta fazer o parse do JSON | ||
json_data = json.loads(json_text) | ||
|
||
# Verifica se o JSON é um dicionário ou lista | ||
if not isinstance(json_data, (dict, list)): | ||
raise HTTPException(status_code=400, detail="O JSON deve ser um objeto ou array") | ||
|
||
# Se for uma lista de objetos, converte diretamente | ||
if isinstance(json_data, list): | ||
df = pd.DataFrame(json_data) | ||
# Se for um objeto único, converte para lista com um item | ||
else: | ||
df = pd.DataFrame([json_data]) | ||
|
||
# Cria nome único para o arquivo Excel | ||
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") | ||
excel_filename = f"uploads/json_data_{timestamp}.xlsx" | ||
|
||
# Cria diretório se não existir | ||
os.makedirs("uploads", exist_ok=True) | ||
|
||
# Salva como Excel | ||
df.to_excel(excel_filename, index=False) | ||
|
||
# Retorna o arquivo Excel | ||
return FileResponse( | ||
path=excel_filename, | ||
filename=os.path.basename(excel_filename), | ||
media_type="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" | ||
) | ||
|
||
except json.JSONDecodeError: | ||
raise HTTPException(status_code=400, detail="JSON inválido") | ||
except Exception as e: | ||
raise HTTPException(status_code=500, detail=f"Erro ao processar JSON: {str(e)}") | ||
|
||
async def convert_json_to_excel(json_data: dict, original_filename: str): | ||
""" | ||
Função auxiliar para converter JSON em Excel com formatação adequada | ||
""" | ||
try: | ||
# Converte JSON para DataFrame | ||
if isinstance(json_data, list): | ||
df = pd.DataFrame(json_data) | ||
else: | ||
df = pd.DataFrame([json_data]) | ||
|
||
# Cria nome único para o arquivo Excel | ||
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") | ||
excel_filename = f"uploads/{os.path.splitext(original_filename)[0]}_{timestamp}.xlsx" | ||
|
||
# Cria diretório se não existir | ||
os.makedirs("uploads", exist_ok=True) | ||
|
||
# Salva primeiro sem índice | ||
df.to_excel(excel_filename, index=False) | ||
|
||
# Carrega o workbook para formatação | ||
wb = load_workbook(excel_filename) | ||
ws = wb.active | ||
|
||
# Define estilos | ||
header_fill = PatternFill(start_color="1F4E78", end_color="1F4E78", fill_type="solid") | ||
header_font = Font(name='Arial', size=11, color="FFFFFF", bold=True) | ||
cell_font = Font(name='Arial', size=10) | ||
|
||
# Aplica estilos ao cabeçalho | ||
for cell in ws[1]: | ||
cell.fill = header_fill | ||
cell.font = header_font | ||
cell.alignment = Alignment(horizontal='center', vertical='center', wrap_text=True) | ||
|
||
# Aplica estilos às células de dados | ||
for row in ws.iter_rows(min_row=2): | ||
for cell in row: | ||
cell.font = cell_font | ||
cell.alignment = Alignment(horizontal='left', vertical='center', wrap_text=True) | ||
cell.border = Border( | ||
left=Side(style='thin', color='D4D4D4'), | ||
right=Side(style='thin', color='D4D4D4'), | ||
top=Side(style='thin', color='D4D4D4'), | ||
bottom=Side(style='thin', color='D4D4D4') | ||
) | ||
|
||
# Ajusta largura das colunas | ||
for column in ws.columns: | ||
max_length = 0 | ||
column_letter = column[0].column_letter | ||
|
||
for cell in column: | ||
try: | ||
if len(str(cell.value)) > max_length: | ||
max_length = len(str(cell.value)) | ||
except: | ||
pass | ||
|
||
adjusted_width = min(max_length + 2, 50) # Limita a largura máxima | ||
ws.column_dimensions[column_letter].width = adjusted_width | ||
|
||
# Remove tabela existente se houver | ||
if len(ws.tables) > 0: | ||
del ws.tables[ws.tables.keys()[0]] | ||
|
||
# Cria uma nova tabela | ||
table_name = f"Table_{timestamp}" # Use uma variável timestamp para nome único | ||
tab = Table(displayName=table_name, ref=ws.dimensions) | ||
|
||
# Aplica estilo à tabela | ||
style = TableStyleInfo( | ||
name="TableStyleMedium2", | ||
showFirstColumn=False, | ||
showLastColumn=False, | ||
showRowStripes=True, | ||
showColumnStripes=False | ||
) | ||
tab.tableStyleInfo = style | ||
|
||
# Adiciona a tabela | ||
ws.add_table(tab) | ||
|
||
# Congela o painel | ||
ws.freeze_panes = 'A2' | ||
|
||
# Salva as alterações | ||
wb.save(excel_filename) | ||
|
||
# Retorna o arquivo Excel | ||
return FileResponse( | ||
path=excel_filename, | ||
filename=os.path.basename(excel_filename), | ||
media_type="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" | ||
) | ||
|
||
except Exception as e: | ||
raise HTTPException(status_code=500, detail=f"Erro ao formatar Excel: {str(e)}") | ||
|
||
if __name__ == "__main__": | ||
import uvicorn | ||
uvicorn.run(app, host="0.0.0.0", port=8000) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,125 @@ | ||
* { | ||
margin: 0; | ||
padding: 0; | ||
box-sizing: border-box; | ||
} | ||
|
||
body { | ||
font-family: Arial, sans-serif; | ||
background-color: #f5f5f5; | ||
color: #333; | ||
line-height: 1.6; | ||
} | ||
|
||
.container { | ||
max-width: 800px; | ||
margin: 2rem auto; | ||
padding: 0 1rem; | ||
} | ||
|
||
h1 { | ||
text-align: center; | ||
color: #2c3e50; | ||
margin-bottom: 2rem; | ||
} | ||
|
||
.converter-box { | ||
background: white; | ||
border-radius: 10px; | ||
box-shadow: 0 2px 10px rgba(0,0,0,0.1); | ||
padding: 2rem; | ||
} | ||
|
||
.tab-container { | ||
display: flex; | ||
margin-bottom: 1rem; | ||
border-bottom: 2px solid #eee; | ||
} | ||
|
||
.tab-button { | ||
padding: 0.5rem 1rem; | ||
border: none; | ||
background: none; | ||
cursor: pointer; | ||
font-size: 1rem; | ||
color: #666; | ||
transition: all 0.3s ease; | ||
} | ||
|
||
.tab-button.active { | ||
color: #2980b9; | ||
border-bottom: 2px solid #2980b9; | ||
margin-bottom: -2px; | ||
} | ||
|
||
.tab-content { | ||
display: none; | ||
} | ||
|
||
.tab-content.active { | ||
display: block; | ||
} | ||
|
||
.upload-form { | ||
display: flex; | ||
flex-direction: column; | ||
gap: 1rem; | ||
} | ||
|
||
.drop-zone { | ||
border: 2px dashed #ccc; | ||
border-radius: 5px; | ||
padding: 2rem; | ||
text-align: center; | ||
cursor: pointer; | ||
transition: border 0.3s ease; | ||
} | ||
|
||
.drop-zone:hover { | ||
border-color: #2980b9; | ||
} | ||
|
||
.drop-zone-input { | ||
display: none; | ||
} | ||
|
||
.json-input { | ||
width: 100%; | ||
height: 200px; | ||
padding: 1rem; | ||
border: 1px solid #ccc; | ||
border-radius: 5px; | ||
resize: vertical; | ||
font-family: monospace; | ||
} | ||
|
||
.convert-button { | ||
background-color: #2980b9; | ||
color: white; | ||
border: none; | ||
padding: 1rem; | ||
border-radius: 5px; | ||
cursor: pointer; | ||
font-size: 1rem; | ||
transition: background-color 0.3s ease; | ||
} | ||
|
||
.convert-button:hover { | ||
background-color: #2471a3; | ||
} | ||
|
||
.drop-zone-hover { | ||
border-color: #2980b9; | ||
background-color: #2980b91a; | ||
} | ||
|
||
.sr-only { | ||
position: absolute; | ||
width: 1px; | ||
height: 1px; | ||
padding: 0; | ||
margin: -1px; | ||
overflow: hidden; | ||
clip: rect(0, 0, 0, 0); | ||
border: 0; | ||
} |
Oops, something went wrong.