gaoluyang
5 天以前 d5d3f57f11d829909bd2b55bc1bab331b69a1607
中强恒兴质量管理页面添加
已修改1个文件
已添加3个文件
1763 ■■■■■ 文件已修改
src/views/qualityManagement/nonconformingManagement/index.vue 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/qualityManagement/visualization/qualityDashboard.vue 307 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/reportAnalysis/reportManagement.vue 733 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/reportAnalysis/reportManagement/index.vue 716 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/qualityManagement/nonconformingManagement/index.vue
@@ -4,7 +4,7 @@
      <div style="display: flex;flex-direction: row;align-items: center;">
        <div>
          <span class="search_title">类型:</span>
          <el-select v-model="searchForm.inspectType" clearable style="width: 240px" @change="handleQuery">
          <el-select v-model="searchForm.inspectType" clearable style="width: 200px" @change="handleQuery">
            <el-option label="原材料检验" :value="0" />
            <el-option label="过程检验" :value="1" />
            <el-option label="出厂检验" :value="2" />
@@ -12,7 +12,7 @@
        </div>
        <div style="margin-left: 10px">
          <span class="search_title">状态:</span>
          <el-select v-model="searchForm.inspectState" clearable style="width: 240px" @change="handleQuery">
          <el-select v-model="searchForm.inspectState" clearable style="width: 200px" @change="handleQuery">
            <el-option label="待处理" :value="0" />
            <el-option label="已处理" :value="1" />
          </el-select>
@@ -21,7 +21,7 @@
          <span class="search_title">产品名称:</span>
          <el-input
              v-model="searchForm.productName"
              style="width: 240px"
              style="width: 200px"
              placeholder="请输入产品名称搜索"
              @change="handleQuery"
              clearable
@@ -30,6 +30,7 @@
        </div>
        <span  style="margin-left: 10px" class="search_title">检测日期:</span>
        <el-date-picker  v-model="searchForm.entryDate" value-format="YYYY-MM-DD" format="YYYY-MM-DD" type="daterange"
                                                 style="width: 300px"
                         placeholder="请选择" clearable @change="changeDaterange" />
        <el-button type="primary" @click="handleQuery" style="margin-left: 10px">搜索</el-button>
      </div>
src/views/qualityManagement/visualization/qualityDashboard.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,307 @@
<template>
  <div class="quality-dashboard">
    <el-row :gutter="16">
      <el-col :xs="24" :sm="12">
        <el-card shadow="hover" class="panel">
          <template #header>
            <div class="panel-title">
              æ£€æµ‹æ ·å“åŠ¨æ€çŠ¶æ€
              <div class="actions">
                <el-switch v-model="voiceEnabled" active-text="语音预警" inactive-text="静音" />
              </div>
            </div>
          </template>
          <div class="status-list">
            <div v-for="item in sampleStatus" :key="item.id" class="status-item" :class="item.status">
              <div class="left">
                <span class="dot" :class="item.status"></span>
                <span class="name">{{ item.name }}</span>
              </div>
              <div class="right">
                <el-tag :type="statusTagType(item.status)" size="small">{{ statusLabel(item.status) }}</el-tag>
                <span class="time">{{ item.time }}</span>
              </div>
            </div>
          </div>
        </el-card>
      </el-col>
      <el-col :xs="24" :sm="12">
        <el-card shadow="hover" class="panel">
          <template #header>
            <div class="panel-title">任务排行(Top 10)</div>
          </template>
          <EChart :xAxis="tasksXAxis" :yAxis="[{ type: 'value' }]" :series="tasksSeries" :grid="{ left: 40, right: 20, top: 20, bottom: 40 }" :tooltip="{ trigger: 'axis' }" :barColors="['#3b82f6']" :chartStyle="{ height: '320px', width: '100%' }" />
        </el-card>
      </el-col>
    </el-row>
    <el-row :gutter="16" style="margin-top: 16px;">
      <el-col :xs="24" :sm="14">
        <el-card shadow="hover" class="panel">
          <template #header>
            <div class="panel-title">历史趋势</div>
          </template>
          <EChart :xAxis="[{ type: 'category', data: trendXAxis }]" :yAxis="[{ type: 'value', name: '数量' }]" :series="trendSeries" :tooltip="{ trigger: 'axis' }" :legend="{ top: 0 }" :lineColors="['#10b981', '#f59e0b']" :chartStyle="{ height: '340px', width: '100%' }" />
        </el-card>
      </el-col>
      <el-col :xs="24" :sm="10">
        <el-card shadow="hover" class="panel">
          <template #header>
            <div class="panel-title">合格率分析</div>
          </template>
          <EChart :series="passRateSeries" :legend="{ show: false }" :chartStyle="{ height: '340px', width: '100%' }" />
          <div class="passrate-text">
            å½“前合格率:<b>{{ (passRate * 100).toFixed(1) }}%</b>
          </div>
        </el-card>
      </el-col>
    </el-row>
    <el-row :gutter="16" style="margin-top: 16px;">
      <el-col :xs="24">
        <el-card shadow="hover" class="panel">
          <template #header>
            <div class="panel-title">SPC æŽ§åˆ¶å›¾ï¼ˆXbar)</div>
          </template>
          <EChart :xAxis="[{ type: 'category', data: spcXAxis }]" :yAxis="[{ type: 'value', name: '测量值' }]" :series="spcSeries" :legend="{ top: 0 }" :tooltip="{ trigger: 'axis' }" :lineColors="['#2563eb', '#ef4444', '#f97316', '#22c55e']" :chartStyle="{ height: '380px', width: '100%' }" />
        </el-card>
      </el-col>
    </el-row>
  </div>
