676 lines
20 KiB
Vue
676 lines
20 KiB
Vue
<script setup lang="ts">
|
||
import { ArrowLeft, Download, FileText } from "@lucide/vue";
|
||
import { computed } from "vue";
|
||
import { useRouter } from "vue-router";
|
||
|
||
import { downloadReportPdf, downloadReportWord } from "@/api/reports";
|
||
import type { ReportCard, RiskReportResult } from "@/types/risk";
|
||
import { formatRiskLevel } from "@/utils/risk";
|
||
|
||
const router = useRouter();
|
||
let parsed: RiskReportResult | null = null;
|
||
try {
|
||
const raw = sessionStorage.getItem("dieya-risk-report");
|
||
parsed = raw ? (JSON.parse(raw) as RiskReportResult) : null;
|
||
} catch {
|
||
parsed = null;
|
||
}
|
||
const report = computed(() => parsed);
|
||
const reportCards = computed<ReportCard[]>(
|
||
() => {
|
||
if (!report.value) return [];
|
||
if (report.value.reportCards?.length) return report.value.reportCards;
|
||
return (report.value.aiSolutionCards ?? []).map((solution) => ({
|
||
type: "solution" as const,
|
||
title: solution.title || "AI 案例推理生成",
|
||
confidence: solution.confidence,
|
||
analysisBase: solution.analysisBase,
|
||
standardFallbackEnabled: solution.standardFallbackEnabled,
|
||
standards: solution.standards,
|
||
similarCases: solution.similarCases,
|
||
risks: solution.risks,
|
||
suggestions: solution.suggestions,
|
||
warning: solution.warning,
|
||
}));
|
||
},
|
||
);
|
||
const reportData = computed(() => {
|
||
const cards = reportCards.value;
|
||
const unique = <T>(items: T[], key: (item: T) => string) => {
|
||
const seen = new Set<string>();
|
||
return items.filter((item) => {
|
||
const value = key(item);
|
||
if (seen.has(value)) return false;
|
||
seen.add(value);
|
||
return true;
|
||
});
|
||
};
|
||
const standards = unique(
|
||
[
|
||
...(report.value?.standardJudgment ?? []),
|
||
...cards.flatMap((card) => card.standards ?? []),
|
||
],
|
||
(item) => `${item.category}|${item.riskPoint}|${item.rule}|${item.status}`,
|
||
);
|
||
const similarCases = unique(
|
||
[
|
||
...(report.value?.similarCases ?? []),
|
||
...cards.flatMap((card) => card.similarCases ?? []),
|
||
],
|
||
(item) => `${item.caseId}|${item.date}|${item.product}|${item.problem ?? item.description}|${item.solution}`,
|
||
);
|
||
const suggestions = unique(
|
||
[
|
||
...(report.value?.aiSuggestions ?? []),
|
||
...cards.flatMap((card) => card.suggestions ?? []),
|
||
],
|
||
(item) => item,
|
||
);
|
||
return {
|
||
cards,
|
||
supplierCards: cards.filter((card) => card.type === "supplier"),
|
||
materialCards: cards.filter((card) => card.type === "material"),
|
||
solutionCards: cards.filter((card) => card.type === "solution"),
|
||
standards,
|
||
similarCases,
|
||
suggestions,
|
||
};
|
||
});
|
||
const dimensionRows = computed(() => {
|
||
if (!report.value) return [];
|
||
if (report.value.dimensionScores?.length) {
|
||
return report.value.dimensionScores.filter(
|
||
(item) => item.dimension !== "节点阶段" && item.dimension !== "缺陷等级",
|
||
);
|
||
}
|
||
return report.value.factors
|
||
.filter(
|
||
(factor) => factor.name !== "节点阶段" && factor.name !== "缺陷等级",
|
||
)
|
||
.map((factor) => ({
|
||
dimension: factor.name,
|
||
score: 100 - factor.value,
|
||
weight: factor.weight,
|
||
status: "database",
|
||
reason: null,
|
||
}));
|
||
});
|
||
const generatedAt = new Intl.DateTimeFormat("zh-CN", {
|
||
dateStyle: "medium",
|
||
timeStyle: "short",
|
||
}).format(new Date());
|
||
|
||
async function downloadWord() {
|
||
if (!report.value) return;
|
||
const response = await downloadReportWord(report.value);
|
||
const blob = response.data;
|
||
const url = URL.createObjectURL(blob);
|
||
const anchor = document.createElement("a");
|
||
anchor.href = url;
|
||
anchor.download = `蝶雅新品风险预估报告-${report.value.subjectId}.docx`;
|
||
anchor.click();
|
||
URL.revokeObjectURL(url);
|
||
}
|
||
|
||
async function downloadPdf() {
|
||
if (!report.value) return;
|
||
const response = await downloadReportPdf(report.value);
|
||
const url = URL.createObjectURL(response.data);
|
||
const anchor = document.createElement("a");
|
||
anchor.href = url;
|
||
anchor.download = `蝶雅新品风险预估报告-${report.value.subjectId}.pdf`;
|
||
anchor.click();
|
||
URL.revokeObjectURL(url);
|
||
}
|
||
|
||
function notify(message: string) {
|
||
window.alert(`${message}功能待接入服务端`);
|
||
}
|
||
|
||
function dimensionConclusion(item: {
|
||
dimension: string;
|
||
score: number;
|
||
reason?: string | null;
|
||
}) {
|
||
const supplier = report.value?.supplierFreq;
|
||
if (item.dimension === "供应商表现" && supplier) {
|
||
const orderText =
|
||
supplier.cooperationCount != null
|
||
? `合作 ${supplier.cooperationCount} 次、`
|
||
: "";
|
||
return `${Math.round(item.score)} 分 · ${orderText}异常 ${supplier.anomalyCount ?? 0} 次`;
|
||
}
|
||
if (
|
||
item.dimension === "标准符合性" &&
|
||
report.value?.standardJudgment?.length
|
||
) {
|
||
return `${Math.round(item.score)} 分 · 命中 ${report.value.standardJudgment.length} 条判定标准,待检测项需人工确认`;
|
||
}
|
||
return `${Math.round(item.score)} 分${item.reason ? ` · ${item.reason}` : ""}`;
|
||
}
|
||
</script>
|
||
|
||
<template>
|
||
<div class="report-page">
|
||
<header class="report-actions">
|
||
<button type="button" class="secondary-button" @click="downloadPdf">
|
||
<Download :size="15" />导出 PDF
|
||
</button>
|
||
<button type="button" class="secondary-button" @click="downloadWord">
|
||
<Download :size="15" />导出 Word
|
||
</button>
|
||
<button
|
||
type="button"
|
||
class="secondary-button"
|
||
@click="notify('钉钉发送')"
|
||
>
|
||
<FileText :size="15" />钉钉发送
|
||
</button>
|
||
<button type="button" class="primary-button" @click="router.back()">
|
||
<ArrowLeft :size="15" />返回对话
|
||
</button>
|
||
</header>
|
||
<p v-if="!report" class="report-empty">
|
||
暂无报告数据,请从风险评估结果卡片点击“生成报告”。
|
||
</p>
|
||
<article v-else class="report-sheet">
|
||
<h1>新品风险预估报告</h1>
|
||
<p class="report-subtitle">
|
||
蝶雅产品部 · 风险预估系统 · 自动生成 · {{ generatedAt }}
|
||
</p>
|
||
<div class="report-meta">
|
||
<span><b>产品/评估对象:</b>{{ report.subjectId }}</span>
|
||
><span
|
||
><b>供应商:</b
|
||
>{{ report.supplierFreq?.supplierName || "未指定" }}</span
|
||
><span
|
||
><b>报告编号:</b>RP-{{ report.subjectId.slice(0, 8) }}-{{
|
||
report.score.toFixed(0)
|
||
}}</span
|
||
>
|
||
</div>
|
||
|
||
<h2>一、风险结论</h2>
|
||
<table>
|
||
<thead>
|
||
<tr>
|
||
<th>评估项</th>
|
||
<th>结论</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
<tr>
|
||
<td>综合评分</td>
|
||
<td class="risk">
|
||
{{ Math.round(report.score) }} / 100 ·
|
||
{{ formatRiskLevel(report.level) }} ·
|
||
{{ report.riskBadge || "需关注" }}
|
||
</td>
|
||
</tr>
|
||
<tr>
|
||
<td>风险警示</td>
|
||
<td>
|
||
{{
|
||
report.alertText ||
|
||
"当前暂无足够历史数据,建议按标准库完成基础验证。"
|
||
}}
|
||
</td>
|
||
</tr>
|
||
<tr v-for="item in dimensionRows" :key="item.dimension">
|
||
<td>{{ item.dimension }}({{ item.weight }}%)</td>
|
||
<td>{{ dimensionConclusion(item) }}</td>
|
||
</tr>
|
||
</tbody>
|
||
</table>
|
||
|
||
<template v-if="report.categoryWarning">
|
||
<h2>二、品类风险预警</h2>
|
||
<table>
|
||
<thead>
|
||
<tr>
|
||
<th>品类</th>
|
||
<th>历史异常</th>
|
||
<th>风险等级</th>
|
||
<th>建议</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
<tr>
|
||
<td>{{ report.categoryWarning.category }}</td>
|
||
<td>{{ report.categoryWarning.anomalyCount }} 条</td>
|
||
<td>{{ report.categoryWarning.riskLevel }}</td>
|
||
<td>
|
||
{{
|
||
report.categoryWarning.suggestion ||
|
||
"结合相似品类和标准库预判"
|
||
}}
|
||
</td>
|
||
</tr>
|
||
</tbody>
|
||
</table>
|
||
</template>
|
||
|
||
<template v-if="report.supplierFreq?.supplierName || reportData.supplierCards.length">
|
||
<h2>三、供应商合作频次分析</h2>
|
||
<table>
|
||
<thead>
|
||
<tr>
|
||
<th>维度</th>
|
||
<th>数值</th>
|
||
<th>说明</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
<tr v-if="report.supplierFreq?.cooperationCount != null">
|
||
<td>合作次数</td>
|
||
<td>{{ report.supplierFreq?.cooperationCount }} 次</td>
|
||
<td>近三年采购订单去重单据数</td>
|
||
</tr>
|
||
<tr>
|
||
<td>异常次数</td>
|
||
<td>{{ report.supplierFreq?.anomalyCount ?? 0 }} 次</td>
|
||
<td>异常案例库统计</td>
|
||
</tr>
|
||
<tr v-if="report.supplierFreq?.alpha != null">
|
||
<td>频次风险系数</td>
|
||
<td>×{{ report.supplierFreq?.alpha?.toFixed(2) }}</td>
|
||
<td>α = 1.0 +(采购订单数 / 100)× 0.3,最高1.5</td>
|
||
</tr>
|
||
<tr v-if="report.supplierFreq?.tier">
|
||
<td>供应商层级</td>
|
||
<td>{{ report.supplierFreq?.tier }}</td>
|
||
<td>{{ report.supplierFreq?.suggestion }}</td>
|
||
</tr>
|
||
<template v-for="(card, cardIndex) in reportData.supplierCards" :key="`supplier-card-${cardIndex}`">
|
||
<tr v-if="card.supplierScore != null"><td>供应商得分</td><td>{{ card.supplierScore.toFixed(2) }} 分</td><td>供应商画像统计</td></tr>
|
||
<tr v-if="card.supplierBaseScore != null"><td>供应商基础分</td><td>{{ card.supplierBaseScore.toFixed(2) }} 分</td><td>供应商画像统计</td></tr>
|
||
<tr v-if="card.topProblems?.length"><td>高频问题</td><td>{{ card.topProblems.map((item) => `${item.name}(${item.count}次)`).join("、") }}</td><td>异常案例库统计</td></tr>
|
||
<tr v-if="card.note"><td>备注</td><td>{{ card.note }}</td><td>供应商画像统计</td></tr>
|
||
</template>
|
||
</tbody>
|
||
</table>
|
||
</template>
|
||
|
||
<template v-if="reportData.materialCards.length">
|
||
<h2>四、物料风险分析</h2>
|
||
<table>
|
||
<thead><tr><th>物料/字段</th><th>分析结果</th></tr></thead>
|
||
<tbody>
|
||
<tr v-for="(card, index) in reportData.materialCards" :key="`material-${index}`">
|
||
<td>{{ card.material || card.title }}</td>
|
||
<td>
|
||
<span v-if="card.anomalyCount != null">历史异常:{{ card.anomalyCount }} 条;</span>
|
||
<span v-if="card.frequentProblems?.length">常见问题:{{ card.frequentProblems.map((item) => `${item.name}(${item.count}次)`).join("、") }};</span>
|
||
<span v-if="card.suggestions?.length">建议:{{ card.suggestions.join(";") }}</span>
|
||
</td>
|
||
</tr>
|
||
</tbody>
|
||
</table>
|
||
</template>
|
||
|
||
<h2>{{ reportData.materialCards.length ? "五" : "四" }}、相似案例匹配({{ reportData.similarCases.length }} 条)</h2>
|
||
<template v-if="reportData.similarCases.length">
|
||
<table>
|
||
<thead>
|
||
<tr>
|
||
<th>日期</th>
|
||
<th>产品</th>
|
||
<th>问题</th>
|
||
<th>处理方案</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
<tr
|
||
v-for="item in reportData.similarCases"
|
||
:key="`${item.date}-${item.product}-${item.problem}`"
|
||
>
|
||
<td>{{ item.date || "未知" }}</td>
|
||
<td>{{ item.product || "未命名" }}</td>
|
||
<td>{{ item.problem || "未填写" }}</td>
|
||
<td>{{ item.solution || "未填写" }}</td>
|
||
</tr>
|
||
</tbody>
|
||
</table>
|
||
</template>
|
||
<p v-else class="report-paragraph">
|
||
暂无相关案例,RAG 相似案例检索未返回匹配结果。
|
||
</p>
|
||
|
||
<h2>{{ reportData.materialCards.length ? "六" : "五" }}、预防建议</h2>
|
||
<ol>
|
||
<li
|
||
v-for="suggestion in reportData.suggestions.length
|
||
? reportData.suggestions
|
||
: ['优先针对低分维度安排专项验证,并明确责任人与完成节点。']"
|
||
:key="suggestion"
|
||
>
|
||
{{ suggestion }}
|
||
</li>
|
||
</ol>
|
||
|
||
<h2>{{ reportData.materialCards.length ? "七" : "六" }}、AI 推理方案(无历史案例场景)</h2>
|
||
<p class="report-paragraph">
|
||
<template v-if="reportData.similarCases.length">
|
||
本次评估已匹配
|
||
{{ reportData.similarCases.length }}
|
||
条相似历史案例,当前采用历史案例与标准库双引擎结论,未单独触发无历史案例类比推理。
|
||
</template>
|
||
<template v-else>
|
||
当前未匹配到足够历史案例,系统将基于相似品类、相似物料生成类比方案,并结合判定标准库进行规则兜底;实际执行前需由产品和品控确认。
|
||
</template>
|
||
</p>
|
||
<template v-if="reportData.solutionCards.length">
|
||
<section v-for="(card, index) in reportData.solutionCards" :key="`solution-${index}`" class="ai-solution-report">
|
||
<header><strong>{{ card.title }}</strong><span v-if="card.confidence">置信度:{{ card.confidence }}</span></header>
|
||
<p v-if="card.analysisBase"><b>分析基础:</b>{{ card.analysisBase }}</p>
|
||
<p v-if="card.standardFallbackEnabled"><b>标准兜底校验:</b>已启用(无直接历史案例)</p>
|
||
<ul v-if="card.risks?.length"><li v-for="risk in card.risks" :key="`${risk.title}-${risk.detail}`"><b>{{ risk.level }} · {{ risk.title }}:</b>{{ risk.detail }}</li></ul>
|
||
<ol v-if="card.suggestions?.length"><li v-for="suggestion in card.suggestions" :key="suggestion">{{ suggestion }}</li></ol>
|
||
<p v-if="card.warning" class="ai-solution-report__warning">{{ card.warning }}</p>
|
||
</section>
|
||
</template>
|
||
|
||
<h2>{{ reportData.materialCards.length ? "八" : "七" }}、标准判定结果</h2>
|
||
<template v-if="reportData.standards.length">
|
||
<table>
|
||
<thead>
|
||
<tr>
|
||
<th>判定维度</th>
|
||
<th>判定标准</th>
|
||
<th>标准来源</th>
|
||
<th>当前状态</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
<tr
|
||
v-for="standard in reportData.standards"
|
||
:key="`${standard.category}-${standard.riskPoint}`"
|
||
>
|
||
<td>{{ standard.category }} · {{ standard.riskPoint }}</td>
|
||
<td>{{ standard.rule }}</td>
|
||
<td>{{ standard.source }}</td>
|
||
<td>{{ standard.status }}</td>
|
||
</tr>
|
||
</tbody>
|
||
</table>
|
||
</template>
|
||
<p v-else class="report-paragraph">暂无相关标准。</p>
|
||
<p class="report-paragraph report-engine-note">
|
||
双引擎融合:历史异常经验引擎提供风险优先级,标准库提供可执行的检测阈值与验证任务。
|
||
</p>
|
||
|
||
<footer class="report-footer">
|
||
<span>编制:蝶雅风险预估系统</span
|
||
><span>生成时间:{{ generatedAt }}</span>
|
||
</footer>
|
||
</article>
|
||
</div>
|
||
</template>
|
||
|
||
<style scoped>
|
||
.report-page {
|
||
flex: 1;
|
||
overflow-y: auto;
|
||
background: #f5f7fb;
|
||
padding: 24px 28px 40px;
|
||
}
|
||
.report-actions {
|
||
display: flex;
|
||
justify-content: flex-end;
|
||
gap: 6px;
|
||
margin: 0 auto 16px;
|
||
max-width: 780px;
|
||
}
|
||
.action-group {
|
||
display: flex;
|
||
gap: 6px;
|
||
}
|
||
.primary-button,
|
||
.secondary-button {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
gap: 6px;
|
||
border-radius: 5px;
|
||
padding: 8px 12px;
|
||
font-size: 12px;
|
||
font-weight: 650;
|
||
cursor: pointer;
|
||
}
|
||
.primary-button {
|
||
border: 1px solid #111827;
|
||
background: #111827;
|
||
color: white;
|
||
}
|
||
.secondary-button {
|
||
border: 1px solid #d1d9e6;
|
||
background: white;
|
||
color: #334155;
|
||
}
|
||
.report-empty {
|
||
max-width: 780px;
|
||
margin: 80px auto;
|
||
text-align: center;
|
||
color: #64748b;
|
||
}
|
||
.report-sheet {
|
||
max-width: 780px;
|
||
margin: 0 auto;
|
||
border: 1px solid #dce3ed;
|
||
border-radius: 8px;
|
||
background: white;
|
||
padding: 48px;
|
||
box-shadow: 0 10px 26px rgb(15 23 42 / 10%);
|
||
}
|
||
h1 {
|
||
margin: 0 0 4px;
|
||
text-align: center;
|
||
font-size: 28px;
|
||
color: #111827;
|
||
}
|
||
.report-subtitle {
|
||
margin: 0 0 28px;
|
||
border-bottom: 2px solid #111827;
|
||
padding-bottom: 20px;
|
||
text-align: center;
|
||
color: #64748b;
|
||
font-size: 12px;
|
||
}
|
||
.report-meta {
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
gap: 32px;
|
||
margin-bottom: 24px;
|
||
color: #334155;
|
||
font-size: 12px;
|
||
}
|
||
h2 {
|
||
margin: 24px 0 12px;
|
||
border-left: 2px solid #111827;
|
||
padding-left: 8px;
|
||
color: #111827;
|
||
font-size: 15px;
|
||
}
|
||
table {
|
||
width: 100%;
|
||
margin-bottom: 16px;
|
||
border-collapse: collapse;
|
||
font-size: 13px;
|
||
}
|
||
th,
|
||
td {
|
||
border: 1px solid #dbe3ef;
|
||
padding: 9px 12px;
|
||
text-align: left;
|
||
vertical-align: top;
|
||
}
|
||
th {
|
||
background: #111827;
|
||
color: white;
|
||
font-size: 12px;
|
||
font-weight: 600;
|
||
}
|
||
.risk {
|
||
color: #d97706;
|
||
font-size: 15px;
|
||
font-weight: 700;
|
||
}
|
||
ol {
|
||
padding-left: 24px;
|
||
color: #334155;
|
||
font-size: 13px;
|
||
line-height: 1.9;
|
||
}
|
||
.report-footer {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
margin-top: 36px;
|
||
color: #94a3b8;
|
||
font-size: 11px;
|
||
}
|
||
.report-paragraph {
|
||
margin: 0 0 12px;
|
||
color: #334155;
|
||
font-size: 13px;
|
||
line-height: 1.8;
|
||
}
|
||
.ai-solution-report {
|
||
margin: 0 0 16px;
|
||
border: 1px solid #bfdbfe;
|
||
border-left: 3px solid #7c3aed;
|
||
border-radius: 7px;
|
||
padding: 14px;
|
||
background: #fafbff;
|
||
}
|
||
.report-subsection-title {
|
||
margin: 14px 0 8px;
|
||
color: #334155;
|
||
font-size: 13px;
|
||
}
|
||
.report-card-detail {
|
||
margin: 0 0 14px;
|
||
border: 1px solid #dbe3ef;
|
||
border-left: 3px solid #2563eb;
|
||
border-radius: 6px;
|
||
padding: 12px 14px;
|
||
background: #fafcff;
|
||
}
|
||
.report-card-detail header {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
margin-bottom: 7px;
|
||
color: #1d4ed8;
|
||
font-size: 13px;
|
||
}
|
||
.report-card-detail header span {
|
||
color: #64748b;
|
||
font-size: 10px;
|
||
}
|
||
.report-card-detail p,
|
||
.report-card-detail li {
|
||
margin: 4px 0;
|
||
color: #334155;
|
||
font-size: 12px;
|
||
line-height: 1.6;
|
||
}
|
||
.report-card-detail ul {
|
||
margin: 4px 0;
|
||
padding-left: 20px;
|
||
}
|
||
.report-card-detail__standards {
|
||
color: #475569;
|
||
}
|
||
.report-card-detail__warning {
|
||
border-radius: 4px;
|
||
padding: 6px 8px;
|
||
background: #fffbeb;
|
||
color: #92400e !important;
|
||
}
|
||
.ai-solution-report header {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
gap: 12px;
|
||
margin-bottom: 8px;
|
||
color: #1d4ed8;
|
||
font-size: 13px;
|
||
}
|
||
.ai-solution-report header span {
|
||
border: 1px solid #fde68a;
|
||
border-radius: 999px;
|
||
padding: 2px 8px;
|
||
background: #fffbeb;
|
||
color: #d97706;
|
||
font-size: 10px;
|
||
}
|
||
.ai-solution-report p,
|
||
.ai-solution-report li {
|
||
color: #334155;
|
||
font-size: 12px;
|
||
line-height: 1.7;
|
||
}
|
||
.ai-solution-report p {
|
||
margin: 6px 0;
|
||
}
|
||
.ai-solution-report h3 {
|
||
margin: 10px 0 4px;
|
||
font-size: 12px;
|
||
}
|
||
.ai-solution-report ul,
|
||
.ai-solution-report ol {
|
||
margin: 4px 0;
|
||
padding-left: 20px;
|
||
}
|
||
.ai-solution-report__warning {
|
||
border-radius: 4px;
|
||
padding: 7px 9px;
|
||
background: #fffbeb;
|
||
color: #92400e !important;
|
||
font-size: 11px !important;
|
||
}
|
||
@media (max-width: 700px) {
|
||
.report-page {
|
||
padding: 12px;
|
||
}
|
||
.report-sheet {
|
||
padding: 24px 16px;
|
||
}
|
||
.report-actions {
|
||
flex-direction: column;
|
||
}
|
||
.action-group {
|
||
justify-content: flex-end;
|
||
}
|
||
.report-meta {
|
||
flex-direction: column;
|
||
gap: 5px;
|
||
}
|
||
table {
|
||
min-width: 680px;
|
||
}
|
||
.report-sheet {
|
||
overflow-x: auto;
|
||
}
|
||
}
|
||
@media print {
|
||
.report-page {
|
||
padding: 0;
|
||
background: white;
|
||
}
|
||
.report-actions {
|
||
display: none;
|
||
}
|
||
.report-sheet {
|
||
border: 0;
|
||
box-shadow: none;
|
||
max-width: none;
|
||
padding: 0;
|
||
}
|
||
h2 {
|
||
break-after: avoid;
|
||
}
|
||
table {
|
||
break-inside: avoid;
|
||
}
|
||
}
|
||
</style>
|