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