WXL
2025-12-30 ed62678cd16042506bad5e5f75665a822f2d5717
src/views/OfficeRelated/checkingIn/components/PersonalAttendanceReport.vue
@@ -12,6 +12,7 @@
          size="small"
          @click="exportReport"
          icon="el-icon-download"
          type="primary"
        >
          导出报表
        </el-button>
@@ -20,7 +21,7 @@
    <!-- 统计概览 -->
    <el-row :gutter="20" class="stats-overview">
      <el-col :span="6">
      <el-col :xs="12" :sm="6" class="stat-col">
        <el-card shadow="hover" class="stat-card">
          <div class="stat-content">
            <div class="stat-icon attendance-icon">
@@ -33,7 +34,7 @@
          </div>
        </el-card>
      </el-col>
      <el-col :span="6">
      <el-col :xs="12" :sm="6" class="stat-col">
        <el-card shadow="hover" class="stat-card">
          <div class="stat-content">
            <div class="stat-icon present-icon">
@@ -46,7 +47,7 @@
          </div>
        </el-card>
      </el-col>
      <el-col :span="6">
      <el-col :xs="12" :sm="6" class="stat-col">
        <el-card shadow="hover" class="stat-card">
          <div class="stat-content">
            <div class="stat-icon abnormal-icon">
@@ -59,7 +60,7 @@
          </div>
        </el-card>
      </el-col>
      <el-col :span="6">
      <el-col :xs="12" :sm="6" class="stat-col">
        <el-card shadow="hover" class="stat-card">
          <div class="stat-content">
            <div class="stat-icon rate-icon">
