|
|
|
@ -1,95 +1,190 @@ |
|
|
|
<script setup lang="ts"> |
|
|
|
import { ref, reactive, onMounted, watch, nextTick } from 'vue'; |
|
|
|
import { ElMessage } from 'element-plus'; |
|
|
|
import { |
|
|
|
getCategoryChart, getDeptRankChart, getAnalysisRank, exportAnalysis |
|
|
|
} from '@/api/efcode/report/registration'; |
|
|
|
import type { RegistrationReportQuery, ChartDataVo, RegistrationAnalysisVo } from '@/api/efcode/report/registration'; |
|
|
|
import { ref, reactive, onMounted, nextTick } from 'vue'; |
|
|
|
import { getCategoryChart, getDeptRankChart } from '@/api/efcode/report/registration'; |
|
|
|
import type { RegistrationReportQuery, ChartDataVo } from '@/api/efcode/report/registration'; |
|
|
|
import * as echarts from 'echarts'; |
|
|
|
|
|
|
|
// 查询参数 |
|
|
|
const queryParams = reactive<RegistrationReportQuery>({ |
|
|
|
pageNum: 1, |
|
|
|
pageSize: 20, |
|
|
|
dateType: 3, |
|
|
|
beginTime: undefined, |
|
|
|
endTime: undefined |
|
|
|
}); |
|
|
|
const loading = ref(false); |
|
|
|
|
|
|
|
// 饼图时段筛选 |
|
|
|
const timeMode = ref('year'); |
|
|
|
const loading = ref(false); |
|
|
|
const selectedYear = ref<string>(String(new Date().getFullYear())); |
|
|
|
const selectedQuarter = ref<number>(Math.ceil((new Date().getMonth() + 1) / 3)); |
|
|
|
const selectedMonth = ref<string>(''); |
|
|
|
|
|
|
|
const yearOptions = (() => { |
|
|
|
const curr = new Date().getFullYear(); |
|
|
|
return Array.from({ length: 5 }, (_, i) => String(curr - i)); |
|
|
|
})(); |
|
|
|
|
|
|
|
const quarterOptions = [ |
|
|
|
{ label: '第一季度', value: 1 }, |
|
|
|
{ label: '第二季度', value: 2 }, |
|
|
|
{ label: '第三季度', value: 3 }, |
|
|
|
{ label: '第四季度', value: 4 } |
|
|
|
]; |
|
|
|
|
|
|
|
// 饼图查询参数 |
|
|
|
const pieParams = reactive<RegistrationReportQuery>({ |
|
|
|
beginTime: undefined, |
|
|
|
endTime: undefined, |
|
|
|
dateSearch: undefined |
|
|
|
}); |
|
|
|
|
|
|
|
// 图表数据 |
|
|
|
const categoryData = ref<ChartDataVo[]>([]); |
|
|
|
const deptRankData = ref<ChartDataVo[]>([]); |
|
|
|
|
|
|
|
// 分析列表 |
|
|
|
const analysisTableData = ref<RegistrationAnalysisVo[]>([]); |
|
|
|
const analysisTotal = ref(0); |
|
|
|
const yearRankData = ref<ChartDataVo[]>([]); |
|
|
|
const quarterRankData = ref<ChartDataVo[]>([]); |
|
|
|
const monthRankData = ref<ChartDataVo[]>([]); |
|
|
|
|
|
|
|
// 图表DOM引用 |
|
|
|
const pieChartRef = ref<HTMLElement>(); |
|
|
|
const barChartRef = ref<HTMLElement>(); |
|
|
|
const yearBarRef = ref<HTMLElement>(); |
|
|
|
const quarterBarRef = ref<HTMLElement>(); |
|
|
|
const monthBarRef = ref<HTMLElement>(); |
|
|
|
let pieChart: echarts.ECharts | null = null; |
|
|
|
let barChart: echarts.ECharts | null = null; |
|
|
|
let yearBarChart: echarts.ECharts | null = null; |
|
|
|
let quarterBarChart: echarts.ECharts | null = null; |
|
|
|
let monthBarChart: echarts.ECharts | null = null; |
|
|
|
|
|
|
|
/** 计算时间范围 */ |
|
|
|
const getYearRange = () => { |
|
|
|
const now = new Date(); |
|
|
|
const year = now.getFullYear(); |
|
|
|
return { beginTime: `${year}-01-01`, endTime: `${year}-12-31` }; |
|
|
|
}; |
|
|
|
|
|
|
|
const getQuarterRange = () => { |
|
|
|
const now = new Date(); |
|
|
|
const year = now.getFullYear(); |
|
|
|
const q = Math.ceil((now.getMonth() + 1) / 3); |
|
|
|
const startMonth = (q - 1) * 3 + 1; |
|
|
|
const endMonth = q * 3; |
|
|
|
const endDay = new Date(year, endMonth, 0).getDate(); |
|
|
|
return { |
|
|
|
beginTime: `${year}-${String(startMonth).padStart(2, '0')}-01`, |
|
|
|
endTime: `${year}-${String(endMonth).padStart(2, '0')}-${endDay}` |
|
|
|
}; |
|
|
|
}; |
|
|
|
|
|
|
|
const getMonthRange = () => { |
|
|
|
const now = new Date(); |
|
|
|
const year = now.getFullYear(); |
|
|
|
const month = now.getMonth() + 1; |
|
|
|
const endDay = new Date(year, month, 0).getDate(); |
|
|
|
return { |
|
|
|
beginTime: `${year}-${String(month).padStart(2, '0')}-01`, |
|
|
|
endTime: `${year}-${String(month).padStart(2, '0')}-${endDay}` |
|
|
|
}; |
|
|
|
}; |
|
|
|
|
|
|
|
onMounted(() => { |
|
|
|
loadCharts(); |
|
|
|
loadAnalysisList(); |
|
|
|
applyPieYearFilter(); |
|
|
|
loadAllCharts(); |
|
|
|
}); |
|
|
|
|
|
|
|
/** 饼图-年度筛选 */ |
|
|
|
const applyPieYearFilter = () => { |
|
|
|
pieParams.dateSearch = undefined; |
|
|
|
const year = selectedYear.value; |
|
|
|
pieParams.beginTime = `${year}-01-01`; |
|
|
|
pieParams.endTime = `${year}-12-31`; |
|
|
|
}; |
|
|
|
|
|
|
|
/** 饼图-季度筛选 */ |
|
|
|
const applyPieQuarterFilter = () => { |
|
|
|
pieParams.dateSearch = undefined; |
|
|
|
const year = selectedYear.value; |
|
|
|
const q = selectedQuarter.value; |
|
|
|
const startMonth = (q - 1) * 3 + 1; |
|
|
|
const endMonth = q * 3; |
|
|
|
const endDay = new Date(Number(year), endMonth, 0).getDate(); |
|
|
|
pieParams.beginTime = `${year}-${String(startMonth).padStart(2, '0')}-01`; |
|
|
|
pieParams.endTime = `${year}-${String(endMonth).padStart(2, '0')}-${endDay}`; |
|
|
|
}; |
|
|
|
|
|
|
|
/** 饼图-月度筛选 */ |
|
|
|
const applyPieMonthFilter = () => { |
|
|
|
pieParams.beginTime = undefined; |
|
|
|
pieParams.endTime = undefined; |
|
|
|
if (selectedMonth.value) { |
|
|
|
pieParams.dateSearch = selectedMonth.value; |
|
|
|
} |
|
|
|
}; |
|
|
|
|
|
|
|
/** 时段模式变更 */ |
|
|
|
const handleTimeModeChange = (mode: string) => { |
|
|
|
queryParams.dateType = undefined; |
|
|
|
queryParams.beginTime = undefined; |
|
|
|
queryParams.endTime = undefined; |
|
|
|
pieParams.beginTime = undefined; |
|
|
|
pieParams.endTime = undefined; |
|
|
|
pieParams.dateSearch = undefined; |
|
|
|
if (mode === 'year') { |
|
|
|
queryParams.dateType = 3; |
|
|
|
applyPieYearFilter(); |
|
|
|
} else if (mode === 'quarter') { |
|
|
|
queryParams.dateType = 2; |
|
|
|
applyPieQuarterFilter(); |
|
|
|
} else if (mode === 'month') { |
|
|
|
queryParams.dateType = 1; |
|
|
|
const now = new Date(); |
|
|
|
selectedMonth.value = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, '0')}`; |
|
|
|
applyPieMonthFilter(); |
|
|
|
} |
|
|
|
if (mode !== 'custom') { |
|
|
|
loadCharts(); |
|
|
|
loadAnalysisList(); |
|
|
|
}; |
|
|
|
|
|
|
|
/** 饼图搜索 */ |
|
|
|
const handlePieSearch = async () => { |
|
|
|
loading.value = true; |
|
|
|
try { |
|
|
|
const res = await getCategoryChart(pieParams); |
|
|
|
categoryData.value = res.data || []; |
|
|
|
await nextTick(); |
|
|
|
renderPieChart(); |
|
|
|
} finally { |
|
|
|
loading.value = false; |
|
|
|
} |
|
|
|
}; |
|
|
|
|
|
|
|
/** 自定义时段搜索 */ |
|
|
|
const handleSearch = () => { |
|
|
|
queryParams.pageNum = 1; |
|
|
|
loadCharts(); |
|
|
|
loadAnalysisList(); |
|
|
|
/** 饼图重置 */ |
|
|
|
const handlePieReset = () => { |
|
|
|
timeMode.value = 'year'; |
|
|
|
selectedYear.value = String(new Date().getFullYear()); |
|
|
|
selectedQuarter.value = Math.ceil((new Date().getMonth() + 1) / 3); |
|
|
|
selectedMonth.value = ''; |
|
|
|
applyPieYearFilter(); |
|
|
|
handlePieSearch(); |
|
|
|
}; |
|
|
|
|
|
|
|
/** 加载图表数据 */ |
|
|
|
const loadCharts = async () => { |
|
|
|
/** 加载所有图表数据 */ |
|
|
|
const loadAllCharts = async () => { |
|
|
|
loading.value = true; |
|
|
|
try { |
|
|
|
const [catRes, deptRes] = await Promise.all([ |
|
|
|
getCategoryChart(queryParams), |
|
|
|
getDeptRankChart(queryParams) |
|
|
|
const yearRange = getYearRange(); |
|
|
|
const quarterRange = getQuarterRange(); |
|
|
|
const monthRange = getMonthRange(); |
|
|
|
|
|
|
|
const yearParams: RegistrationReportQuery = { beginTime: yearRange.beginTime, endTime: yearRange.endTime }; |
|
|
|
const quarterParams: RegistrationReportQuery = { beginTime: quarterRange.beginTime, endTime: quarterRange.endTime }; |
|
|
|
const monthParams: RegistrationReportQuery = { beginTime: monthRange.beginTime, endTime: monthRange.endTime }; |
|
|
|
|
|
|
|
const [catRes, yearRes, quarterRes, monthRes] = await Promise.all([ |
|
|
|
getCategoryChart(pieParams), |
|
|
|
getDeptRankChart(yearParams), |
|
|
|
getDeptRankChart(quarterParams), |
|
|
|
getDeptRankChart(monthParams) |
|
|
|
]); |
|
|
|
|
|
|
|
categoryData.value = catRes.data || []; |
|
|
|
deptRankData.value = deptRes.data || []; |
|
|
|
yearRankData.value = yearRes.data || []; |
|
|
|
quarterRankData.value = quarterRes.data || []; |
|
|
|
monthRankData.value = monthRes.data || []; |
|
|
|
|
|
|
|
await nextTick(); |
|
|
|
renderPieChart(); |
|
|
|
renderBarChart(); |
|
|
|
renderYearBarChart(); |
|
|
|
renderQuarterBarChart(); |
|
|
|
renderMonthBarChart(); |
|
|
|
} finally { |
|
|
|
loading.value = false; |
|
|
|
} |
|
|
|
}; |
|
|
|
|
|
|
|
/** 加载分析列表 */ |
|
|
|
const loadAnalysisList = async () => { |
|
|
|
try { |
|
|
|
const res = await getAnalysisRank(queryParams); |
|
|
|
analysisTableData.value = res.rows || []; |
|
|
|
analysisTotal.value = res.total || 0; |
|
|
|
} catch (e) {} |
|
|
|
}; |
|
|
|
|
|
|
|
/** 渲染饼图 */ |
|
|
|
const renderPieChart = () => { |
|
|
|
if (!pieChartRef.value) return; |
|
|
|
@ -97,7 +192,7 @@ const renderPieChart = () => { |
|
|
|
pieChart = echarts.init(pieChartRef.value); |
|
|
|
} |
|
|
|
const option: echarts.EChartsOption = { |
|
|
|
title: { text: '执法类别分布', left: 'center' }, |
|
|
|
title: { text: '执法类别数量及占比', left: 'center' }, |
|
|
|
tooltip: { trigger: 'item', formatter: '{b}: {c} ({d}%)' }, |
|
|
|
legend: { orient: 'vertical', left: 'left', top: 'middle' }, |
|
|
|
series: [{ |
|
|
|
@ -114,75 +209,67 @@ const renderPieChart = () => { |
|
|
|
pieChart.setOption(option, true); |
|
|
|
}; |
|
|
|
|
|
|
|
/** 渲染柱状图 */ |
|
|
|
const renderBarChart = () => { |
|
|
|
if (!barChartRef.value) return; |
|
|
|
if (!barChart) { |
|
|
|
barChart = echarts.init(barChartRef.value); |
|
|
|
/** 渲染竖向柱状图通用方法 */ |
|
|
|
const renderBarChart = ( |
|
|
|
chartRef: HTMLElement | undefined, |
|
|
|
chartInstance: echarts.ECharts | null, |
|
|
|
data: ChartDataVo[], |
|
|
|
title: string, |
|
|
|
color: string |
|
|
|
): echarts.ECharts | null => { |
|
|
|
if (!chartRef) return chartInstance; |
|
|
|
if (!chartInstance) { |
|
|
|
chartInstance = echarts.init(chartRef); |
|
|
|
} |
|
|
|
const data = [...deptRankData.value].reverse(); // 倒序排列让最多的在上面 |
|
|
|
const option: echarts.EChartsOption = { |
|
|
|
title: { text: '部门执法排行TOP10', left: 'center' }, |
|
|
|
title: { text: title, left: 'center', textStyle: { fontSize: 14 } }, |
|
|
|
tooltip: { trigger: 'axis', axisPointer: { type: 'shadow' } }, |
|
|
|
grid: { left: '20%', right: '5%', top: '15%', bottom: '5%' }, |
|
|
|
xAxis: { type: 'value' }, |
|
|
|
yAxis: { |
|
|
|
grid: { left: '3%', right: '5%', top: '15%', bottom: '25%', containLabel: true }, |
|
|
|
xAxis: { |
|
|
|
type: 'category', |
|
|
|
data: data.map(item => item.name), |
|
|
|
axisLabel: { width: 120, overflow: 'truncate' } |
|
|
|
axisLabel: { rotate: 30, interval: 0, fontSize: 10, width: 70, overflow: 'truncate' } |
|
|
|
}, |
|
|
|
yAxis: { type: 'value' }, |
|
|
|
series: [{ |
|
|
|
type: 'bar', |
|
|
|
data: data.map(item => item.value), |
|
|
|
barWidth: '40%', |
|
|
|
itemStyle: { |
|
|
|
color: new echarts.graphic.LinearGradient(0, 0, 1, 0, [ |
|
|
|
{ offset: 0, color: '#83bff6' }, |
|
|
|
{ offset: 1, color: '#188df0' } |
|
|
|
]) |
|
|
|
color: color, |
|
|
|
borderRadius: [4, 4, 0, 0] |
|
|
|
}, |
|
|
|
label: { show: true, position: 'right', formatter: '{c}' } |
|
|
|
label: { show: true, position: 'top', formatter: '{c}', fontSize: 10 } |
|
|
|
}] |
|
|
|
}; |
|
|
|
barChart.setOption(option, true); |
|
|
|
chartInstance.setOption(option, true); |
|
|
|
return chartInstance; |
|
|
|
}; |
|
|
|
|
|
|
|
/** 颜色分级样式 */ |
|
|
|
const getColorStyle = (level: string) => { |
|
|
|
switch (level) { |
|
|
|
case 'green': return { color: '#67c23a' }; |
|
|
|
case 'yellow': return { color: '#e6a23c' }; |
|
|
|
case 'orange': return { color: '#f56c6c' }; |
|
|
|
case 'red': return { color: '#f56c6c', fontWeight: 'bold' }; |
|
|
|
default: return {}; |
|
|
|
} |
|
|
|
const renderYearBarChart = () => { |
|
|
|
yearBarChart = renderBarChart(yearBarRef.value, yearBarChart, yearRankData.value, '年度执法数量排行(TOP10)', '#7ca9fb'); |
|
|
|
}; |
|
|
|
|
|
|
|
/** 导出 */ |
|
|
|
const handleExport = async () => { |
|
|
|
try { |
|
|
|
const res = await exportAnalysis(queryParams); |
|
|
|
const blob = new Blob([res as any], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' }); |
|
|
|
const url = window.URL.createObjectURL(blob); |
|
|
|
const link = document.createElement('a'); |
|
|
|
link.href = url; |
|
|
|
link.download = '执法分析.xlsx'; |
|
|
|
link.click(); |
|
|
|
window.URL.revokeObjectURL(url); |
|
|
|
} catch (e) { |
|
|
|
ElMessage.error('导出失败'); |
|
|
|
} |
|
|
|
const renderQuarterBarChart = () => { |
|
|
|
quarterBarChart = renderBarChart(quarterBarRef.value, quarterBarChart, quarterRankData.value, '季度执法数量排行(TOP10)', '#188df0'); |
|
|
|
}; |
|
|
|
|
|
|
|
const renderMonthBarChart = () => { |
|
|
|
monthBarChart = renderBarChart(monthBarRef.value, monthBarChart, monthRankData.value, '月执法数量排行(TOP10)', '#67c23a'); |
|
|
|
}; |
|
|
|
|
|
|
|
// 响应窗口变化 |
|
|
|
window.addEventListener('resize', () => { |
|
|
|
pieChart?.resize(); |
|
|
|
barChart?.resize(); |
|
|
|
yearBarChart?.resize(); |
|
|
|
quarterBarChart?.resize(); |
|
|
|
monthBarChart?.resize(); |
|
|
|
}); |
|
|
|
</script> |
|
|
|
|
|
|
|
<template> |
|
|
|
<div> |
|
|
|
<!-- 筛选区 --> |
|
|
|
<div v-loading="loading"> |
|
|
|
<!-- 饼图筛选区 --> |
|
|
|
<el-form inline class="search-form"> |
|
|
|
<el-form-item label="时段"> |
|
|
|
<el-select v-model="timeMode" style="width: 100px" @change="handleTimeModeChange"> |
|
|
|
@ -192,58 +279,50 @@ window.addEventListener('resize', () => { |
|
|
|
<el-option label="自定义" value="custom" /> |
|
|
|
</el-select> |
|
|
|
</el-form-item> |
|
|
|
<el-form-item v-if="timeMode === 'custom'" label="时间范围"> |
|
|
|
<el-date-picker v-model="queryParams.beginTime" type="date" placeholder="开始" value-format="YYYY-MM-DD" style="width: 130px" /> |
|
|
|
<el-form-item v-if="timeMode === 'year'"> |
|
|
|
<el-select v-model="selectedYear" style="width: 100px" @change="applyPieYearFilter"> |
|
|
|
<el-option v-for="y in yearOptions" :key="y" :label="y + '年'" :value="y" /> |
|
|
|
</el-select> |
|
|
|
</el-form-item> |
|
|
|
<el-form-item v-if="timeMode === 'quarter'"> |
|
|
|
<el-select v-model="selectedYear" style="width: 100px" @change="applyPieQuarterFilter"> |
|
|
|
<el-option v-for="y in yearOptions" :key="y" :label="y + '年'" :value="y" /> |
|
|
|
</el-select> |
|
|
|
<el-select v-model="selectedQuarter" style="width: 110px; margin-left: 4px" @change="applyPieQuarterFilter"> |
|
|
|
<el-option v-for="q in quarterOptions" :key="q.value" :label="q.label" :value="q.value" /> |
|
|
|
</el-select> |
|
|
|
</el-form-item> |
|
|
|
<el-form-item v-if="timeMode === 'month'"> |
|
|
|
<el-date-picker v-model="selectedMonth" type="month" placeholder="选择月份" value-format="YYYY-MM" style="width: 150px" @change="applyPieMonthFilter" /> |
|
|
|
</el-form-item> |
|
|
|
<el-form-item v-if="timeMode === 'custom'"> |
|
|
|
<el-date-picker v-model="pieParams.beginTime" type="date" placeholder="开始" value-format="YYYY-MM-DD" style="width: 130px" /> |
|
|
|
<span style="margin: 0 4px">-</span> |
|
|
|
<el-date-picker v-model="queryParams.endTime" type="date" placeholder="结束" value-format="YYYY-MM-DD" style="width: 130px" /> |
|
|
|
<el-date-picker v-model="pieParams.endTime" type="date" placeholder="结束" value-format="YYYY-MM-DD" style="width: 130px" /> |
|
|
|
</el-form-item> |
|
|
|
<el-form-item> |
|
|
|
<el-button type="primary" @click="handleSearch">查询</el-button> |
|
|
|
<el-button type="primary" @click="handlePieSearch">搜索</el-button> |
|
|
|
<el-button @click="handlePieReset">重置</el-button> |
|
|
|
</el-form-item> |
|
|
|
</el-form> |
|
|
|
|
|
|
|
<!-- 图表区 --> |
|
|
|
<el-row :gutter="16" class="chart-row" v-loading="loading"> |
|
|
|
<!-- 图表区:2行2列 --> |
|
|
|
<el-row :gutter="16" class="chart-row"> |
|
|
|
<el-col :span="12"> |
|
|
|
<div ref="pieChartRef" class="chart-container"></div> |
|
|
|
</el-col> |
|
|
|
<el-col :span="12"> |
|
|
|
<div ref="barChartRef" class="chart-container"></div> |
|
|
|
<div ref="yearBarRef" class="chart-container"></div> |
|
|
|
</el-col> |
|
|
|
</el-row> |
|
|
|
<el-row :gutter="16" class="chart-row"> |
|
|
|
<el-col :span="12"> |
|
|
|
<div ref="quarterBarRef" class="chart-container"></div> |
|
|
|
</el-col> |
|
|
|
<el-col :span="12"> |
|
|
|
<div ref="monthBarRef" class="chart-container"></div> |
|
|
|
</el-col> |
|
|
|
</el-row> |
|
|
|
|
|
|
|
<!-- 分析列表 --> |
|
|
|
<div class="analysis-section"> |
|
|
|
<div class="section-header"> |
|
|
|
<span class="section-title">执法分析(按企业维度统计)</span> |
|
|
|
<el-button type="warning" size="small" @click="handleExport">导出</el-button> |
|
|
|
</div> |
|
|
|
<el-table :data="analysisTableData" border stripe style="width: 100%"> |
|
|
|
<el-table-column label="区域" prop="district" width="100" /> |
|
|
|
<el-table-column label="街道" prop="street" width="100" /> |
|
|
|
<el-table-column label="企业名称" prop="enterpriseName" min-width="180" show-overflow-tooltip /> |
|
|
|
<el-table-column label="执法单位" prop="bureauDeptName" min-width="150" show-overflow-tooltip /> |
|
|
|
<el-table-column label="执法人员" prop="lawEnforcer" width="120" show-overflow-tooltip /> |
|
|
|
<el-table-column label="执法事项" prop="lawEnforcementItemText" min-width="150" show-overflow-tooltip /> |
|
|
|
<el-table-column label="执法时间" prop="enforcementTime" width="110" align="center" /> |
|
|
|
<el-table-column label="执法次数" prop="totalCount" width="90" align="center"> |
|
|
|
<template #default="{ row }"> |
|
|
|
<span :style="getColorStyle(row.colorLevel)">{{ row.totalCount }}</span> |
|
|
|
</template> |
|
|
|
</el-table-column> |
|
|
|
</el-table> |
|
|
|
|
|
|
|
<el-pagination |
|
|
|
v-model:current-page="queryParams.pageNum" |
|
|
|
v-model:page-size="queryParams.pageSize" |
|
|
|
:page-sizes="[10, 20, 50, 100]" |
|
|
|
:total="analysisTotal" |
|
|
|
layout="total, sizes, prev, pager, next, jumper" |
|
|
|
@size-change="loadAnalysisList" |
|
|
|
@current-change="loadAnalysisList" |
|
|
|
class="mt-4" |
|
|
|
/> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
</template> |
|
|
|
|
|
|
|
@ -252,27 +331,10 @@ window.addEventListener('resize', () => { |
|
|
|
margin-bottom: 16px; |
|
|
|
} |
|
|
|
.chart-row { |
|
|
|
margin-bottom: 24px; |
|
|
|
margin-bottom: 16px; |
|
|
|
} |
|
|
|
.chart-container { |
|
|
|
width: 100%; |
|
|
|
height: 350px; |
|
|
|
} |
|
|
|
.analysis-section { |
|
|
|
margin-top: 16px; |
|
|
|
.section-header { |
|
|
|
display: flex; |
|
|
|
align-items: center; |
|
|
|
justify-content: space-between; |
|
|
|
margin-bottom: 12px; |
|
|
|
.section-title { |
|
|
|
font-size: 16px; |
|
|
|
font-weight: bold; |
|
|
|
color: #303133; |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
.mt-4 { |
|
|
|
margin-top: 16px; |
|
|
|
height: 380px; |
|
|
|
} |
|
|
|
</style> |
|
|
|
|