This commit is contained in:
HuangHai
2026-01-20 07:43:13 +08:00
parent 6263487425
commit b0b4533f57
6 changed files with 124 additions and 2 deletions

View File

@@ -13,6 +13,9 @@ from Tools.T6_Export import export_excel, DorisExcelExporter, extract_hourly_pri
import tempfile
import os
import zipfile
import subprocess
from pydantic import BaseModel
from starlette.background import BackgroundTask
from Model.YltAnalyticsModel import (
StationBase,
CompetitorStation,
@@ -95,6 +98,55 @@ async def export_prices_zip():
)
class AiReportRequest(BaseModel):
content: str
@router.post("/api/export/ai-report-docx")
async def export_ai_report_docx(req: AiReportRequest):
content = req.content
if not content:
raise HTTPException(status_code=400, detail="Content is empty")
# Create temp markdown file
with tempfile.NamedTemporaryFile(mode="w", suffix=".md", delete=False, encoding="utf-8") as tmp_md:
tmp_md.write(content)
tmp_md_path = tmp_md.name
output_docx_path = tmp_md_path.replace(".md", ".docx")
# Check template
template_path = "static/template/templates.docx"
cmd = ['pandoc', '-s', tmp_md_path, '-o', output_docx_path, '--resource-path=static']
# Only add reference doc if it exists, but the user requested it specifically.
# We'll check if it exists, if not, we might fail or warn, but let's try to include it if possible.
if os.path.exists(template_path):
cmd.extend(['--reference-doc', template_path])
try:
subprocess.run(cmd, check=True)
except subprocess.CalledProcessError as e:
# Clean up
if os.path.exists(tmp_md_path):
os.remove(tmp_md_path)
raise HTTPException(status_code=500, detail=f"Pandoc conversion failed: {str(e)}")
def cleanup():
if os.path.exists(tmp_md_path):
os.remove(tmp_md_path)
if os.path.exists(output_docx_path):
os.remove(output_docx_path)
return FileResponse(
output_docx_path,
media_type="application/vnd.openxmlformats-officedocument.wordprocessingml.document",
filename="AI分析报告.docx",
background=BackgroundTask(cleanup)
)
@router.get("/api/ai/pricing/strategy-summary")
async def ai_pricing_strategy_summary():
async def generate_stream():

View File

@@ -278,6 +278,42 @@ body {
margin-bottom: 12px;
}
/* Markdown Table Styling in AI Result */
.ai-result table {
width: 100%;
border-collapse: collapse;
margin: 16px 0;
font-size: 13px;
background-color: var(--card-bg); /* Use card bg to distinguish from ai-box bg */
border: 1px solid var(--card-border);
border-radius: 6px;
overflow: hidden; /* For rounded corners on table */
}
.ai-result th,
.ai-result td {
padding: 10px 12px;
border: 1px solid var(--card-border); /* Explicit border color */
text-align: left;
}
.ai-result th {
background-color: rgba(15, 23, 42, 0.8);
color: var(--text-secondary);
font-weight: 600;
font-size: 12px;
text-transform: uppercase;
letter-spacing: 0.05em;
}
.ai-result tr:nth-child(even) {
background-color: rgba(255, 255, 255, 0.02); /* Slight zebra striping */
}
.ai-result tr:hover td {
background-color: var(--table-row-hover);
}
/* Custom Scrollbar Webkit */
::-webkit-scrollbar {
width: 8px;

View File

@@ -15,9 +15,13 @@
<div class="dashboard-title">⚡ 充电站电价分析驾驶舱</div>
<div class="controls">
<button class="btn-primary" @click="exportAllPrices" :disabled="exporting">
<span v-if="!exporting">📊 一键导出分析报告</span>
<span v-if="!exporting">📊 导出分时段电价表</span>
<span v-else>⏳ 导出中...</span>
</button>
<button class="btn-primary" @click="exportAiReport" :disabled="exportingReport || !aiText" :title="!aiText ? '请先生成AI分析报告' : ''">
<span v-if="!exportingReport">📑 导出分析报告</span>
<span v-else>⏳ 生成中...</span>
</button>
</div>
</header>

View File

@@ -14,6 +14,7 @@ const aiText = ref("")
const placeholder = ref("点击左侧按钮查询四家供应商最新24小时分时电价再使用右侧AI综合分析功能对各司定价策略进行对比。")
const loading = ref(false)
const exporting = ref(false)
const exportingReport = ref(false)
const aiLoading = ref(false)
const aiBoxRef = ref(null)
const priceTableRows = ref([])
@@ -122,6 +123,35 @@ exporting.value = false
}
}
const exportAiReport = async () => {
if (!aiText.value) {
alert("请先生成AI分析报告")
return
}
try {
exportingReport.value = true
const res = await axios.post(apiBase.value + "/api/export/ai-report-docx", {
content: aiText.value
}, {responseType: "blob"})
const blob = new Blob([res.data], {type: "application/vnd.openxmlformats-officedocument.wordprocessingml.document"})
const url = window.URL.createObjectURL(blob)
const a = document.createElement("a")
a.href = url
a.download = "AI分析报告.docx"
document.body.appendChild(a)
a.click()
document.body.removeChild(a)
window.URL.revokeObjectURL(url)
} catch (e) {
console.error(e)
alert("导出失败请检查是否安装Pandoc")
} finally {
exportingReport.value = false
}
}
const startAiAnalysis = async () => {
if (aiLoading.value) return
aiText.value = ""
@@ -210,6 +240,6 @@ const getPriceColor = (price) => {
return '#f1f5f9' // Slate-100
}
return {apiBase,operators,aiText,placeholder,loading,exporting,aiLoading,priceTableRows,loadAllOperatorsPrices,exportAllPrices,startAiAnalysis,formatCell,getPriceColor,renderedAiText,aiBoxRef}
return {apiBase,operators,aiText,placeholder,loading,exporting,exportingReport,aiLoading,priceTableRows,loadAllOperatorsPrices,exportAllPrices,exportAiReport,startAiAnalysis,formatCell,getPriceColor,renderedAiText,aiBoxRef}
}
}).mount("#app")

Binary file not shown.