@@ -74,98 +75,150 @@
      </el-col>
    </el-row>
    <!-- 图表区域 -->
    <!-- 图表区域 - 优化布局 -->
    <el-row :gutter="20" class="charts-section">
      <el-col :span="12">
        <el-card header="出勤趋势" shadow="never">
      <el-col :xs="24" :lg="12" class="chart-col">
        <el-card class="chart-card" shadow="never">
          <template #header>
            <div class="chart-header">
              <span class="chart-title">出勤趋势分析</span>
              <div class="chart-legend">
                <span class="legend-item">
                  <span class="legend-color bar-color"></span>
                  出勤天数
                </span>
                <span class="legend-item">
                  <span class="legend-color line-color"></span>
                  出勤率
                </span>
              </div>
            </div>
          </template>
          <div id="attendanceTrendChart" class="chart-container"></div>
        </el-card>
      </el-col>
      <el-col :span="12">
        <el-card header="考勤分布" shadow="never">
      <el-col :xs="24" :lg="12" class="chart-col">
        <el-card class="chart-card" shadow="never">
          <template #header>
            <div class="chart-header">
              <span class="chart-title">考勤分布</span>
            </div>
          </template>
          <div id="attendanceDistributionChart" class="chart-container"></div>
        </el-card>
      </el-col>
    </el-row>
    <!-- 详细统计表格 -->
    <el-card header="详细统计" class="detail-table-card" shadow="never">
      <el-table :data="detailedStats" border style="width: 100%">
        <el-table-column prop="month" label="月份"  />
        <el-table-column prop="workDays" label="应出勤天数"  />
        <el-table-column prop="actualDays" label="实际出勤"  />
        <el-table-column prop="lateTimes" label="迟到次数" >
          <template #default="scope">
            <el-tag v-if="scope.row.lateTimes > 0" type="warning" size="small">
              {{ scope.row.lateTimes }}
            </el-tag>
            <span v-else>{{ scope.row.lateTimes }}</span>
          </template>
        </el-table-column>
        <el-table-column prop="leaveEarlyTimes" label="早退次数" >
          <template #default="scope">
            <el-tag v-if="scope.row.leaveEarlyTimes > 0" type="warning" size="small">
              {{ scope.row.leaveEarlyTimes }}
            </el-tag>
            <span v-else>{{ scope.row.leaveEarlyTimes }}</span>
          </template>
        </el-table-column>
        <el-table-column prop="absenceDays" label="缺勤天数" >
          <template #default="scope">
            <el-tag v-if="scope.row.absenceDays > 0" type="danger" size="small">
              {{ scope.row.absenceDays }}
            </el-tag>
            <span v-else>{{ scope.row.absenceDays }}</span>
          </template>
        </el-table-column>
        <el-table-column prop="businessTripDays" label="出差天数" >
          <template #default="scope">
            <el-tag v-if="scope.row.businessTripDays > 0" type="primary" size="small">
              {{ scope.row.businessTripDays }}
            </el-tag>
            <span v-else>{{ scope.row.businessTripDays }}</span>
          </template>
        </el-table-column>
        <el-table-column prop="attendanceRate" label="出勤率" >
          <template #default="scope">
            <el-progress
              :percentage="scope.row.attendanceRate"
              :show-text="false"
              :color="getProgressColor(scope.row.attendanceRate)"
            />
            <span>{{ scope.row.attendanceRate }}%</span>
          </template>
        </el-table-column>
      </el-table>
    <el-card class="detail-card" shadow="never">
      <template #header>
        <div class="card-header">
          <span class="card-title">月度详细统计</span>
          <el-button size="mini" type="text" @click="toggleTableExpand">
            {{ tableExpanded ? '收起' : '展开' }}
            <i :class="tableExpanded ? 'el-icon-arrow-up' : 'el-icon-arrow-down'"></i>
          </el-button>
        </div>
      </template>
      <el-collapse-transition>
        <div v-show="tableExpanded">
          <el-table
            :data="detailedStats"
            border
            style="width: 100%"
            class="stats-table"
            :row-class-name="getRowClassName"
          >
            <!-- 表格列定义保持不变 -->
            <el-table-column prop="month" label="月份" align="center" />
            <el-table-column prop="workDays" label="应出勤天数" align="center" />
            <el-table-column prop="actualDays" label="实际出勤" align="center" />
            <el-table-column prop="lateTimes" label="迟到次数" align="center">
              <template #default="scope">
                <el-tag v-if="scope.row.lateTimes > 0" type="warning" size="small">
                  {{ scope.row.lateTimes }}
                </el-tag>
                <span v-else>{{ scope.row.lateTimes }}</span>
              </template>
            </el-table-column>
            <el-table-column prop="leaveEarlyTimes" label="早退次数" align="center">
              <template #default="scope">
                <el-tag v-if="scope.row.leaveEarlyTimes > 0" type="warning" size="small">
                  {{ scope.row.leaveEarlyTimes }}
                </el-tag>
                <span v-else>{{ scope.row.leaveEarlyTimes }}</span>
              </template>
            </el-table-column>
            <el-table-column prop="absenceDays" label="缺勤天数" align="center">
              <template #default="scope">
                <el-tag v-if="scope.row.absenceDays > 0" type="danger" size="small">
                  {{ scope.row.absenceDays }}
                </el-tag>
                <span v-else>{{ scope.row.absenceDays }}</span>
              </template>
            </el-table-column>
            <el-table-column prop="businessTripDays" label="出差天数" align="center">
              <template #default="scope">
                <el-tag v-if="scope.row.businessTripDays > 0" type="primary" size="small">
                  {{ scope.row.businessTripDays }}
                </el-tag>
                <span v-else>{{ scope.row.businessTripDays }}</span>
              </template>
            </el-table-column>
            <el-table-column prop="attendanceRate" label="出勤率" align="center" min-width="100">
              <template #default="scope">
                <div class="progress-cell">
                  <el-progress
                    :percentage="scope.row.attendanceRate"
                    :show-text="false"
                    :color="getProgressColor(scope.row.attendanceRate)"
                    class="rate-progress"
                  />
                  <span class="rate-text" :class="getRateTextClass(scope.row.attendanceRate)">
                    {{ scope.row.attendanceRate }}%
                  </span>
                </div>
              </template>
            </el-table-column>
          </el-table>
        </div>
      </el-collapse-transition>
    </el-card>
    <!-- 异常记录 -->
    <el-card header="异常记录" class="abnormal-records-card" shadow="never">
      <el-table :data="abnormalRecords" border style="width: 100%">
        <el-table-column prop="date" label="日期"  />
        <el-table-column prop="type" label="异常类型" >
    <el-card class="abnormal-card" shadow="never">
      <template #header>
        <div class="card-header">
          <span class="card-title">异常记录明细</span>
        </div>
      </template>
      <el-table :data="abnormalRecords" border class="abnormal-table">
        <!-- 异常记录表格列定义保持不变 -->
        <el-table-column prop="date" label="日期" align="center" />
        <el-table-column prop="type" label="异常类型" align="center">
          <template #default="scope">
            <el-tag :type="getAbnormalType(scope.row.type)" size="small">
            <el-tag :type="getAbnormalType(scope.row.type)" size="small" effect="light">
              {{ scope.row.type }}
            </el-tag>
          </template>
        </el-table-column>
        <el-table-column prop="description" label="描述" min-width="200" />
        <el-table-column prop="duration" label="时长"  />
        <el-table-column prop="status" label="状态" >
        <el-table-column prop="description" label="描述" align="center" min-width="200" />
        <el-table-column prop="duration" label="时长" align="center" />
        <el-table-column prop="status" label="状态" align="center">
          <template #default="scope">
            <el-tag
              :type="scope.row.status === '已处理' ? 'success' : 'warning'"
              size="small"
              effect="light"
            >
              {{ scope.row.status }}
            </el-tag>
          </template>
        </el-table-column>
        <el-table-column label="操作" >
        <el-table-column label="操作" align="center" width="80">
          <template #default="scope">
            <el-button type="text" size="mini" @click="viewAbnormalDetail(scope.row)">
              查看
              详情
            </el-button>
          </template>
        </el-table-column>