</template>
<script setup>
import { onMounted, onBeforeUnmount, reactive, ref } from 'vue'
import EChart from '@/components/Echarts/echarts.vue'
const voiceEnabled = ref(true)
let dataTimer = null
// 1) æ ·å“åŠ¨æ€çŠ¶æ€ï¼ˆæ»šåŠ¨æ›´æ–°ï¼‰
const sampleStatus = ref([])
const statusPool = ['processing', 'warning', 'error', 'success']
function statusLabel(s) {
  return s === 'processing' ? '检测中' : s === 'warning' ? '预警' : s === 'error' ? '不合格' : '合格'
}
function statusTagType(s) {
  return s === 'processing' ? 'info' : s === 'warning' ? 'warning' : s === 'error' ? 'danger' : 'success'
}
function randomSample() {
  const id = Math.random().toString(36).slice(2, 8)
  const status = statusPool[Math.floor(Math.random() * statusPool.length)]
  const name = `样品-${Math.floor(Math.random() * 900 + 100)}`
  const time = new Date().toLocaleTimeString('zh-CN', { hour12: false })
  return { id, name, status, time }
}
// 2) ä»»åŠ¡æŽ’è¡Œï¼ˆæŸ±çŠ¶å›¾ï¼‰
const tasksXAxis = reactive([{ type: 'category', data: [] }])
const tasksSeries = ref([
  {
    type: 'bar',
    data: [],
    label: { show: true, position: 'inside', align: 'center', verticalAlign: 'middle', color: '#fff' },
    encode: undefined,
  },
])
// 3) åŽ†å²è¶‹åŠ¿ï¼ˆæŠ˜çº¿ï¼‰
const trendXAxis = ref([])
const trendSeries = ref([
  { name: '来样数', type: 'line', smooth: true, data: [] },
  { name: '完成数', type: 'line', smooth: true, data: [] },
])
// 4) åˆæ ¼çŽ‡åˆ†æžï¼ˆä»ªè¡¨ç›˜ï¼‰
const passRate = ref(0.92)
const passRateSeries = ref([
  {
    type: 'gauge',
    progress: { show: true, width: 12 },
    axisLine: { lineStyle: { width: 12 } },
    pointer: { show: true },
    detail: { valueAnimation: true, formatter: (v) => `${(v * 100).toFixed(1)}%` },
    data: [{ value: passRate.value }],
  },
])
// 5) SPC æŽ§åˆ¶å›¾
const spcXAxis = ref([])
const spcData = ref([]) // å®žé™…测量值
const CL = ref(50)
const UCL = ref(55)
const LCL = ref(45)
const spcSeries = ref([
  {
    name: '测量均值',
    type: 'line',
    smooth: false,
    symbol: 'circle',
    data: [],
    markLine: {
      symbol: 'none',
      lineStyle: { type: 'dashed', color: '#999' },
      data: [
        { yAxis: () => UCL.value, name: 'UCL' },
        { yAxis: () => CL.value, name: 'CL' },
        { yAxis: () => LCL.value, name: 'LCL' },
      ],
      label: { formatter: ({ name }) => name },
    },
  },
  { name: 'UCL', type: 'line', data: [], symbol: 'none', lineStyle: { type: 'dashed', color: '#ef4444' } },
  { name: 'CL', type: 'line', data: [], symbol: 'none', lineStyle: { type: 'dashed', color: '#f97316' } },
  { name: 'LCL', type: 'line', data: [], symbol: 'none', lineStyle: { type: 'dashed', color: '#22c55e' } },
])
// è¯­éŸ³æ’­æŠ¥
function speak(text) {
  if (!voiceEnabled.value) return
  if (!('speechSynthesis' in window)) return
  const utter = new SpeechSynthesisUtterance(text)
  utter.lang = 'zh-CN'
  try {
    window.speechSynthesis.cancel()
    window.speechSynthesis.speak(utter)
  } catch (e) {
    // ignore
  }
}
function refreshFakeData() {
  // æ ·å“çŠ¶æ€æ»šåŠ¨
  const next = randomSample()
  sampleStatus.value = [next, ...sampleStatus.value].slice(0, 8)
  // ä»»åŠ¡æŽ’è¡Œ
  const tasks = Array.from({ length: 10 }).map((_, i) => ({ name: `任务-${i + 1}`, count: Math.floor(Math.random() * 100 + 20) }))
  tasks.sort((a, b) => a.count - b.count)
  tasksXAxis.data = tasks.map(t => t.name)
  tasksSeries.value[0].data = tasks.map(t => t.count)
  // åŽ†å²è¶‹åŠ¿ï¼ˆè¿½åŠ ç‚¹ï¼‰
  const nowLabel = new Date().toLocaleTimeString('zh-CN', { minute: '2-digit', second: '2-digit' })
  if (trendXAxis.value.length > 15) {
    trendXAxis.value.shift()
    trendSeries.value[0].data.shift()
    trendSeries.value[1].data.shift()
  }
  trendXAxis.value.push(nowLabel)
  const incoming = Math.floor(Math.random() * 30 + 20)
  const finished = Math.max(0, incoming - Math.floor(Math.random() * 10))
  trendSeries.value[0].data.push(incoming)
  trendSeries.value[1].data.push(finished)
  // åˆæ ¼çŽ‡ï¼ˆè½»å¾®æ³¢åŠ¨ï¼‰
  const delta = (Math.random() - 0.5) * 0.02
  passRate.value = Math.min(0.99, Math.max(0.6, passRate.value + delta))
  passRateSeries.value[0].data[0].value = passRate.value
  // SPC æ•°æ®ï¼ˆçª—口移动)
  const nextVal = CL.value + (Math.random() - 0.5) * 8 // æ³¢åЍ
  if (spcXAxis.value.length > 30) {
    spcXAxis.value.shift()
    spcData.value.shift()
  }
  spcXAxis.value.push(`${spcXAxis.value.length + 1}`)
  spcData.value.push(parseFloat(nextVal.toFixed(2)))
  spcSeries.value[0].data = [...spcData.value]
  spcSeries.value[1].data = new Array(spcData.value.length).fill(UCL.value)
  spcSeries.value[2].data = new Array(spcData.value.length).fill(CL.value)
  spcSeries.value[3].data = new Array(spcData.value.length).fill(LCL.value)
  // è§¦å‘播报:合格率过低或 SPC è¶…限
  if (passRate.value < 0.8) {
    speak(`预警,当前合格率为 ${(passRate.value * 100).toFixed(0)}%,低于 80% é˜ˆå€¼`)
  }
  const last = spcData.value[spcData.value.length - 1]
  if (last > UCL.value) {
    speak(`预警,最新测量值 ${last.toFixed(2)} è¶…过上限`)
  }
  if (last < LCL.value) {
    speak(`预警,最新测量值 ${last.toFixed(2)} ä½ŽäºŽä¸‹é™`)
  }
}
onMounted(() => {
  // åˆå§‹åŒ–几条假数据
  sampleStatus.value = Array.from({ length: 5 }).map(() => randomSample())
  for (let i = 0; i < 10; i++) {
    trendXAxis.value.push(`T-${i}`)
    trendSeries.value[0].data.push(Math.floor(Math.random() * 30 + 20))
    trendSeries.value[1].data.push(Math.floor(Math.random() * 25 + 15))
  }
  for (let i = 0; i < 20; i++) {
    spcXAxis.value.push(`${i + 1}`)
    const v = CL.value + (Math.random() - 0.5) * 6
    spcData.value.push(parseFloat(v.toFixed(2)))
  }
  spcSeries.value[0].data = [...spcData.value]
  spcSeries.value[1].data = new Array(spcData.value.length).fill(UCL.value)
  spcSeries.value[2].data = new Array(spcData.value.length).fill(CL.value)
  spcSeries.value[3].data = new Array(spcData.value.length).fill(LCL.value)
  dataTimer = setInterval(refreshFakeData, 10000)
})
onBeforeUnmount(() => {
  if (dataTimer) clearInterval(dataTimer)
  try { window.speechSynthesis && window.speechSynthesis.cancel() } catch (e) {}
})
</script>
<style scoped>
.quality-dashboard {
  padding: 8px;
}
.panel-title {
  display: flex;
  align-items: center;
  justify-content: space-between;
  font-weight: 600;
}
.status-list {
  display: flex;
  flex-direction: column;
  gap: 8px;
  max-height: 320px;
  overflow: auto;
}
.status-item {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 8px 10px;
  border-radius: 6px;
  background: var(--el-fill-color-light);
}
.status-item .left {
  display: flex;
  align-items: center;
  gap: 8px;
}
.status-item .right {
  display: flex;
  align-items: center;
  gap: 10px;
}
.status-item .name { font-weight: 500; }
.status-item .time { color: var(--el-text-color-secondary); font-size: 12px; }
.dot {
  width: 8px;
  height: 8px;
  border-radius: 50%;
  display: inline-block;
}
.dot.processing { background: #60a5fa; }
.dot.warning { background: #f59e0b; }
.dot.error { background: #ef4444; }
.dot.success { background: #10b981; }
.passrate-text {
  text-align: center;
  margin-top: 8px;
}
</style>
src/views/reportAnalysis/reportManagement.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,733 @@
<template>
  <div class="report-management">
    <!-- é¡µé¢æ ‡é¢˜ -->
    <div class="page-header">
      <h2>报表管理</h2>
      <p>提供样品进度、设备使用、检测项目、领用记录等各类自动统计报表</p>
    </div>
    <!-- ç­›é€‰æ¡ä»¶ -->
    <el-card class="filter-card" shadow="never">
      <el-form :model="filterForm" inline>
        <el-form-item label="时间范围">
          <el-date-picker
            v-model="filterForm.dateRange"
            type="daterange"
            range-separator="至"
            start-placeholder="开始日期"
            end-placeholder="结束日期"
            format="YYYY-MM-DD"
            value-format="YYYY-MM-DD"
            @change="handleFilterChange"
          />
        </el-form-item>
        <el-form-item label="报表类型">
          <el-select v-model="filterForm.reportType" placeholder="请选择报表类型" @change="handleFilterChange">
            <el-option label="样品进度报表" value="sample" />
            <el-option label="设备使用报表" value="equipment" />
            <el-option label="检测项目报表" value="inspection" />
            <el-option label="领用记录报表" value="usage" />
          </el-select>
        </el-form-item>
        <el-form-item>
          <el-button type="primary" @click="handleFilterChange">查询</el-button>
          <el-button @click="resetFilter">重置</el-button>
          <el-button type="success" @click="exportReport">导出报表</el-button>
        </el-form-item>
      </el-form>
    </el-card>
    <!-- ç»Ÿè®¡å¡ç‰‡ -->
    <div class="statistics-cards">
      <el-row :gutter="20">
        <el-col :span="6">
          <el-card class="stat-card" shadow="hover">
            <div class="stat-content">
              <div class="stat-icon">
                <el-icon><Box /></el-icon>
              </div>
              <div class="stat-info">
                <div class="stat-number">{{ statistics.totalSamples }}</div>
                <div class="stat-label">总样品数</div>
              </div>
            </div>
          </el-card>
        </el-col>
        <el-col :span="6">
          <el-card class="stat-card" shadow="hover">
            <div class="stat-content">
              <div class="stat-icon">
                <el-icon><Tools /></el-icon>
              </div>
              <div class="stat-info">
                <div class="stat-number">{{ statistics.activeEquipment }}</div>
                <div class="stat-label">在用设备</div>
              </div>
            </div>
          </el-card>
        </el-col>
        <el-col :span="6">
          <el-card class="stat-card" shadow="hover">
            <div class="stat-content">
              <div class="stat-icon">
                <el-icon><Document /></el-icon>
              </div>
              <div class="stat-info">
                <div class="stat-number">{{ statistics.completedInspections }}</div>
                <div class="stat-label">已完成检测</div>
              </div>
            </div>
          </el-card>
        </el-col>
        <el-col :span="6">
          <el-card class="stat-card" shadow="hover">
            <div class="stat-content">
              <div class="stat-icon">
                <el-icon><ShoppingCart /></el-icon>
              </div>
              <div class="stat-info">
                <div class="stat-number">{{ statistics.totalUsage }}</div>
                <div class="stat-label">总领用次数</div>
              </div>
            </div>
          </el-card>
        </el-col>
      </el-row>
    </div>
    <!-- å›¾è¡¨åŒºåŸŸ -->
    <div class="charts-container">
      <el-row :gutter="20">
        <!-- æ ·å“è¿›åº¦å›¾è¡¨ -->
        <el-col :span="12">
          <el-card class="chart-card" shadow="hover">
            <template #header>
              <div class="card-header">
                <span>样品进度统计</span>
                <el-button type="text" @click="refreshSampleChart">刷新</el-button>
              </div>
            </template>
            <div ref="sampleChartRef" class="chart-container"></div>
          </el-card>
        </el-col>
        <!-- è®¾å¤‡ä½¿ç”¨å›¾è¡¨ -->
        <el-col :span="12">
          <el-card class="chart-card" shadow="hover">
            <template #header>
              <div class="card-header">
                <span>设备使用率统计</span>
                <el-button type="text" @click="refreshEquipmentChart">刷新</el-button>
              </div>
            </template>
            <div ref="equipmentChartRef" class="chart-container"></div>
          </el-card>
        </el-col>
      </el-row>
      <el-row :gutter="20" style="margin-top: 20px;">
        <!-- æ£€æµ‹é¡¹ç›®ç»Ÿè®¡ -->
        <el-col :span="12">
          <el-card class="chart-card" shadow="hover">
            <template #header>
              <div class="card-header">
                <span>检测项目分布</span>
                <el-button type="text" @click="refreshInspectionChart">刷新</el-button>
              </div>
            </template>
            <div ref="inspectionChartRef" class="chart-container"></div>
          </el-card>
        </el-col>
        <!-- é¢†ç”¨è®°å½•趋势 -->
        <el-col :span="12">
          <el-card class="chart-card" shadow="hover">
            <template #header>
              <div class="card-header">
                <span>领用记录趋势</span>
                <el-button type="text" @click="refreshUsageChart">刷新</el-button>
              </div>
            </template>
            <div ref="usageChartRef" class="chart-container"></div>
          </el-card>
        </el-col>
      </el-row>
    </div>
    <!-- è¯¦ç»†æ•°æ®è¡¨æ ¼ -->
    <el-card class="table-card" shadow="hover">
      <template #header>
        <div class="card-header">
          <span>详细数据</span>
          <div>
            <el-button type="primary" size="small" @click="refreshTable">刷新</el-button>
            <el-button type="success" size="small" @click="exportTable">导出</el-button>
          </div>
        </div>
      </template>
      <el-table
        :data="tableData"
        style="width: 100%"
        v-loading="tableLoading"
        stripe
        border
      >
        <el-table-column prop="id" label="编号" width="80" />
        <el-table-column prop="name" label="名称" />
        <el-table-column prop="type" label="类型" width="120" />
        <el-table-column prop="status" label="状态" width="100">
          <template #default="scope">
            <el-tag :type="getStatusType(scope.row.status)">
              {{ scope.row.status }}
            </el-tag>
          </template>
        </el-table-column>
        <el-table-column prop="progress" label="进度" width="120">
          <template #default="scope">
            <el-progress :percentage="scope.row.progress" :status="getProgressStatus(scope.row.progress)" />
          </template>
        </el-table-column>
        <el-table-column prop="createTime" label="创建时间" width="180" />
        <el-table-column prop="updateTime" label="更新时间" width="180" />
        <el-table-column label="操作" width="150" fixed="right">
          <template #default="scope">
            <el-button type="text" size="small" @click="viewDetail(scope.row)">查看</el-button>
            <el-button type="text" size="small" @click="editItem(scope.row)">编辑</el-button>
          </template>
        </el-table-column>
      </el-table>
      <div class="pagination-container">
        <el-pagination
          v-model:current-page="pagination.currentPage"
          v-model:page-size="pagination.pageSize"
          :page-sizes="[10, 20, 50, 100]"
          :total="pagination.total"
          layout="total, sizes, prev, pager, next, jumper"
          @size-change="handleSizeChange"
          @current-change="handleCurrentChange"
        />
      </div>
    </el-card>
  </div>
</template>
<script setup>
import { ref, reactive, onMounted, nextTick } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import * as echarts from 'echarts'
import { Box, Tools, Document, ShoppingCart } from '@element-plus/icons-vue'
// å“åº”式数据
const filterForm = reactive({
  dateRange: [],
  reportType: 'sample'
})
const statistics = reactive({
  totalSamples: 1250,
  activeEquipment: 45,
  completedInspections: 890,
  totalUsage: 2340
})
const tableData = ref([])
const tableLoading = ref(false)
const pagination = reactive({
  currentPage: 1,
  pageSize: 20,
  total: 0
})
// å›¾è¡¨å¼•用
const sampleChartRef = ref(null)
const equipmentChartRef = ref(null)
const inspectionChartRef = ref(null)
const usageChartRef = ref(null)
// å›¾è¡¨å®žä¾‹
let sampleChart = null
let equipmentChart = null
let inspectionChart = null
let usageChart = null
// åˆå§‹åŒ–数据
const initData = () => {
  // æ¨¡æ‹Ÿè¡¨æ ¼æ•°æ®
  tableData.value = [
    {
      id: 'SP001',
      name: '样品A-001',
      type: '金属材料',
      status: '检测中',
      progress: 75,
      createTime: '2024-01-15 09:30:00',
      updateTime: '2024-01-20 14:20:00'
    },
    {
      id: 'SP002',
      name: '样品B-002',
      type: '塑料制品',
      status: '已完成',
      progress: 100,
      createTime: '2024-01-10 10:15:00',
      updateTime: '2024-01-18 16:45:00'
    },
    {
      id: 'SP003',
      name: '样品C-003',
      type: '电子元件',
      status: '待检测',
      progress: 0,
      createTime: '2024-01-22 08:45:00',
      updateTime: '2024-01-22 08:45:00'
    },
    {
      id: 'EQ001',
      name: '检测设备A',
      type: '光谱仪',
      status: '使用中',
      progress: 60,
      createTime: '2024-01-05 14:20:00',
      updateTime: '2024-01-20 11:30:00'
    },
    {
      id: 'EQ002',
      name: '检测设备B',
      type: '显微镜',
      status: '空闲',
      progress: 0,
      createTime: '2024-01-08 16:10:00',
      updateTime: '2024-01-19 09:15:00'
    }
  ]
  pagination.total = tableData.value.length
}
// åˆå§‹åŒ–样品进度图表
const initSampleChart = () => {
  if (sampleChartRef.value) {
    sampleChart = echarts.init(sampleChartRef.value)
    const option = {
      title: {
        text: '样品进度分布',
        left: 'center'
      },
      tooltip: {
        trigger: 'item',
        formatter: '{a} <br/>{b}: {c} ({d}%)'
      },
      legend: {
        orient: 'vertical',
        left: 'left'
      },
      series: [
        {
          name: '样品状态',
          type: 'pie',
          radius: ['40%', '70%'],
          avoidLabelOverlap: false,
          label: {
            show: false,
            position: 'center'
          },
          emphasis: {
            label: {
              show: true,
              fontSize: '18',
              fontWeight: 'bold'
            }
          },
          labelLine: {
            show: false
          },
          data: [
            { value: 450, name: '已完成' },
            { value: 320, name: '检测中' },
            { value: 280, name: '待检测' },
            { value: 200, name: '已暂停' }
          ]
        }
      ]
    }
    sampleChart.setOption(option)
  }
}
// åˆå§‹åŒ–设备使用图表
const initEquipmentChart = () => {
  if (equipmentChartRef.value) {
    equipmentChart = echarts.init(equipmentChartRef.value)
    const option = {
      title: {
        text: '设备使用率',
        left: 'center'
      },
      tooltip: {
        trigger: 'axis',
        axisPointer: {
          type: 'shadow'
        }
      },
      xAxis: {
        type: 'category',
        data: ['光谱仪', '显微镜', '硬度计', '拉力机', '冲击机', '金相仪']
      },
      yAxis: {
        type: 'value',
        name: '使用率(%)'
      },
      series: [
        {
          name: '使用率',
          type: 'bar',
          data: [85, 60, 75, 90, 45, 70],
          label: {
            show: true,
            position: 'inside',
            align: 'center',
            verticalAlign: 'middle',
            formatter: '{c}%',
            color: '#fff'
          },
          itemStyle: {
            color: function(params) {
              const colors = ['#409EFF', '#67C23A', '#E6A23C', '#F56C6C', '#909399', '#9C27B0']
              return colors[params.dataIndex]
            }
          }
        }
      ]
    }
    equipmentChart.setOption(option)
  }
}
// åˆå§‹åŒ–检测项目图表
const initInspectionChart = () => {
  if (inspectionChartRef.value) {
    inspectionChart = echarts.init(inspectionChartRef.value)
    const option = {
      title: {
        text: '检测项目分布',
        left: 'center'
      },
      tooltip: {
        trigger: 'item'
      },
      legend: {
        orient: 'vertical',
        left: 'left'
      },
      series: [
        {
          name: '检测项目',
          type: 'pie',
          radius: '50%',
          data: [
            { value: 335, name: '物理性能' },
            { value: 310, name: '化学分析' },
            { value: 234, name: '尺寸测量' },
            { value: 135, name: '外观检查' },
            { value: 148, name: '其他检测' }
          ],
          emphasis: {
            itemStyle: {
              shadowBlur: 10,
              shadowOffsetX: 0,
              shadowColor: 'rgba(0, 0, 0, 0.5)'
            }
          }
        }
      ]
    }
    inspectionChart.setOption(option)
  }
}
// åˆå§‹åŒ–领用记录图表
const initUsageChart = () => {
  if (usageChartRef.value) {
    usageChart = echarts.init(usageChartRef.value)
    const option = {
      title: {
        text: '领用记录趋势',
        left: 'center'
      },
      tooltip: {
        trigger: 'axis'
      },
      legend: {
        data: ['领用次数', '归还次数']
      },
      xAxis: {
        type: 'category',
        boundaryGap: false,
        data: ['1月', '2月', '3月', '4月', '5月', '6月', '7月', '8月', '9月', '10月', '11月', '12月']
      },
      yAxis: {
        type: 'value'
      },
      series: [
        {
          name: '领用次数',
          type: 'line',
          stack: 'Total',
          data: [120, 132, 101, 134, 90, 230, 210, 182, 191, 234, 290, 330]
        },
        {
          name: '归还次数',
          type: 'line',
          stack: 'Total',
          data: [110, 125, 95, 128, 85, 220, 200, 175, 185, 225, 280, 320]
        }
      ]
    }
    usageChart.setOption(option)
  }
}
// äº‹ä»¶å¤„理函数
const handleFilterChange = () => {
  ElMessage.success('筛选条件已更新')
  // è¿™é‡Œå¯ä»¥æ ¹æ®ç­›é€‰æ¡ä»¶é‡æ–°åŠ è½½æ•°æ®
}
const resetFilter = () => {
  filterForm.dateRange = []
  filterForm.reportType = 'sample'
  ElMessage.info('筛选条件已重置')
}
const exportReport = () => {
  ElMessage.success('报表导出功能开发中...')
}
const refreshSampleChart = () => {
  initSampleChart()
  ElMessage.success('样品进度图表已刷新')
}
const refreshEquipmentChart = () => {
  initEquipmentChart()
  ElMessage.success('设备使用图表已刷新')
}
const refreshInspectionChart = () => {
  initInspectionChart()
  ElMessage.success('检测项目图表已刷新')
}
const refreshUsageChart = () => {
  initUsageChart()
  ElMessage.success('领用记录图表已刷新')
}
const refreshTable = () => {
  tableLoading.value = true
  setTimeout(() => {
    tableLoading.value = false
    ElMessage.success('表格数据已刷新')
  }, 1000)
}
const exportTable = () => {
  ElMessage.success('表格导出功能开发中...')
}
const handleSizeChange = (val) => {
  pagination.pageSize = val
  // é‡æ–°åŠ è½½æ•°æ®
}
const handleCurrentChange = (val) => {
  pagination.currentPage = val
  // é‡æ–°åŠ è½½æ•°æ®
}
const getStatusType = (status) => {
  const statusMap = {
    '已完成': 'success',
    '检测中': 'warning',
    '待检测': 'info',
    '已暂停': 'danger',
    '使用中': 'primary',
    '空闲': 'info'
  }
  return statusMap[status] || 'info'
}
const getProgressStatus = (progress) => {
  if (progress === 100) return 'success'
  if (progress >= 80) return 'warning'
  if (progress >= 50) return ''
  return 'exception'
}
const viewDetail = (row) => {
  ElMessage.info(`查看详情: ${row.name}`)
}
const editItem = (row) => {
  ElMessage.info(`编辑项目: ${row.name}`)
}
// ç”Ÿå‘½å‘¨æœŸ
onMounted(() => {
  initData()
  nextTick(() => {
    initSampleChart()
    initEquipmentChart()
    initInspectionChart()
    initUsageChart()
  })
  // ç›‘听窗口大小变化,重新调整图表大小
  window.addEventListener('resize', () => {
    sampleChart?.resize()
    equipmentChart?.resize()
    inspectionChart?.resize()
    usageChart?.resize()
  })
})
</script>
<style scoped>
.report-management {
  padding: 20px;
  background-color: #f5f5f5;
  min-height: 100vh;
}
.page-header {
  margin-bottom: 20px;
  text-align: center;
}
.page-header h2 {
  color: #303133;
  margin-bottom: 8px;
  font-size: 24px;
  font-weight: 600;
}
.page-header p {
  color: #909399;
  font-size: 14px;
  margin: 0;
}
.filter-card {
  margin-bottom: 20px;
}
.statistics-cards {
  margin-bottom: 20px;
}
.stat-card {
  height: 120px;
}
.stat-content {
  display: flex;
  align-items: center;
  height: 100%;
}
.stat-icon {
  width: 60px;
  height: 60px;
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  margin-right: 20px;
  font-size: 24px;
  color: white;
}
.stat-card:nth-child(1) .stat-icon {
  background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
}
.stat-card:nth-child(2) .stat-icon {
  background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);
}
.stat-card:nth-child(3) .stat-icon {
  background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%);
}
.stat-card:nth-child(4) .stat-icon {
  background: linear-gradient(135deg, #43e97b 0%, #38f9d7 100%);
}
.stat-info {
  flex: 1;
}
.stat-number {
  font-size: 28px;
  font-weight: bold;
  color: #303133;
  margin-bottom: 8px;
}
.stat-label {
  font-size: 14px;
  color: #909399;
}
.charts-container {
  margin-bottom: 20px;
}
.chart-card {
  margin-bottom: 20px;
}
.card-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
}
.chart-container {
  height: 300px;
  width: 100%;
}
.table-card {
  margin-bottom: 20px;
}
.pagination-container {
  margin-top: 20px;
  text-align: right;
}
:deep(.el-card__header) {
  padding: 15px 20px;
  border-bottom: 1px solid #ebeef5;
  background-color: #fafafa;
}
:deep(.el-card__body) {
  padding: 20px;
}
:deep(.el-table) {
  margin-bottom: 20px;
}
:deep(.el-progress) {
  margin: 0;
}
:deep(.el-tag) {
  margin: 0;
}
</style>
src/views/reportAnalysis/reportManagement/index.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,716 @@
<template>
    <div class="report-management">
        <!-- ç­›é€‰æ¡ä»¶ -->
        <el-card class="filter-card" shadow="never">
            <el-form :model="filterForm" inline>
                <el-form-item label="时间范围">
                    <el-date-picker
                        style="width: 300px"
                        v-model="filterForm.dateRange"
                        type="daterange"
                        range-separator="至"
                        start-placeholder="开始日期"
                        end-placeholder="结束日期"
                        format="YYYY-MM-DD"
                        value-format="YYYY-MM-DD"
                        @change="handleFilterChange"
                    />
                </el-form-item>
                <el-form-item label="报表类型">
                    <el-select v-model="filterForm.reportType" placeholder="请选择报表类型" @change="handleFilterChange" style="width: 300px">
                        <el-option label="样品进度报表" value="sample" />
                        <el-option label="设备使用报表" value="equipment" />
                        <el-option label="检测项目报表" value="inspection" />
                        <el-option label="领用记录报表" value="usage" />
                    </el-select>
                </el-form-item>
                <el-form-item>
                    <el-button type="primary" @click="handleFilterChange">查询</el-button>
                    <el-button @click="resetFilter">重置</el-button>
                    <el-button type="success" @click="exportReport">导出报表</el-button>
                </el-form-item>
            </el-form>
        </el-card>
        <!-- ç»Ÿè®¡å¡ç‰‡ -->
        <div class="statistics-cards">
            <el-row :gutter="20">
                <el-col :span="6">
                    <el-card class="stat-card" shadow="hover">
                        <div class="stat-content">
                            <div class="stat-icon">
                                <el-icon><Box /></el-icon>
                            </div>
                            <div class="stat-info">
                                <div class="stat-number">{{ statistics.totalSamples }}</div>
                                <div class="stat-label">总样品数</div>
                            </div>
                        </div>
                    </el-card>
                </el-col>
                <el-col :span="6">
                    <el-card class="stat-card" shadow="hover">
                        <div class="stat-content">
                            <div class="stat-icon">
                                <el-icon><Tools /></el-icon>
                            </div>
                            <div class="stat-info">
                                <div class="stat-number">{{ statistics.activeEquipment }}</div>
                                <div class="stat-label">在用设备</div>
                            </div>
                        </div>
                    </el-card>
                </el-col>
                <el-col :span="6">
                    <el-card class="stat-card" shadow="hover">
                        <div class="stat-content">
                            <div class="stat-icon">
                                <el-icon><Document /></el-icon>
                            </div>
                            <div class="stat-info">
                                <div class="stat-number">{{ statistics.completedInspections }}</div>
                                <div class="stat-label">已完成检测</div>
                            </div>
                        </div>
                    </el-card>
                </el-col>
                <el-col :span="6">
                    <el-card class="stat-card" shadow="hover">
                        <div class="stat-content">
                            <div class="stat-icon">
                                <el-icon><ShoppingCart /></el-icon>
                            </div>
                            <div class="stat-info">
                                <div class="stat-number">{{ statistics.totalUsage }}</div>
                                <div class="stat-label">总领用次数</div>
                            </div>
                        </div>
                    </el-card>
                </el-col>
            </el-row>
        </div>
        <!-- å›¾è¡¨åŒºåŸŸ -->
        <div class="charts-container">
            <el-row :gutter="20">
                <!-- æ ·å“è¿›åº¦å›¾è¡¨ -->
                <el-col :span="12">
                    <el-card class="chart-card" shadow="hover">
                        <template #header>
                            <div class="card-header">
                                <span>样品进度统计</span>
                                <el-button link @click="refreshSampleChart">刷新</el-button>
                            </div>
                        </template>
                        <div ref="sampleChartRef" class="chart-container"></div>
                    </el-card>
                </el-col>
                <!-- è®¾å¤‡ä½¿ç”¨å›¾è¡¨ -->
                <el-col :span="12">
                    <el-card class="chart-card" shadow="hover">
                        <template #header>
                            <div class="card-header">
                                <span>设备使用率统计</span>
                                <el-button link @click="refreshEquipmentChart">刷新</el-button>
                            </div>
                        </template>
                        <div ref="equipmentChartRef" class="chart-container"></div>
                    </el-card>
                </el-col>
            </el-row>
            <el-row :gutter="20" style="margin-top: 20px;">
                <!-- æ£€æµ‹é¡¹ç›®ç»Ÿè®¡ -->
                <el-col :span="12">
                    <el-card class="chart-card" shadow="hover">
                        <template #header>
                            <div class="card-header">
                                <span>检测项目分布</span>
                                <el-button link @click="refreshInspectionChart">刷新</el-button>
                            </div>
                        </template>
                        <div ref="inspectionChartRef" class="chart-container"></div>
                    </el-card>
                </el-col>
                <!-- é¢†ç”¨è®°å½•趋势 -->
                <el-col :span="12">
                    <el-card class="chart-card" shadow="hover">
                        <template #header>
                            <div class="card-header">
                                <span>领用记录趋势</span>
                                <el-button link @click="refreshUsageChart">刷新</el-button>
                            </div>
                        </template>
                        <div ref="usageChartRef" class="chart-container"></div>
                    </el-card>
                </el-col>
            </el-row>
        </div>
        <!-- è¯¦ç»†æ•°æ®è¡¨æ ¼ -->
        <el-card class="table-card" shadow="hover">
            <template #header>
                <div class="card-header">
                    <span>详细数据</span>
                    <div>
                        <el-button type="primary" size="small" @click="refreshTable">刷新</el-button>
                        <el-button type="success" size="small" @click="exportTable">导出</el-button>
                    </div>
                </div>
            </template>
            <el-table
                :data="tableData"
                style="width: 100%"
                v-loading="tableLoading"
                stripe
                border
            >
                <el-table-column prop="id" label="编号" width="80" />
                <el-table-column prop="name" label="名称" />
                <el-table-column prop="type" label="类型" width="120" />
                <el-table-column prop="status" label="状态" width="100">
                    <template #default="scope">
                        <el-tag :type="getStatusType(scope.row.status)">
                            {{ scope.row.status }}
                        </el-tag>
                    </template>
                </el-table-column>
                <el-table-column prop="progress" label="进度" width="120">
                    <template #default="scope">
                        <el-progress :percentage="scope.row.progress" :status="getProgressStatus(scope.row.progress)" />
                    </template>
                </el-table-column>
                <el-table-column prop="createTime" label="创建时间" width="180" />
                <el-table-column prop="updateTime" label="更新时间" width="180" />
                <el-table-column label="操作" width="150" fixed="right">
                    <template #default="scope">
                        <el-button link size="small" @click="viewDetail(scope.row)">查看</el-button>
                        <el-button link size="small" @click="editItem(scope.row)">编辑</el-button>
                    </template>
                </el-table-column>
            </el-table>
            <div class="pagination-container">
                <el-pagination
                    v-model:current-page="pagination.currentPage"
                    v-model:page-size="pagination.pageSize"
                    :page-sizes="[10, 20, 50, 100]"
                    :total="pagination.total"
                    layout="total, sizes, prev, pager, next, jumper"
                    @size-change="handleSizeChange"
                    @current-change="handleCurrentChange"
                />
            </div>
        </el-card>
    </div>
</template>
<script setup>
import { ref, reactive, onMounted, nextTick } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import * as echarts from 'echarts'
import { Box, Tools, Document, ShoppingCart } from '@element-plus/icons-vue'
// å“åº”式数据
const filterForm = reactive({
    dateRange: [],
    reportType: 'sample'
})
const statistics = reactive({
    totalSamples: 1250,
    activeEquipment: 45,
    completedInspections: 890,
    totalUsage: 2340
})
const tableData = ref([])
const tableLoading = ref(false)
const pagination = reactive({
    currentPage: 1,
    pageSize: 20,
    total: 0
})
// å›¾è¡¨å¼•用
const sampleChartRef = ref(null)
const equipmentChartRef = ref(null)
const inspectionChartRef = ref(null)
const usageChartRef = ref(null)
// å›¾è¡¨å®žä¾‹
let sampleChart = null
let equipmentChart = null
let inspectionChart = null
let usageChart = null
// åˆå§‹åŒ–数据
const initData = () => {
    // æ¨¡æ‹Ÿè¡¨æ ¼æ•°æ®
    tableData.value = [
        {
            id: 'SP001',
            name: '样品A-001',
            type: '金属材料',
            status: '检测中',
            progress: 75,
            createTime: '2024-01-15 09:30:00',
            updateTime: '2024-01-20 14:20:00'
        },
        {
            id: 'SP002',
            name: '样品B-002',
            type: '塑料制品',
            status: '已完成',
            progress: 100,
            createTime: '2024-01-10 10:15:00',
            updateTime: '2024-01-18 16:45:00'
        },
        {
            id: 'SP003',
            name: '样品C-003',
            type: '电子元件',
            status: '待检测',
            progress: 0,
            createTime: '2024-01-22 08:45:00',
            updateTime: '2024-01-22 08:45:00'
        },
        {
            id: 'EQ001',
            name: '检测设备A',
            type: '光谱仪',
            status: '使用中',
            progress: 60,
            createTime: '2024-01-05 14:20:00',
            updateTime: '2024-01-20 11:30:00'
        },
        {
            id: 'EQ002',
            name: '检测设备B',
            type: '显微镜',
            status: '空闲',
            progress: 0,
            createTime: '2024-01-08 16:10:00',
            updateTime: '2024-01-19 09:15:00'
        }
    ]
    pagination.total = tableData.value.length
}
// åˆå§‹åŒ–样品进度图表
const initSampleChart = () => {
    if (sampleChartRef.value) {
        sampleChart = echarts.init(sampleChartRef.value)
        const option = {
            title: {
                show: false
            },
            tooltip: {
                trigger: 'item',
                formatter: '{a} <br/>{b}: {c} ({d}%)'
            },
            legend: {
                orient: 'vertical',
                left: 'left'
            },
            series: [
                {
                    name: '样品状态',
                    type: 'pie',
                    radius: ['40%', '70%'],
                    avoidLabelOverlap: false,
                    label: {
                        show: false,
                        position: 'center'
                    },
                    emphasis: {
                        label: {
                            show: true,
                            fontSize: '18',
                            fontWeight: 'bold'
                        }
                    },
                    labelLine: {
                        show: false
                    },
                    data: [
                        { value: 450, name: '已完成' },
                        { value: 320, name: '检测中' },
                        { value: 280, name: '待检测' },
                        { value: 200, name: '已暂停' }
                    ]
                }
            ]
        }
        sampleChart.setOption(option)
    }
}
// åˆå§‹åŒ–设备使用图表
const initEquipmentChart = () => {
    if (equipmentChartRef.value) {
        equipmentChart = echarts.init(equipmentChartRef.value)
        const option = {
            title: {
                show: false
            },
            tooltip: {
                trigger: 'axis',
                axisPointer: {
                    type: 'shadow'
                }
            },
            xAxis: {
                type: 'category',
                data: ['光谱仪', '显微镜', '硬度计', '拉力机', '冲击机', '金相仪']
            },
            yAxis: {
                type: 'value',
                name: '使用率(%)'
            },
            series: [
                {
                    name: '使用率',
                    type: 'bar',
                    data: [85, 60, 75, 90, 45, 70],
                    itemStyle: {
                        color: function(params) {
                            const colors = ['#409EFF', '#67C23A', '#E6A23C', '#F56C6C', '#909399', '#9C27B0']
                            return colors[params.dataIndex]
                        }
                    }
                }
            ]
        }
        equipmentChart.setOption(option)
    }
}
// åˆå§‹åŒ–检测项目图表
const initInspectionChart = () => {
    if (inspectionChartRef.value) {
        inspectionChart = echarts.init(inspectionChartRef.value)
        const option = {
            title: {
                show: false
            },
            tooltip: {
                trigger: 'item'
            },
            legend: {
                orient: 'vertical',
                left: 'left'
            },
            series: [
                {
                    name: '检测项目',
                    type: 'pie',
                    radius: '50%',
                    data: [
                        { value: 335, name: '物理性能' },
                        { value: 310, name: '化学分析' },
                        { value: 234, name: '尺寸测量' },
                        { value: 135, name: '外观检查' },
                        { value: 148, name: '其他检测' }
                    ],
                    emphasis: {
                        itemStyle: {
                            shadowBlur: 10,
                            shadowOffsetX: 0,
                            shadowColor: 'rgba(0, 0, 0, 0.5)'
                        }
                    }
                }
            ]
        }
        inspectionChart.setOption(option)
    }
}
// åˆå§‹åŒ–领用记录图表
const initUsageChart = () => {
    if (usageChartRef.value) {
        usageChart = echarts.init(usageChartRef.value)
        const option = {
            title: {
                show: false
            },
            tooltip: {
                trigger: 'axis'
            },
            legend: {
                data: ['领用次数', '归还次数']
            },
            xAxis: {
                type: 'category',
                boundaryGap: false,
                data: ['1月', '2月', '3月', '4月', '5月', '6月', '7月', '8月', '9月', '10月', '11月', '12月']
            },
            yAxis: {
                type: 'value'
            },
            series: [
                {
                    name: '领用次数',
                    type: 'line',
                    stack: 'Total',
                    data: [120, 132, 101, 134, 90, 230, 210, 182, 191, 234, 290, 330]
                },
                {
                    name: '归还次数',
                    type: 'line',
                    stack: 'Total',
                    data: [110, 125, 95, 128, 85, 220, 200, 175, 185, 225, 280, 320]
                }
            ]
        }
        usageChart.setOption(option)
    }
}
// äº‹ä»¶å¤„理函数
const handleFilterChange = () => {
    ElMessage.success('筛选条件已更新')
    // è¿™é‡Œå¯ä»¥æ ¹æ®ç­›é€‰æ¡ä»¶é‡æ–°åŠ è½½æ•°æ®
}
const resetFilter = () => {
    filterForm.dateRange = []
    filterForm.reportType = 'sample'
    ElMessage.info('筛选条件已重置')
}
const exportReport = () => {
    ElMessage.success('报表导出功能开发中...')
}
const refreshSampleChart = () => {
    initSampleChart()
    ElMessage.success('样品进度图表已刷新')
}
const refreshEquipmentChart = () => {
    initEquipmentChart()
    ElMessage.success('设备使用图表已刷新')
}
const refreshInspectionChart = () => {
    initInspectionChart()
    ElMessage.success('检测项目图表已刷新')
}
const refreshUsageChart = () => {
    initUsageChart()
    ElMessage.success('领用记录图表已刷新')
}
const refreshTable = () => {
    tableLoading.value = true
    setTimeout(() => {
        tableLoading.value = false
        ElMessage.success('表格数据已刷新')
    }, 1000)
}
const exportTable = () => {
    ElMessage.success('表格导出功能开发中...')
}
const handleSizeChange = (val) => {
    pagination.pageSize = val
    // é‡æ–°åŠ è½½æ•°æ®
}
const handleCurrentChange = (val) => {
    pagination.currentPage = val
    // é‡æ–°åŠ è½½æ•°æ®
}
const getStatusType = (status) => {
    const statusMap = {
        '已完成': 'success',
        '检测中': 'warning',
        '待检测': 'info',
        '已暂停': 'danger',
        '使用中': 'primary',
        '空闲': 'info'
    }
    return statusMap[status] || 'info'
}
const getProgressStatus = (progress) => {
    if (progress === 100) return 'success'
    if (progress >= 80) return 'warning'
    if (progress >= 50) return ''
    return 'exception'
}
const viewDetail = (row) => {
    ElMessage.info(`查看详情: ${row.name}`)
}
const editItem = (row) => {
    ElMessage.info(`编辑项目: ${row.name}`)
}
// ç”Ÿå‘½å‘¨æœŸ
onMounted(() => {
    initData()
    nextTick(() => {
        initSampleChart()
        initEquipmentChart()
        initInspectionChart()
        initUsageChart()
    })
    // ç›‘听窗口大小变化,重新调整图表大小
    window.addEventListener('resize', () => {
        sampleChart?.resize()
        equipmentChart?.resize()
        inspectionChart?.resize()
        usageChart?.resize()
    })
})
</script>
<style scoped>
.report-management {
    padding: 20px;
    background-color: #f5f5f5;
    min-height: 100vh;
}
.page-header {
    margin-bottom: 20px;
    text-align: center;
}
.page-header h2 {
    color: #303133;
    margin-bottom: 8px;
    font-size: 24px;
    font-weight: 600;
}
.page-header p {
    color: #909399;
    font-size: 14px;
    margin: 0;
}
.filter-card {
    margin-bottom: 20px;
}
.statistics-cards {
    margin-bottom: 20px;
}
.stat-card {
    height: 120px;
}
.stat-content {
    display: flex;
    align-items: center;
    height: 100%;
}
.stat-icon {
    width: 60px;
    height: 60px;
    border-radius: 50%;
    display: flex;
    align-items: center;
    justify-content: center;
    margin-right: 20px;
    font-size: 24px;
    color: white;
}
.stat-card:nth-child(1) .stat-icon {
    background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
}
.stat-card:nth-child(2) .stat-icon {
    background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);
}
.stat-card:nth-child(3) .stat-icon {
    background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%);
}
.stat-card:nth-child(4) .stat-icon {
    background: linear-gradient(135deg, #43e97b 0%, #38f9d7 100%);
}
.stat-info {
    flex: 1;
}
.stat-number {
    font-size: 28px;
    font-weight: bold;
    color: #303133;
    margin-bottom: 8px;
}
.stat-label {
    font-size: 14px;
    color: #909399;
}
.charts-container {
    margin-bottom: 20px;
}
.chart-card {
    margin-bottom: 20px;
}
.card-header {
    display: flex;
    justify-content: space-between;
    align-items: center;
}
.chart-container {
    height: 300px;
    width: 100%;
}
.table-card {
    margin-bottom: 20px;
}
.pagination-container {
    margin-top: 20px;
    text-align: right;
}
:deep(.el-card__header) {
    padding: 15px 20px;
    border-bottom: 1px solid #ebeef5;
    background-color: #fafafa;
}
:deep(.el-card__body) {
    padding: 20px;
}
:deep(.el-table) {
    margin-bottom: 20px;
}
:deep(.el-progress) {
    margin: 0;
}
:deep(.el-tag) {
    margin: 0;
}
</style>