From 39e6447b576ee5504e157b8168d91774e6fdebe1 Mon Sep 17 00:00:00 2001
From: gaoluyang <2820782392@qq.com>
Date: 星期五, 22 五月 2026 10:51:51 +0800
Subject: [PATCH] 浪潮lims 1.添加数据分析与展示页面
---
src/api/lims/dataAnalysis.js | 47 +++
src/views/lims/dataAnalysis/index.vue | 796 +++++++++++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 843 insertions(+), 0 deletions(-)
diff --git a/src/api/lims/dataAnalysis.js b/src/api/lims/dataAnalysis.js
new file mode 100644
index 0000000..80fb4c2
--- /dev/null
+++ b/src/api/lims/dataAnalysis.js
@@ -0,0 +1,47 @@
+// 鏁版嵁鍒嗘瀽涓庡睍绀烘帴鍙�
+import request from '@/utils/request'
+
+// 鑾峰彇鐪嬫澘鍏ㄩ儴鏁版嵁锛堟帹鑽愶級
+export function getDashboard(query) {
+ return request({
+ url: '/lims/dataAnalysis/dashboard',
+ method: 'get',
+ params: query
+ })
+}
+
+// 鑾峰彇姒傝鎸囨爣
+export function getOverview(query) {
+ return request({
+ url: '/lims/dataAnalysis/overview',
+ method: 'get',
+ params: query
+ })
+}
+
+// 鑾峰彇瓒嬪娍鏁版嵁
+export function getTrend(query) {
+ return request({
+ url: '/lims/dataAnalysis/trend',
+ method: 'get',
+ params: query
+ })
+}
+
+// 鑾峰彇姣旇緝鍒嗘瀽鏁版嵁
+export function getComparison(query) {
+ return request({
+ url: '/lims/dataAnalysis/comparison',
+ method: 'get',
+ params: query
+ })
+}
+
+// 鑾峰彇璐ㄩ噺鍒嗗竷鏁版嵁
+export function getQualityDistribution(query) {
+ return request({
+ url: '/lims/dataAnalysis/qualityDistribution',
+ method: 'get',
+ params: query
+ })
+}
diff --git a/src/views/lims/dataAnalysis/index.vue b/src/views/lims/dataAnalysis/index.vue
new file mode 100644
index 0000000..d3b40f4
--- /dev/null
+++ b/src/views/lims/dataAnalysis/index.vue
@@ -0,0 +1,796 @@
+<template>
+ <div class="app-container data-analysis-container">
+ <!-- 绛涢�夋潯浠� -->
+ <div class="search_form">
+ <div class="search-left">
+ <span class="search_title">鏃堕棿鑼冨洿锛�</span>
+ <el-date-picker
+ v-model="dateRange"
+ type="daterange"
+ range-separator="鑷�"
+ start-placeholder="寮�濮嬫棩鏈�"
+ end-placeholder="缁撴潫鏃ユ湡"
+ value-format="YYYY-MM-DD"
+ @change="handleQuery"
+ style="width: 260px"
+ />
+ <span class="search_title" style="margin-left: 20px">鏁版嵁绫诲瀷锛�</span>
+ <el-select
+ v-model="searchForm.dataType"
+ placeholder="璇烽�夋嫨"
+ clearable
+ @change="handleQuery"
+ style="width: 140px"
+ >
+ <el-option label="娓╁害" value="temperature" />
+ <el-option label="婀垮害" value="humidity" />
+ <el-option label="鍘嬪姏" value="pressure" />
+ <el-option label="娴侀噺" value="flow" />
+ <el-option label="娴撳害" value="concentration" />
+ </el-select>
+ <span class="search_title" style="margin-left: 20px">璁惧缂栧彿锛�</span>
+ <el-input
+ v-model="searchForm.deviceCode"
+ placeholder="璇疯緭鍏ヨ澶囩紪鍙�"
+ clearable
+ @change="handleQuery"
+ style="width: 160px"
+ />
+ <span class="search_title" style="margin-left: 20px">瓒嬪娍绮掑害锛�</span>
+ <el-radio-group v-model="searchForm.granularity" @change="handleQuery">
+ <el-radio-button value="day">鎸夊ぉ</el-radio-button>
+ <el-radio-button value="hour">鎸夊皬鏃�</el-radio-button>
+ </el-radio-group>
+ </div>
+ <div class="search-right">
+ <el-button type="primary" @click="handleQuery">鏌ヨ</el-button>
+ <el-button @click="resetQuery">閲嶇疆</el-button>
+ </div>
+ </div>
+
+ <!-- 姒傝鎸囨爣鍗$墖 -->
+ <div class="overview-cards">
+ <div class="overview-card">
+ <div class="card-icon total">
+ <el-icon><DataLine /></el-icon>
+ </div>
+ <div class="card-content">
+ <div class="card-label">閲囬泦鎬绘潯鏁�</div>
+ <div class="card-value">{{ dashboardData.overview?.totalCollections || 0 }}</div>
+ </div>
+ </div>
+ <div class="overview-card">
+ <div class="card-icon today">
+ <el-icon><Calendar /></el-icon>
+ </div>
+ <div class="card-content">
+ <div class="card-label">浠婃棩閲囬泦</div>
+ <div class="card-value">{{ dashboardData.overview?.todayCollections || 0 }}</div>
+ </div>
+ </div>
+ <div class="overview-card">
+ <div class="card-icon abnormal">
+ <el-icon><Warning /></el-icon>
+ </div>
+ <div class="card-content">
+ <div class="card-label">寮傚父鏁版嵁</div>
+ <div class="card-value">{{ dashboardData.overview?.abnormalCollections || 0 }}</div>
+ </div>
+ </div>
+ <div class="overview-card">
+ <div class="card-icon rate">
+ <el-icon><CircleCheck /></el-icon>
+ </div>
+ <div class="card-content">
+ <div class="card-label">鍚堟牸鐜�</div>
+ <div class="card-value">{{ dashboardData.overview?.qualifiedRate || 0 }}%</div>
+ </div>
+ </div>
+ <div class="overview-card">
+ <div class="card-icon experiment">
+ <el-icon><Collection /></el-icon>
+ </div>
+ <div class="card-content">
+ <div class="card-label">杩涜涓疄楠�</div>
+ <div class="card-value">{{ dashboardData.overview?.inProgressExperiments || 0 }}</div>
+ </div>
+ </div>
+ <div class="overview-card">
+ <div class="card-icon warning">
+ <el-icon><Bell /></el-icon>
+ </div>
+ <div class="card-content">
+ <div class="card-label">棰勮鐩戞帶</div>
+ <div class="card-value">{{ dashboardData.overview?.warningMonitors || 0 }}</div>
+ </div>
+ </div>
+ <div class="overview-card">
+ <div class="card-icon sample">
+ <el-icon><Box /></el-icon>
+ </div>
+ <div class="card-content">
+ <div class="card-label">鍦ㄥ簱鏍峰搧</div>
+ <div class="card-value">{{ dashboardData.overview?.inStockSamples || 0 }}</div>
+ </div>
+ </div>
+ </div>
+
+ <!-- 鍥捐〃鍖哄煙 -->
+ <div class="charts-container">
+ <!-- 瓒嬪娍鍒嗘瀽 -->
+ <div class="chart-panel trend-panel">
+ <div class="panel-header">
+ <div class="panel-title">
+ <el-icon><TrendCharts /></el-icon>
+ 鏁版嵁瓒嬪娍鍒嗘瀽
+ </div>
+ <div class="panel-extra">
+ <span v-if="dashboardData.startTime && dashboardData.endTime">
+ {{ formatDateTime(dashboardData.startTime) }} ~ {{ formatDateTime(dashboardData.endTime) }}
+ </span>
+ </div>
+ </div>
+ <div class="chart-content">
+ <div ref="trendChartRef" style="width: 100%; height: 100%;"></div>
+ </div>
+ </div>
+
+ <!-- 姣旇緝鍒嗘瀽 -->
+ <div class="chart-panel comparison-panel">
+ <div class="panel-header">
+ <div class="panel-title">
+ <el-icon><Histogram /></el-icon>
+ 鏁版嵁姣旇緝鍒嗘瀽
+ </div>
+ <el-radio-group v-model="searchForm.dimension" size="small" @change="handleQuery">
+ <el-radio-button value="dataType">鎸夋暟鎹被鍨�</el-radio-button>
+ <el-radio-button value="deviceName">鎸夎澶囧悕绉�</el-radio-button>
+ <el-radio-button value="deviceCode">鎸夎澶囩紪鍙�</el-radio-button>
+ </el-radio-group>
+ </div>
+ <div class="chart-content">
+ <div ref="comparisonChartRef" style="width: 100%; height: 100%;"></div>
+ </div>
+ </div>
+
+ <!-- 璐ㄩ噺鍒嗗竷 -->
+ <div class="chart-panel quality-panel">
+ <div class="panel-header">
+ <div class="panel-title">
+ <el-icon><PieChart /></el-icon>
+ 璐ㄩ噺鍒嗗竷缁熻
+ </div>
+ </div>
+ <div class="chart-content">
+ <div ref="qualityChartRef" style="width: 100%; height: 100%;"></div>
+ </div>
+ <!-- 璐ㄩ噺鍒嗗竷鍥句緥 -->
+ <div class="quality-legend" v-if="qualityChartData.length > 0">
+ <div class="legend-item" v-for="item in qualityChartData" :key="item.category">
+ <span class="legend-dot" :style="{ backgroundColor: item.color }"></span>
+ <span class="legend-label">{{ item.name }}</span>
+ <span class="legend-value">{{ item.value }}</span>
+ <span class="legend-ratio">({{ item.ratio }}%)</span>
+ </div>
+ </div>
+ <div v-else class="no-data-text">鏆傛棤鏁版嵁</div>
+ </div>
+ </div>
+ </div>
+</template>
+
+<script setup>
+import { ref, reactive, computed, onMounted, onBeforeUnmount, nextTick } from 'vue'
+import {
+ DataLine, Calendar, Warning, CircleCheck,
+ Collection, Bell, Box, TrendCharts, Histogram, PieChart
+} from '@element-plus/icons-vue'
+import { getDashboard } from '@/api/lims/dataAnalysis'
+import { ElMessage } from 'element-plus'
+import * as echarts from 'echarts'
+
+// 鎼滅储琛ㄥ崟
+const searchForm = reactive({
+ dataType: '',
+ deviceCode: '',
+ granularity: 'day',
+ dimension: 'dataType'
+})
+
+// 鏃ユ湡鑼冨洿
+const dateRange = ref([])
+
+// 鐪嬫澘鏁版嵁
+const dashboardData = ref({
+ overview: {},
+ trend: [],
+ comparison: [],
+ qualityDistribution: []
+})
+
+// 鍥捐〃瀹炰緥
+const trendChartRef = ref(null)
+const comparisonChartRef = ref(null)
+const qualityChartRef = ref(null)
+let trendChart = null
+let comparisonChart = null
+let qualityChart = null
+
+// 鍔犺浇鐘舵��
+const loading = ref(false)
+
+// 鑾峰彇榛樿鏃堕棿鑼冨洿锛堟渶杩�7澶╋級
+const getDefaultDateRange = () => {
+ const end = new Date()
+ const start = new Date()
+ start.setDate(start.getDate() - 7)
+ return [
+ start.toISOString().slice(0, 10),
+ end.toISOString().slice(0, 10)
+ ]
+}
+
+// 鏍煎紡鍖栨棩鏈熸椂闂�
+const formatDateTime = (dateTime) => {
+ if (!dateTime) return ''
+ return dateTime.replace('T', ' ')
+}
+
+// 鍒濆鍖栬秼鍔垮浘琛�
+const initTrendChart = () => {
+ if (!trendChartRef.value) return
+ trendChart = echarts.init(trendChartRef.value)
+ updateTrendChart()
+}
+
+// 鏇存柊瓒嬪娍鍥捐〃
+const updateTrendChart = () => {
+ if (!trendChart) return
+
+ const trend = dashboardData.value.trend || []
+ const xAxis = trend.map(item => item.time)
+ const pointCount = trend.map(item => item.pointCount)
+ const avgValue = trend.map(item => item.avgValue)
+ const maxValue = trend.map(item => item.maxValue)
+ const minValue = trend.map(item => item.minValue)
+
+ const option = {
+ tooltip: {
+ trigger: 'axis',
+ axisPointer: {
+ type: 'cross'
+ }
+ },
+ legend: {
+ data: ['閲囬泦鐐规暟', '骞冲潎鍊�', '鏈�澶у��', '鏈�灏忓��'],
+ bottom: 0
+ },
+ grid: {
+ left: '3%',
+ right: '4%',
+ bottom: '10%',
+ top: '10%',
+ containLabel: true
+ },
+ xAxis: {
+ type: 'category',
+ boundaryGap: false,
+ data: xAxis
+ },
+ yAxis: [
+ {
+ type: 'value',
+ name: '閲囬泦鐐规暟',
+ position: 'left'
+ },
+ {
+ type: 'value',
+ name: '鏁板��',
+ position: 'right'
+ }
+ ],
+ series: [
+ {
+ name: '閲囬泦鐐规暟',
+ type: 'line',
+ data: pointCount,
+ smooth: true,
+ areaStyle: {
+ opacity: 0.1
+ }
+ },
+ {
+ name: '骞冲潎鍊�',
+ type: 'line',
+ yAxisIndex: 1,
+ data: avgValue,
+ smooth: true
+ },
+ {
+ name: '鏈�澶у��',
+ type: 'line',
+ yAxisIndex: 1,
+ data: maxValue,
+ smooth: true
+ },
+ {
+ name: '鏈�灏忓��',
+ type: 'line',
+ yAxisIndex: 1,
+ data: minValue,
+ smooth: true
+ }
+ ]
+ }
+
+ trendChart.setOption(option, true)
+}
+
+// 鍒濆鍖栨瘮杈冨浘琛�
+const initComparisonChart = () => {
+ if (!comparisonChartRef.value) return
+ comparisonChart = echarts.init(comparisonChartRef.value)
+ updateComparisonChart()
+}
+
+// 鏇存柊姣旇緝鍥捐〃
+const updateComparisonChart = () => {
+ if (!comparisonChart) return
+
+ const comparison = dashboardData.value.comparison || []
+
+ if (comparison.length === 0) {
+ comparisonChart.clear()
+ return
+ }
+
+ const xAxis = comparison.map(item => item.dimensionValue)
+ const pointCount = comparison.map(item => item.pointCount)
+ const avgValue = comparison.map(item => item.avgValue)
+
+ const option = {
+ tooltip: {
+ trigger: 'axis',
+ axisPointer: {
+ type: 'shadow'
+ }
+ },
+ legend: {
+ data: ['閲囬泦鐐规暟', '骞冲潎鍊�'],
+ bottom: 0
+ },
+ grid: {
+ left: '3%',
+ right: '4%',
+ bottom: '10%',
+ top: '10%',
+ containLabel: true
+ },
+ xAxis: {
+ type: 'category',
+ data: xAxis
+ },
+ yAxis: [
+ {
+ type: 'value',
+ name: '閲囬泦鐐规暟'
+ },
+ {
+ type: 'value',
+ name: '骞冲潎鍊�'
+ }
+ ],
+ series: [
+ {
+ name: '閲囬泦鐐规暟',
+ type: 'bar',
+ data: pointCount,
+ itemStyle: {
+ borderRadius: [4, 4, 0, 0]
+ }
+ },
+ {
+ name: '骞冲潎鍊�',
+ type: 'bar',
+ yAxisIndex: 1,
+ data: avgValue,
+ itemStyle: {
+ borderRadius: [4, 4, 0, 0]
+ }
+ }
+ ]
+ }
+
+ comparisonChart.setOption(option, true)
+}
+
+// 璐ㄩ噺鍒嗗竷棰滆壊鏄犲皠
+const qualityColorMap = {
+ qualified: '#67C23A',
+ abnormal: '#F56C6C',
+ pending: '#E6A23C'
+}
+
+const qualityNameMap = {
+ qualified: '鍚堟牸',
+ abnormal: '寮傚父',
+ pending: '寰呭鐞�'
+}
+
+// 璐ㄩ噺鍒嗗竷鍥捐〃鏁版嵁
+const qualityChartData = computed(() => {
+ const distribution = dashboardData.value.qualityDistribution || []
+ return distribution.map(item => ({
+ ...item,
+ name: qualityNameMap[item.category] || item.category,
+ value: item.pointCount,
+ color: qualityColorMap[item.category] || '#909399'
+ }))
+})
+
+// 鍒濆鍖栬川閲忓垎甯冨浘琛�
+const initQualityChart = () => {
+ if (!qualityChartRef.value) return
+ qualityChart = echarts.init(qualityChartRef.value)
+ updateQualityChart()
+}
+
+// 鏇存柊璐ㄩ噺鍒嗗竷鍥捐〃
+const updateQualityChart = () => {
+ if (!qualityChart) return
+
+ const distribution = dashboardData.value.qualityDistribution || []
+
+ if (distribution.length === 0) {
+ qualityChart.clear()
+ return
+ }
+
+ const data = distribution.map(item => ({
+ name: qualityNameMap[item.category] || item.category,
+ value: item.pointCount,
+ itemStyle: {
+ color: qualityColorMap[item.category] || '#909399'
+ }
+ }))
+
+ const option = {
+ tooltip: {
+ trigger: 'item',
+ formatter: '{b}: {c} ({d}%)'
+ },
+ series: [
+ {
+ type: 'pie',
+ radius: ['40%', '70%'],
+ avoidLabelOverlap: false,
+ itemStyle: {
+ borderRadius: 10,
+ borderColor: '#fff',
+ borderWidth: 2
+ },
+ label: {
+ show: false,
+ position: 'center'
+ },
+ emphasis: {
+ label: {
+ show: true,
+ fontSize: 20,
+ fontWeight: 'bold'
+ }
+ },
+ labelLine: {
+ show: false
+ },
+ data: data
+ }
+ ]
+ }
+
+ qualityChart.setOption(option, true)
+}
+
+// 鏌ヨ鏁版嵁
+const handleQuery = async () => {
+ loading.value = true
+ try {
+ const params = {
+ ...searchForm
+ }
+
+ // 澶勭悊鏃堕棿鑼冨洿 - 灏嗘棩鏈熻浆鎹负鏃ユ湡鏃堕棿鏍煎紡
+ if (dateRange.value && dateRange.value.length === 2) {
+ params.startTime = dateRange.value[0] + ' 00:00:00'
+ params.endTime = dateRange.value[1] + ' 23:59:59'
+ }
+
+ const res = await getDashboard(params)
+ if (res.code === 200) {
+ dashboardData.value = res.data || {}
+
+ // 鏇存柊鍥捐〃
+ nextTick(() => {
+ updateTrendChart()
+ updateComparisonChart()
+ updateQualityChart()
+ })
+ } else {
+ ElMessage.error(res.msg || '鑾峰彇鏁版嵁澶辫触')
+ }
+ } catch (error) {
+ console.error('鑾峰彇鐪嬫澘鏁版嵁澶辫触:', error)
+ ElMessage.error('鑾峰彇鏁版嵁澶辫触')
+ } finally {
+ loading.value = false
+ }
+}
+
+// 閲嶇疆鏌ヨ
+const resetQuery = () => {
+ searchForm.dataType = ''
+ searchForm.deviceCode = ''
+ searchForm.granularity = 'day'
+ searchForm.dimension = 'dataType'
+ dateRange.value = getDefaultDateRange()
+ handleQuery()
+}
+
+// 绐楀彛澶у皬鏀瑰彉鏃堕噸鏂拌皟鏁村浘琛�
+const handleResize = () => {
+ trendChart?.resize()
+ comparisonChart?.resize()
+ qualityChart?.resize()
+}
+
+// 鍒濆鍖�
+onMounted(() => {
+ dateRange.value = getDefaultDateRange()
+ nextTick(() => {
+ initTrendChart()
+ initComparisonChart()
+ initQualityChart()
+ handleQuery()
+ })
+ window.addEventListener('resize', handleResize)
+})
+
+onBeforeUnmount(() => {
+ window.removeEventListener('resize', handleResize)
+ trendChart?.dispose()
+ comparisonChart?.dispose()
+ qualityChart?.dispose()
+})
+</script>
+
+<style lang="scss" scoped>
+.data-analysis-container {
+ .search_form {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ flex-wrap: wrap;
+ gap: 10px;
+ margin-bottom: 20px;
+
+ .search-left {
+ display: flex;
+ align-items: center;
+ flex-wrap: wrap;
+ gap: 10px;
+ }
+
+ .search-right {
+ display: flex;
+ gap: 10px;
+ }
+ }
+
+ // 姒傝鍗$墖
+ .overview-cards {
+ display: grid;
+ grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
+ gap: 16px;
+ margin-bottom: 20px;
+
+ .overview-card {
+ background: #fff;
+ border-radius: 8px;
+ padding: 20px;
+ display: flex;
+ align-items: center;
+ gap: 16px;
+ box-shadow: 0 2px 12px rgba(0, 0, 0, 0.05);
+ transition: transform 0.3s;
+
+ &:hover {
+ transform: translateY(-2px);
+ box-shadow: 0 4px 20px rgba(0, 0, 0, 0.1);
+ }
+
+ .card-icon {
+ width: 56px;
+ height: 56px;
+ border-radius: 12px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ font-size: 28px;
+
+ &.total {
+ background: linear-gradient(135deg, #409EFF 0%, #1677FF 100%);
+ color: #fff;
+ }
+
+ &.today {
+ background: linear-gradient(135deg, #67C23A 0%, #52C41A 100%);
+ color: #fff;
+ }
+
+ &.abnormal {
+ background: linear-gradient(135deg, #F56C6C 0%, #FF4D4F 100%);
+ color: #fff;
+ }
+
+ &.rate {
+ background: linear-gradient(135deg, #E6A23C 0%, #FAAD14 100%);
+ color: #fff;
+ }
+
+ &.experiment {
+ background: linear-gradient(135deg, #909399 0%, #666666 100%);
+ color: #fff;
+ }
+
+ &.warning {
+ background: linear-gradient(135deg, #FF6B6B 0%, #EE5A6F 100%);
+ color: #fff;
+ }
+
+ &.sample {
+ background: linear-gradient(135deg, #36CFC9 0%, #13C2C2 100%);
+ color: #fff;
+ }
+ }
+
+ .card-content {
+ flex: 1;
+
+ .card-label {
+ font-size: 14px;
+ color: #909399;
+ margin-bottom: 8px;
+ }
+
+ .card-value {
+ font-size: 28px;
+ font-weight: 600;
+ color: #303133;
+ line-height: 1;
+ }
+ }
+ }
+ }
+
+ // 鍥捐〃瀹瑰櫒
+ .charts-container {
+ display: grid;
+ grid-template-columns: 2fr 1fr;
+ grid-template-rows: 1fr 1fr;
+ gap: 20px;
+
+ .chart-panel {
+ background: #fff;
+ border-radius: 8px;
+ padding: 20px;
+ box-shadow: 0 2px 12px rgba(0, 0, 0, 0.05);
+
+ .panel-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ margin-bottom: 20px;
+
+ .panel-title {
+ font-size: 16px;
+ font-weight: 600;
+ color: #303133;
+ display: flex;
+ align-items: center;
+ gap: 8px;
+
+ .el-icon {
+ font-size: 20px;
+ color: #409EFF;
+ }
+ }
+
+ .panel-extra {
+ font-size: 12px;
+ color: #909399;
+ }
+ }
+
+ .chart-content {
+ height: 300px;
+ }
+ }
+
+ .trend-panel {
+ grid-row: span 2;
+
+ .chart-content {
+ height: 620px;
+ }
+ }
+
+ .quality-panel {
+ .chart-content {
+ height: 220px;
+ }
+
+ .quality-legend {
+ display: flex;
+ flex-direction: column;
+ gap: 12px;
+ margin-top: 16px;
+ padding-top: 16px;
+ border-top: 1px solid #EBEEF5;
+
+ .legend-item {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+
+ .legend-dot {
+ width: 12px;
+ height: 12px;
+ border-radius: 50%;
+ }
+
+ .legend-label {
+ flex: 1;
+ font-size: 14px;
+ color: #606266;
+ }
+
+ .legend-value {
+ font-size: 16px;
+ font-weight: 600;
+ color: #303133;
+ }
+
+ .legend-ratio {
+ font-size: 12px;
+ color: #909399;
+ }
+ }
+ }
+
+ .no-data-text {
+ text-align: center;
+ color: #909399;
+ font-size: 14px;
+ padding: 40px 0;
+ }
+ }
+ }
+}
+
+@media (max-width: 1200px) {
+ .data-analysis-container {
+ .charts-container {
+ grid-template-columns: 1fr;
+ grid-template-rows: auto;
+
+ .trend-panel {
+ grid-row: span 1;
+
+ .chart-content {
+ height: 300px;
+ }
+ }
+ }
+ }
+}
+</style>
--
Gitblit v1.9.3