@@ -201,7 +254,8 @@
      detailedStats: [],
      abnormalRecords: [],
      trendChart: null,
      distributionChart: null
      distributionChart: null,
      tableExpanded: true
    }
  },
  mounted() {
@@ -211,7 +265,7 @@
    })
  },
  beforeDestroy() {
    // 销毁图表实例
    // 销毁图表实例[3](@ref)
    if (this.trendChart) {
      this.trendChart.dispose()
    }
@@ -221,7 +275,7 @@
  },
  methods: {
    initData() {
      // 初始化概览数据
      // 初始化数据逻辑保持不变
      this.overview = {
        totalDays: this.stats.totalDays || 22,
        presentDays: this.stats.presentDays || 20,
@@ -229,7 +283,6 @@
        attendanceRate: this.stats.attendanceRate || 90.9
      }
      // 初始化详细统计
      this.detailedStats = [
        { month: '2024-12', workDays: 22, actualDays: 20, lateTimes: 2,
          leaveEarlyTimes: 1, absenceDays: 0, businessTripDays: 3, attendanceRate: 90.9 },
@@ -239,7 +292,6 @@
          leaveEarlyTimes: 0, absenceDays: 0, businessTripDays: 1, attendanceRate: 95.7 }
      ]
      // 初始化异常记录
      this.abnormalRecords = [
        { date: '2024-12-15', type: '迟到', description: '早上迟到30分钟', duration: '30分钟', status: '已处理' },
        { date: '2024-12-08', type: '早退', description: '下午提前1小时离开', duration: '1小时', status: '已处理' },
@@ -261,43 +313,89 @@
      const option = {
        tooltip: {
          trigger: 'axis',
          backgroundColor: 'rgba(255, 255, 255, 0.95)',
          borderColor: '#ebeef5',
          borderWidth: 1,
          textStyle: {
            color: '#606266'
          },
          formatter: function(params) {
            let result = params[0].axisValue + '<br/>'
            let result = `<div style="font-weight: 600; margin-bottom: 8px;">${params[0].axisValue}</div>`
            params.forEach(param => {
              result += `${param.seriesName}: ${param.value}<br/>`
              const icon = param.seriesType === 'bar' ? '●' : '◆'
              result += `<div>${icon} ${param.seriesName}: <span style="font-weight: 600; color: ${param.color}">${param.value}${param.seriesName === '出勤率' ? '%' : ''}</span></div>`
            })
            return result
          }
        },
        legend: {
          data: ['出勤天数', '异常天数', '出勤率'],
          bottom: 10
          bottom: 10,
          textStyle: {
            color: '#606266'
          },
          itemWidth: 12,
          itemHeight: 12
        },
        grid: {
          left: '3%',
          right: '4%',
          right: '3%',
          bottom: '15%',
          top: '10%',
          top: '15%',
          containLabel: true
        },
        xAxis: {
          type: 'category',
          data: ['10月', '11月', '12月']
          data: ['10月', '11月', '12月'],
          axisLine: {
            lineStyle: {
              color: '#dcdfe6'
            }
          },
          axisLabel: {
            color: '#606266'
          }
        },
        yAxis: [
          {
            type: 'value',
            name: '天数',
            min: 0,
            max: 30
            max: 30,
            axisLine: {
              show: true,
              lineStyle: {
                color: '#dcdfe6'
              }
            },
            axisLabel: {
              color: '#606266',
              formatter: '{value}'
            },
            splitLine: {
              lineStyle: {
                color: '#f0f2f5',
                type: 'dashed'
              }
            }
          },
          {
            type: 'value',
            name: '出勤率(%)',
            min: 0,
            max: 100,
            axisLine: {
              show: true,
              lineStyle: {
                color: '#dcdfe6'
              }
            },
            axisLabel: {
              color: '#606266',
              formatter: '{value}%'
            },
            splitLine: {
              show: false
            }
          }
        ],
@@ -305,19 +403,39 @@
          {
            name: '出勤天数',
            type: 'bar',
            barWidth: '30%',
            barWidth: '25%',
            data: [22, 19, 20],
            itemStyle: {
              color: '#409EFF'
              color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
                { offset: 0, color: '#409EFF' },
                { offset: 1, color: '#66b1ff' }
              ]),
              borderRadius: [2, 2, 0, 0]
            },
            emphasis: {
              itemStyle: {
                shadowBlur: 10,
                shadowColor: 'rgba(64, 158, 255, 0.5)'
              }
            }
          },
          {
            name: '异常天数',
            type: 'bar',
            barWidth: '30%',
            barWidth: '25%',
            data: [1, 2, 2],
            itemStyle: {
              color: '#F56C6C'
              color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
                { offset: 0, color: '#F56C6C' },
                { offset: 1, color: '#f78989' }
              ]),
              borderRadius: [2, 2, 0, 0]
            },
            emphasis: {
              itemStyle: {
                shadowBlur: 10,
                shadowColor: 'rgba(245, 108, 108, 0.5)'
              }
            }
          },
          {
@@ -325,14 +443,28 @@
            type: 'line',
            yAxisIndex: 1,
            data: [95.7, 90.5, 90.9],
            symbol: 'circle',
            symbolSize: 8,
            itemStyle: {
              color: '#67C23A'
              color: '#67C23A',
              borderColor: '#fff',
              borderWidth: 2
            },
            lineStyle: {
              width: 3
            }
              width: 3,
              shadowColor: 'rgba(103, 194, 58, 0.3)',
              shadowBlur: 8,
              shadowOffsetY: 2
            },
            emphasis: {
              scale: true
            },
            animationEasing: 'cubicInOut',
            animationDuration: 2000
          }
        ]
        ],
        animation: true,
        animationDuration: 1500
      }
      this.trendChart.setOption(option)
    },
@@ -345,13 +477,27 @@
      const option = {
        tooltip: {
          trigger: 'item',
          formatter: '{a} <br/>{b}: {c} ({d}%)'
          backgroundColor: 'rgba(255, 255, 255, 0.95)',
          borderColor: '#ebeef5',
          borderWidth: 1,
          textStyle: {
            color: '#606266'
          },
          formatter: '{a} <br/>{b}: {c}天 ({d}%)'
        },
        legend: {
          orient: 'vertical',
          right: 10,
          top: 'center',
          data: ['正常出勤', '迟到', '早退', '缺勤', '出差']
          textStyle: {
            color: '#606266',
            fontSize: 12
          },
          itemWidth: 12,
          itemHeight: 12,
          formatter: function(name) {
            return `${name}`
          }
        },
        series: [
          {
@@ -359,24 +505,38 @@
            type: 'pie',
            radius: ['40%', '70%'],
            center: ['40%', '50%'],
            avoidLabelOverlap: false,
            avoidLabelOverlap: true,
            itemStyle: {
              borderColor: '#fff',
              borderWidth: 2
              borderWidth: 2,
              borderRadius: 3,
              shadowBlur: 5,
              shadowColor: 'rgba(0, 0, 0, 0.1)'
            },
            label: {
              show: false,
              position: 'center'
              show: true,
              formatter: '{b}\n{d}%',
              textStyle: {
                fontSize: 12,
                fontWeight: 'normal'
              }
            },
            emphasis: {
              label: {
                show: true,
                fontSize: 18,
                fontSize: 14,
                fontWeight: 'bold'
              },
              itemStyle: {
                shadowBlur: 10,
                shadowOffsetX: 0,
                shadowColor: 'rgba(0, 0, 0, 0.2)'
              }
            },
            labelLine: {
              show: false
              length: 15,
              length2: 10,
              smooth: true
            },
            data: [
              { value: 20, name: '正常出勤', itemStyle: { color: '#67C23A' } },
@@ -384,23 +544,30 @@
              { value: 1, name: '早退', itemStyle: { color: '#F56C6C' } },
              { value: 0, name: '缺勤', itemStyle: { color: '#909399' } },
              { value: 3, name: '出差', itemStyle: { color: '#409EFF' } }
            ]
            ],
            animationType: 'scale',
            animationEasing: 'elasticOut',
            animationDelay: function(idx) {
              return Math.random() * 200
            }
          }
        ]
        ],
        animation: true,
        animationDuration: 1000
      }
      this.distributionChart.setOption(option)
    },
    setupChartResize() {
      // 监听窗口变化,重新渲染图表
      const handleResize = () => {
      // 使用节流函数优化性能[3](@ref)
      const handleResize = this.throttle(() => {
        if (this.trendChart) {
          this.trendChart.resize()
        }
        if (this.distributionChart) {
          this.distributionChart.resize()
        }
      }
      }, 300)
      window.addEventListener('resize', handleResize)
      this.$once('hook:beforeDestroy', () => {
@@ -408,13 +575,42 @@
      })
    },
    throttle(func, wait) {
      let timeout = null
      return function() {
        const context = this
        const args = arguments
        if (!timeout) {
          timeout = setTimeout(() => {
            timeout = null
            func.apply(context, args)
          }, wait)
        }
      }
    },
    toggleTableExpand() {
      this.tableExpanded = !this.tableExpanded
    },
    getRowClassName({ rowIndex }) {
      return rowIndex % 2 === 1 ? 'even-row' : 'odd-row'
    },
    getRateTextClass(rate) {
      if (rate >= 95) return 'rate-excellent'
      if (rate >= 90) return 'rate-good'
      if (rate >= 80) return 'rate-average'
      return 'rate-poor'
    },
    // 其他方法保持不变
    handlePeriodChange(period) {
      this.reportPeriod = period
      this.updateChartData()
    },
    updateChartData() {
      // 根据选择的周期更新图表数据
      let data
      switch (this.reportPeriod) {
        case 'month':
@@ -429,12 +625,10 @@
        default:
          data = this.getMonthlyData()
      }
      this.updateCharts(data)
    },
    getMonthlyData() {
      // 模拟月度数据
      return {
        xAxis: ['10月', '11月', '12月'],
        attendance: [22, 19, 20],
@@ -444,7 +638,6 @@
    },
    getQuarterlyData() {
      // 模拟季度数据
      return {
        xAxis: ['Q1', 'Q2', 'Q3', 'Q4'],
        attendance: [65, 62, 58, 61],
@@ -454,9 +647,8 @@
    },
    getYearlyData() {
      // 模拟年度数据
      return {
        xAxis: ['2022', '2023', '2024'],
        xAxis: ['2022', '2025', '2024'],
        attendance: [240, 248, 252],
        abnormal: [25, 18, 15],
        rate: [90.2, 92.5, 94.1]
@@ -470,7 +662,7 @@
        option.series[0].data = data.attendance
        option.series[1].data = data.abnormal
        option.series[2].data = data.rate
        this.trendChart.setOption(option)
        this.trendChart.setOption(option, { notMerge: false })
      }
    },
@@ -493,12 +685,10 @@
    viewAbnormalDetail(record) {
      this.$message.info(`查看异常记录: ${record.date} - ${record.type}`)
      // 这里可以打开详情对话框
    },
    exportReport() {
      this.$message.success('报表导出功能开发中')
      // 这里可以实现导出PDF或Excel功能
    }
  }
}
@@ -506,9 +696,9 @@
<style scoped>
.personal-attendance-report {
  padding: 20px;
  background: #fff;
  border-radius: 8px;
  padding: 24px;
  background: #f8f9fa;
  min-height: 100vh;
}
.report-header {
@@ -516,15 +706,17 @@
  justify-content: space-between;
  align-items: center;
  margin-bottom: 24px;
  padding-bottom: 16px;
  border-bottom: 1px solid #ebeef5;
  padding: 0;
}
.report-header h4 {
  margin: 0;
  color: #303133;
  font-size: 20px;
  font-size: 24px;
  font-weight: 600;
  background: linear-gradient(135deg, #409EFF, #67C23A);
  -webkit-background-clip: text;
  -webkit-text-fill-color: transparent;
}
.header-actions {
@@ -534,51 +726,78 @@
}
.stats-overview {
  margin-bottom: 24px;
  margin-bottom: 32px;
}
.stat-col {
  margin-bottom: 20px;
}
.stat-card {
  border-radius: 8px;
  transition: all 0.3s ease;
  border-radius: 12px;
  border: none;
  transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
  background: #fff;
  position: relative;
  overflow: hidden;
}
.stat-card::before {
  content: '';
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  height: 3px;
  background: linear-gradient(90deg, var(--gradient-start), var(--gradient-end));
}
.stat-card:hover {
  transform: translateY(-2px);
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
  transform: translateY(-4px);
  box-shadow: 0 12px 32px rgba(0, 0, 0, 0.15);
}
.stat-content {
  display: flex;
  align-items: center;
  padding: 16px;
  padding: 20px;
}
.stat-icon {
  width: 60px;
  height: 60px;
  border-radius: 50%;
  width: 64px;
  height: 64px;
  border-radius: 16px;
  display: flex;
  align-items: center;
  justify-content: center;
  margin-right: 16px;
  font-size: 24px;
  font-size: 28px;
  color: white;
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
}
.attendance-icon {
  background: linear-gradient(135deg, #409EFF, #79BBFF);
  --gradient-start: #409EFF;
  --gradient-end: #66b1ff;
  background: linear-gradient(135deg, var(--gradient-start), var(--gradient-end));
}
.present-icon {
  background: linear-gradient(135deg, #67C23A, #95D475);
  --gradient-start: #67C23A;
  --gradient-end: #85ce61;
  background: linear-gradient(135deg, var(--gradient-start), var(--gradient-end));
}
.abnormal-icon {
  background: linear-gradient(135deg, #E6A23C, #EEBD6D);
  --gradient-start: #E6A23C;
  --gradient-end: #ebb563;
  background: linear-gradient(135deg, var(--gradient-start), var(--gradient-end));
}
.rate-icon {
  background: linear-gradient(135deg, #F56C6C, #F89898);
  --gradient-start: #F56C6C;
  --gradient-end: #f78989;
  background: linear-gradient(135deg, var(--gradient-start), var(--gradient-end));
}
.stat-info {
@@ -586,47 +805,162 @@
}
.stat-value {
  font-size: 28px;
  font-weight: bold;
  font-size: 32px;
  font-weight: 700;
  color: #303133;
  margin-bottom: 4px;
  line-height: 1;
}
.stat-label {
  color: #909399;
  font-size: 14px;
  font-weight: 500;
}
.charts-section {
  margin-bottom: 32px;
}
.chart-col {
  margin-bottom: 24px;
}
.chart-container {
  width: 100%;
  height: 300px;
.chart-card {
  border-radius: 12px;
  border: none;
  background: #fff;
  box-shadow: 0 2px 12px rgba(0, 0, 0, 0.08);
}
.detail-table-card,
.abnormal-records-card {
  margin-bottom: 20px;
.chart-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 0;
}
.chart-title {
  font-size: 16px;
  font-weight: 600;
  color: #303133;
}
.chart-legend {
  display: flex;
  gap: 16px;
  align-items: center;
}
.legend-item {
  display: flex;
  align-items: center;
  gap: 6px;
  font-size: 12px;
  color: #606266;
}
.legend-color {
  width: 12px;
  height: 12px;
  border-radius: 2px;
}
.bar-color {
  background: linear-gradient(135deg, #409EFF, #66b1ff);
}
.line-color {
  background: linear-gradient(135deg, #67C23A, #85ce61);
}
.chart-container {
  width: 40vw;
  height: 320px;
  position: relative;
}
.detail-card,
.abnormal-card {
  border-radius: 12px;
  border: none;
  background: #fff;
  box-shadow: 0 2px 12px rgba(0, 0, 0, 0.08);
  margin-bottom: 24px;
}
.card-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 0;
}
.card-title {
  font-size: 16px;
  font-weight: 600;
  color: #303133;
}
.stats-table {
  border-radius: 8px;
  overflow: hidden;
}
.stats-table :deep(.el-table__row) {
  transition: background-color 0.3s;
}
.stats-table :deep(.el-table__row:hover) {
  background-color: #f5f7fa;
}
.stats-table :deep(.even-row) {
  background-color: #fafbfc;
}
.progress-cell {
  display: flex;
  align-items: center;
  gap: 8px;
}
.rate-progress {
  flex: 1;
}
.rate-text {
  font-size: 12px;
  font-weight: 600;
  min-width: 40px;
}
.rate-excellent { color: #67C23A; }
.rate-good { color: #E6A23C; }
.rate-average { color: #F56C6C; }
.rate-poor { color: #909399; }
.abnormal-table {
  border-radius: 8px;
  overflow: hidden;
}
/* 响应式设计 */
@media (max-width: 1200px) {
  .stats-overview .el-col {
    margin-bottom: 16px;
  .chart-container {
    height: 280px;
  }
}
@media (max-width: 768px) {
  .personal-attendance-report {
    padding: 12px;
    padding: 16px;
  }
  .report-header {
    flex-direction: column;
    align-items: flex-start;
    gap: 12px;
    gap: 16px;
  }
  .header-actions {
@@ -635,14 +969,42 @@
  }
  .stat-content {
    padding: 12px;
    padding: 16px;
    flex-direction: column;
    text-align: center;
  }
  .stat-icon {
    width: 50px;
    height: 50px;
    font-size: 20px;
    margin-right: 12px;
    margin-right: 0;
    margin-bottom: 12px;
    width: 56px;
    height: 56px;
    font-size: 24px;
  }
  .stat-value {
    font-size: 28px;
  }
  .chart-container {
    height: 240px;
  }
  .chart-header {
    flex-direction: column;
    gap: 12px;
    align-items: flex-start;
  }
  .chart-legend {
    align-self: stretch;
    justify-content: space-around;
  }
}
@media (max-width: 480px) {
  .stat-col {
    margin-bottom: 16px;
  }
  .stat-value {
@@ -650,7 +1012,7 @@
  }
  .chart-container {
    height: 250px;
    height: 200px;
  }
}
@@ -662,21 +1024,23 @@
  opacity: 0;
}
/* 表格样式优化 */
.el-table {
  border-radius: 4px;
  overflow: hidden;
/* 滚动条样式优化 */
:deep(::-webkit-scrollbar) {
  width: 6px;
  height: 6px;
}
.el-table::before {
  display: none;
:deep(::-webkit-scrollbar-track) {
  background: #f1f1f1;
  border-radius: 3px;
}
/* 卡片标题样式 */
.el-card__header {
  background: #f8f9fa;
  border-bottom: 1px solid #ebeef5;
  font-weight: 600;
  color: #303133;
:deep(::-webkit-scrollbar-thumb) {
  background: #c1c1c1;
  border-radius: 3px;
}
:deep(::-webkit-scrollbar-thumb:hover) {
  background: #a8a8a8;
}
</style>