994 lines
		
	
	
		
			26 KiB
		
	
	
	
		
			Vue
		
	
	
	
	
	
			
		
		
	
	
			994 lines
		
	
	
		
			26 KiB
		
	
	
	
		
			Vue
		
	
	
	
	
	
| <template>
 | ||
|   <view class="statistics-container">
 | ||
|     <!-- 移动设备顶部状态栏 -->
 | ||
|     <view class="top_bar flex w-full" v-if="isMobile">
 | ||
|       <view class="chat-header">
 | ||
|         <view class="header-left" @click="goBack">
 | ||
|           <i class="fas fa-arrow-left"></i>
 | ||
|         </view>
 | ||
|         <view class="header-title">考勤统计</view>
 | ||
|         <view class="header-right" @click="showMoreOptions">
 | ||
|           <i class="fas fa-ellipsis-v"></i>
 | ||
|         </view>
 | ||
|       </view>
 | ||
|     </view>
 | ||
|     
 | ||
|     <!-- 浏览器环境下的导航栏 -->
 | ||
|     <view class="chat-header" v-else>
 | ||
|       <view class="header-left" @click="goBack">
 | ||
|         <i class="fas fa-arrow-left"></i>
 | ||
|       </view>
 | ||
|       <view class="header-title">考勤统计</view>
 | ||
|       <view class="header-right" @click="showMoreOptions">
 | ||
|         <i class="fas fa-ellipsis-v"></i>
 | ||
|       </view>
 | ||
|     </view>
 | ||
| 
 | ||
|     <!-- 页面内容 -->
 | ||
|     <scroll-view scroll-y class="statistics-content">
 | ||
| 
 | ||
|     <!-- 导出报表 -->
 | ||
|     <view class="export-card card">
 | ||
|         <view class="export-title">导出报表</view>
 | ||
|         <view class="exports">
 | ||
|           <view class="btn-export" @click="exportReport"> 导出 </view>
 | ||
|           <i class="fas fa-angle-right" style="font-size: 20rpx; color: var(--tab-inactive);position: relative;top: 4rpx;"></i>
 | ||
|         </view>
 | ||
|     </view>
 | ||
| 
 | ||
|     <!-- 统计数据 -->
 | ||
|     <view class="stats-card card">
 | ||
|       <!-- Tabbar -->
 | ||
|       <view class="stat-tabs">
 | ||
|         <view 
 | ||
|           v-for="t in tabs" 
 | ||
|           :key="t.value" 
 | ||
|           class="stat-tab"
 | ||
|           :class="{ active: statTab === t.value }"
 | ||
|           @click="statTab = t.value"
 | ||
|           >{{ t.label }}</view
 | ||
|         >
 | ||
|       </view>
 | ||
| 
 | ||
|       <!-- 日统计(显示日历) -->
 | ||
|       <view v-if="statTab === 'day'" class="tab-content">
 | ||
|         <view class="calendar">
 | ||
|           <view class="cal-title">
 | ||
|             {{ today.getFullYear() }}年{{ today.getMonth() + 1 }}月
 | ||
|           </view>
 | ||
|           <view class="cal-week-head">
 | ||
|             <text
 | ||
|               v-for="w in ['日', '一', '二', '三', '四', '五', '六']"
 | ||
|               :key="w"
 | ||
|               >{{ w }}</text
 | ||
|             >
 | ||
|           </view>
 | ||
|           <view class="cal-body">
 | ||
|             <view
 | ||
|               v-for="(week, weekIndex) in calendarWeeks"
 | ||
|               :key="weekIndex"
 | ||
|               class="cal-row"
 | ||
|             >
 | ||
|               <view
 | ||
|                 v-for="day in week"
 | ||
|                 :key="day.key"
 | ||
|                 class="cal-cell"
 | ||
|                 :class="{
 | ||
|                   empty: day.empty,
 | ||
|                   selected:
 | ||
|                     !day.empty && calendarSelected.includes(day.fullDate),
 | ||
|                 }"
 | ||
|                 @click="
 | ||
|                   !day.empty &&
 | ||
|                     selectDay({
 | ||
|                       fullDate: day.fullDate,
 | ||
|                       day: day.day,
 | ||
|                       month: day.month,
 | ||
|                       year: day.year,
 | ||
|                     })
 | ||
|                 "
 | ||
|                 >{{ day.empty ? "" : day.day }}</view
 | ||
|               >
 | ||
|             </view>
 | ||
|           </view>
 | ||
|         </view>
 | ||
|         
 | ||
|         <!-- 选中日期的数据显示卡片 -->
 | ||
|         <view v-if="selectedDayInfo" class="day-data-card">
 | ||
|           <view class="day-data-header">
 | ||
|             <view class="day-data-title">
 | ||
|               <i class="fas fa-calendar-day"></i>
 | ||
|               <text>{{ selectedDayInfo.year }}年{{ selectedDayInfo.month }}月{{ selectedDayInfo.day }}日</text>
 | ||
|             </view>
 | ||
|             <view class="day-data-subtitle">考勤详情</view>
 | ||
|           </view>
 | ||
|           
 | ||
|           <view class="day-data-content">
 | ||
|             <view 
 | ||
|               class="day-data-item"
 | ||
|               v-for="(item, index) in dailyStats"
 | ||
|               :key="index"
 | ||
|             >
 | ||
|               <view class="day-data-icon" :class="item.status || 'default'">
 | ||
|                 <i :class="item.icon"></i>
 | ||
|               </view>
 | ||
|               <view class="day-data-info">
 | ||
|                 <view class="day-data-label">{{ item.title }}</view>
 | ||
|                 <view class="day-data-value" :class="item.status || 'default'">
 | ||
|                   {{ item.value }}
 | ||
|                 </view>
 | ||
|               </view>
 | ||
|             </view>
 | ||
|           </view>
 | ||
|         </view>
 | ||
|       </view>
 | ||
| 
 | ||
|       <!-- 周统计 -->
 | ||
|       <view v-if="statTab === 'week'" class="tab-content">
 | ||
|         <view class="bar-tabs">
 | ||
|           <view 
 | ||
|             class="bar-tab"
 | ||
|             v-for="(item, index) in weekTabs"
 | ||
|             :key="item.value"
 | ||
|             :class="{ active: weekTab === item.value }"
 | ||
|             @click="weekTab = item.value"
 | ||
|             >{{ item.label }}</view
 | ||
|           >
 | ||
|         </view>
 | ||
|         <view class="count-list">
 | ||
|           <view
 | ||
|             class="count-item"
 | ||
|             v-for="(item, idx) in (weekStats[weekTab] || [])"
 | ||
|             :key="idx"
 | ||
|           >
 | ||
|             <view class="c-value">{{ item.value }}</view>
 | ||
|             <view class="c-title">{{ item.title }}</view>
 | ||
|           </view>
 | ||
|         </view>
 | ||
|       </view>
 | ||
| 
 | ||
|       <!-- 月统计 -->
 | ||
|       <view v-if="statTab === 'month'" class="tab-content">
 | ||
|         <view class="month-grid">
 | ||
|               <view 
 | ||
|             class="month-item"
 | ||
|             v-for="(item, index) in monthTabs"
 | ||
|                 :key="item.value"
 | ||
|             :class="{ active: monthTab === item.value }"
 | ||
|                 @click="monthTab = item.value"
 | ||
|           >
 | ||
|             <view class="month-label">{{ item.label }}</view>
 | ||
|             </view>
 | ||
|         </view>
 | ||
|         <view class="count-list">
 | ||
|           <view
 | ||
|             class="count-item"
 | ||
|             v-for="(item, idx) in (monthStats[monthTab] || [])"
 | ||
|             :key="idx"
 | ||
|           >
 | ||
|             <view class="c-value">{{ item.value }}</view>
 | ||
|             <view class="c-title">{{ item.title }}</view>
 | ||
|           </view>
 | ||
|         </view>
 | ||
|       </view>
 | ||
|     </view>
 | ||
|     </scroll-view>
 | ||
|   </view>
 | ||
| </template>
 | ||
| 
 | ||
| <script>
 | ||
| export default {
 | ||
|   data() {
 | ||
|     const today = new Date();
 | ||
|     return {
 | ||
|       today: today,
 | ||
|       tabs: [
 | ||
|         { label: "日统计", value: "day" },
 | ||
|         { label: "周统计", value: "week" },
 | ||
|         { label: "月统计", value: "month" },
 | ||
|       ],
 | ||
|       statTab: "day",
 | ||
|       calendarSelected: [
 | ||
|         `${today.getFullYear()}-${String(today.getMonth() + 1).padStart(
 | ||
|           2,
 | ||
|           "0"
 | ||
|         )}-${String(today.getDate()).padStart(2, "0")}`,
 | ||
|       ],
 | ||
|       weekTabs: [
 | ||
|         { label: "第1周", value: "1" },
 | ||
|         { label: "第2周", value: "2" },
 | ||
|         { label: "第3周", value: "3" },
 | ||
|         { label: "第4周", value: "4" },
 | ||
|       ],
 | ||
|       weekTab: "1",
 | ||
|       monthTabs: Array.from({ length: 12 }, (_, i) => ({
 | ||
|         label: `${i + 1}月`,
 | ||
|         value: String(i + 1),
 | ||
|       })),
 | ||
|       monthTab: String(today.getMonth() + 1),
 | ||
|       baseStat: [
 | ||
|         { title: "平均工时", value: "7.8h" },
 | ||
|         { title: "迟到次数", value: "1" },
 | ||
|         { title: "早退次数", value: "0" },
 | ||
|         { title: "缺卡次数", value: "0" },
 | ||
|         { title: "旷工次数", value: "0" },
 | ||
|         { title: "外勤次数", value: "2" },
 | ||
|         { title: "加班时长", value: "4h" },
 | ||
|         { title: "调休时长", value: "2h" },
 | ||
|       ],
 | ||
|       selectedDayData: null, // 选中日期的数据
 | ||
|       selectedDayInfo: null, // 选中日期的基本信息
 | ||
|     };
 | ||
|   },
 | ||
|   computed: {
 | ||
|     weekStats() {
 | ||
|       try {
 | ||
|         return {
 | ||
|           "1": this.baseStat || [],
 | ||
|           "2": (this.baseStat || []).map((item, i) =>
 | ||
|             i === 1 ? { ...item, value: "0" } : item
 | ||
|           ),
 | ||
|           "3": (this.baseStat || []).map((item, i) =>
 | ||
|             i === 3 ? { ...item, value: "1" } : item
 | ||
|           ),
 | ||
|           "4": (this.baseStat || []).map((item, i) =>
 | ||
|             i === 5 ? { ...item, value: "3" } : item
 | ||
|           ),
 | ||
|         };
 | ||
|       } catch (error) {
 | ||
|         console.error('周统计数据生成错误:', error);
 | ||
|         return {};
 | ||
|       }
 | ||
|     },
 | ||
|     monthStats() {
 | ||
|       try {
 | ||
|         const stats = {};
 | ||
|         (this.monthTabs || []).forEach((_, i) => {
 | ||
|           stats[String(i + 1)] = (this.baseStat || []).map((item) => ({
 | ||
|             ...item,
 | ||
|             value: String(
 | ||
|               Math.floor(Math.random() * 3) +
 | ||
|                 (item.value.includes("h") ? "h" : "")
 | ||
|             ),
 | ||
|           }));
 | ||
|         });
 | ||
|         return stats;
 | ||
|       } catch (error) {
 | ||
|         console.error('月统计数据生成错误:', error);
 | ||
|         return {};
 | ||
|       }
 | ||
|     },
 | ||
|     // 日历相关计算属性
 | ||
|     days() {
 | ||
|       const d = new Date(
 | ||
|         this.today.getFullYear(),
 | ||
|         this.today.getMonth() + 1,
 | ||
|         0
 | ||
|       ).getDate();
 | ||
|       return Array.from({ length: d }, (_, i) => i + 1);
 | ||
|     },
 | ||
|     firstDay() {
 | ||
|       return new Date(
 | ||
|         this.today.getFullYear(),
 | ||
|         this.today.getMonth(),
 | ||
|         1
 | ||
|       ).getDay();
 | ||
|     },
 | ||
|     calendarWeeks() {
 | ||
|       try {
 | ||
|         const year = this.today.getFullYear();
 | ||
|         const month = this.today.getMonth();
 | ||
|         const firstDay = new Date(year, month, 1).getDay();
 | ||
|         const daysInMonth = new Date(year, month + 1, 0).getDate();
 | ||
| 
 | ||
|         const weeks = [];
 | ||
|         let currentWeek = [];
 | ||
| 
 | ||
|         // 添加空白日期(上个月的末尾几天)
 | ||
|         for (let i = 0; i < firstDay; i++) {
 | ||
|           currentWeek.push({
 | ||
|             key: `empty-${i}`,
 | ||
|             empty: true,
 | ||
|             day: "",
 | ||
|             fullDate: "",
 | ||
|             month: month,
 | ||
|             year: year,
 | ||
|           });
 | ||
|         }
 | ||
| 
 | ||
|         // 添加当前月的日期
 | ||
|         for (let day = 1; day <= daysInMonth; day++) {
 | ||
|           const fullDate = `${year}-${String(month + 1).padStart(
 | ||
|             2,
 | ||
|             "0"
 | ||
|           )}-${String(day).padStart(2, "0")}`;
 | ||
|           currentWeek.push({
 | ||
|             key: `day-${day}`,
 | ||
|             empty: false,
 | ||
|             day: day,
 | ||
|             fullDate: fullDate,
 | ||
|             month: month + 1,
 | ||
|             year: year,
 | ||
|           });
 | ||
| 
 | ||
|           // 如果一周满了(7天),开始新的一周
 | ||
|           if (currentWeek.length === 7) {
 | ||
|             weeks.push([...currentWeek]);
 | ||
|             currentWeek = [];
 | ||
|           }
 | ||
|         }
 | ||
| 
 | ||
|         // 如果最后一周不满7天,用空白日期填充
 | ||
|         while (currentWeek.length > 0 && currentWeek.length < 7) {
 | ||
|           currentWeek.push({
 | ||
|             key: `empty-end-${currentWeek.length}`,
 | ||
|             empty: true,
 | ||
|             day: "",
 | ||
|             fullDate: "",
 | ||
|             month: month,
 | ||
|             year: year,
 | ||
|           });
 | ||
|         }
 | ||
| 
 | ||
|         // 如果还有未完成的周,添加到结果中
 | ||
|         if (currentWeek.length > 0) {
 | ||
|           weeks.push(currentWeek);
 | ||
|         }
 | ||
| 
 | ||
|         return weeks;
 | ||
|       } catch (error) {
 | ||
|         console.error('日历生成错误:', error);
 | ||
|         return [];
 | ||
|       }
 | ||
|     },
 | ||
|     isMobile() {
 | ||
|       return getApp().globalData.isMobile;
 | ||
|     },
 | ||
|     // 生成每日数据
 | ||
|     dailyStats() {
 | ||
|       if (!this.selectedDayInfo) return [];
 | ||
|       
 | ||
|       const { year, month, day } = this.selectedDayInfo;
 | ||
|       const dateStr = `${year}-${String(month).padStart(2, '0')}-${String(day).padStart(2, '0')}`;
 | ||
|       
 | ||
|       // 模拟不同日期的不同数据
 | ||
|       const randomSeed = year * 10000 + month * 100 + day;
 | ||
|       const random = (seed) => {
 | ||
|         const x = Math.sin(seed) * 10000;
 | ||
|         return x - Math.floor(x);
 | ||
|       };
 | ||
|       
 | ||
|       return [
 | ||
|         { 
 | ||
|           title: "上班时间", 
 | ||
|           value: `${String(Math.floor(random(randomSeed) * 2) + 8).padStart(2, '0')}:${String(Math.floor(random(randomSeed + 1) * 60)).padStart(2, '0')}`,
 | ||
|           icon: "fas fa-clock"
 | ||
|         },
 | ||
|         { 
 | ||
|           title: "下班时间", 
 | ||
|           value: `${String(Math.floor(random(randomSeed + 2) * 2) + 17).padStart(2, '0')}:${String(Math.floor(random(randomSeed + 3) * 60)).padStart(2, '0')}`,
 | ||
|           icon: "fas fa-clock"
 | ||
|         },
 | ||
|         { 
 | ||
|           title: "迟到状态", 
 | ||
|           value: random(randomSeed + 5) > 0.8 ? "迟到" : "正常",
 | ||
|           icon: random(randomSeed + 5) > 0.8 ? "fas fa-exclamation-triangle" : "fas fa-check-circle",
 | ||
|           status: random(randomSeed + 5) > 0.8 ? "warning" : "success"
 | ||
|         },
 | ||
|         { 
 | ||
|           title: "早退状态", 
 | ||
|           value: random(randomSeed + 6) > 0.9 ? "早退" : "正常",
 | ||
|           icon: random(randomSeed + 6) > 0.9 ? "fas fa-exclamation-triangle" : "fas fa-check-circle",
 | ||
|           status: random(randomSeed + 6) > 0.9 ? "warning" : "success"
 | ||
|         },
 | ||
|         { 
 | ||
|           title: "工作时长", 
 | ||
|           value: `${(random(randomSeed + 4) * 4 + 6).toFixed(1)}h`,
 | ||
|           icon: "fas fa-hourglass-half"
 | ||
|         },
 | ||
|         { 
 | ||
|           title: "外勤次数", 
 | ||
|           value: Math.floor(random(randomSeed + 7) * 3).toString(),
 | ||
|           icon: "fas fa-map-marker-alt"
 | ||
|         },
 | ||
|         { 
 | ||
|           title: "加班时长", 
 | ||
|           value: random(randomSeed + 8) > 0.6 ? `${(random(randomSeed + 8) * 3).toFixed(1)}h` : "0h",
 | ||
|           icon: "fas fa-moon"
 | ||
|         },
 | ||
|         { 
 | ||
|           title: "调休时长", 
 | ||
|           value: random(randomSeed + 9) > 0.7 ? `${(random(randomSeed + 9) * 2).toFixed(1)}h` : "0h",
 | ||
|           icon: "fas fa-calendar-alt"
 | ||
|         },
 | ||
|       ];
 | ||
|     },
 | ||
|   },
 | ||
|   mounted() {
 | ||
|     console.log('统计页面初始化');
 | ||
|     console.log('当前月份:', this.today.getMonth() + 1);
 | ||
|     console.log('基础数据:', this.baseStat);
 | ||
|     console.log('周标签:', this.weekTabs);
 | ||
|     console.log('月标签:', this.monthTabs);
 | ||
|     console.log('当前周标签值:', this.weekTab, typeof this.weekTab);
 | ||
|     console.log('当前月标签值:', this.monthTab, typeof this.monthTab);
 | ||
|     console.log('周统计数据:', this.weekStats);
 | ||
|     console.log('月统计数据:', this.monthStats);
 | ||
|     console.log('周统计键:', Object.keys(this.weekStats));
 | ||
|     console.log('月统计键:', Object.keys(this.monthStats));
 | ||
|     console.log('周统计访问测试:', this.weekStats[this.weekTab]);
 | ||
|     console.log('月统计访问测试:', this.monthStats[this.monthTab]);
 | ||
|     
 | ||
|     // 初始化默认选中今天
 | ||
|     this.selectedDayInfo = {
 | ||
|       year: this.today.getFullYear(),
 | ||
|       month: this.today.getMonth() + 1,
 | ||
|       day: this.today.getDate(),
 | ||
|       fullDate: `${this.today.getFullYear()}-${String(this.today.getMonth() + 1).padStart(2, '0')}-${String(this.today.getDate()).padStart(2, '0')}`
 | ||
|     };
 | ||
|     console.log('默认选中今天:', this.selectedDayInfo);
 | ||
|   },
 | ||
|   methods: {
 | ||
|     selectDay(day) {
 | ||
|       this.calendarSelected = [day.fullDate];
 | ||
|       this.selectedDayInfo = {
 | ||
|         year: day.year,
 | ||
|         month: day.month,
 | ||
|         day: day.day,
 | ||
|         fullDate: day.fullDate
 | ||
|       };
 | ||
|       console.log('选择日期:', day);
 | ||
|       console.log('选中日期信息:', this.selectedDayInfo);
 | ||
|     },
 | ||
|     exportReport() {
 | ||
|       uni.showToast({ title: "导出功能暂未开放" });
 | ||
|     },
 | ||
|     goBack() {
 | ||
|       uni.navigateBack();
 | ||
|     },
 | ||
|     showMoreOptions() {
 | ||
|       uni.showActionSheet({
 | ||
|         itemList: ['刷新数据', '导出报表', '设置'],
 | ||
|         success: (res) => {
 | ||
|           if (res.tapIndex === 0) {
 | ||
|             uni.showToast({ title: '刷新成功' });
 | ||
|           } else if (res.tapIndex === 1) {
 | ||
|             this.exportReport();
 | ||
|           } else if (res.tapIndex === 2) {
 | ||
|             uni.showToast({ title: '设置功能暂未开放' });
 | ||
|           }
 | ||
|         }
 | ||
|       });
 | ||
|     },
 | ||
|   },
 | ||
| };
 | ||
| </script>
 | ||
| 
 | ||
| <style lang="scss" scoped>
 | ||
| /* 移动设备顶部状态栏 */
 | ||
| .top_bar {
 | ||
|   background: var(--gradient-primary);
 | ||
|   box-shadow: var(--shadow-lg);
 | ||
|   z-index: 9999;
 | ||
|   position: fixed;
 | ||
|   top: 0;
 | ||
|   left: 0;
 | ||
|   right: 0;
 | ||
|   height: calc(var(--status-bar-height) + 88rpx);
 | ||
|   display: flex;
 | ||
|   align-items: flex-end;
 | ||
|   padding-top: var(--status-bar-height);
 | ||
|   box-sizing: border-box;
 | ||
| }
 | ||
| 
 | ||
| /* 支持安全区域的设备 */
 | ||
| @supports (padding: max(0px)) {
 | ||
|   .top_bar {
 | ||
|     height: calc(var(--status-bar-height) + 88rpx + env(safe-area-inset-top));
 | ||
|     padding-top: calc(var(--status-bar-height) + env(safe-area-inset-top));
 | ||
|   }
 | ||
|   
 | ||
|   .top_bar + .statistics-content {
 | ||
|     margin-top: calc(var(--status-bar-height) + 88rpx + env(safe-area-inset-top));
 | ||
|   }
 | ||
| }
 | ||
| 
 | ||
| /* 顶部导航栏 */
 | ||
| .chat-header {
 | ||
|   display: flex;
 | ||
|   justify-content: space-between;
 | ||
|   align-items: center;
 | ||
|   height: 88rpx;
 | ||
|   background-color: var(--surface);
 | ||
|   border-bottom: 1rpx solid var(--border);
 | ||
|   padding: 0 20rpx;
 | ||
|   box-sizing: border-box;
 | ||
|   box-shadow: var(--shadow);
 | ||
| }
 | ||
| 
 | ||
| /* 移动设备下的导航栏样式 */
 | ||
| .top_bar .chat-header {
 | ||
|   background: transparent;
 | ||
|   border-bottom: none;
 | ||
|   box-shadow: none;
 | ||
|   width: 100%;
 | ||
|   height: 88rpx;
 | ||
|   padding: 0 20rpx;
 | ||
|   box-sizing: border-box;
 | ||
| }
 | ||
| 
 | ||
| .top_bar .header-title {
 | ||
|   color: var(--white);
 | ||
|   font-weight: 600;
 | ||
|   font-size: 36rpx;
 | ||
| }
 | ||
| 
 | ||
| .top_bar .header-left i,
 | ||
| .top_bar .header-right i {
 | ||
|   color: var(--white);
 | ||
|   font-size: 40rpx;
 | ||
| }
 | ||
| 
 | ||
| .header-left,
 | ||
| .header-right {
 | ||
|   width: 80rpx;
 | ||
|   height: 88rpx;
 | ||
|   display: flex;
 | ||
|   align-items: center;
 | ||
|   justify-content: center;
 | ||
|   transition: background-color 0.2s ease;
 | ||
| }
 | ||
| 
 | ||
| /* 移动设备下的按钮悬停效果 */
 | ||
| .top_bar .header-left:active,
 | ||
| .top_bar .header-right:active {
 | ||
|   background-color: rgba(255, 255, 255, 0.2);
 | ||
|   border-radius: 8rpx;
 | ||
| }
 | ||
| 
 | ||
| .header-title {
 | ||
|   font-size: 32rpx;
 | ||
|   font-weight: 600;
 | ||
|   color: var(--title-color);
 | ||
| }
 | ||
| 
 | ||
| .header-icon {
 | ||
|   font-size: 36rpx;
 | ||
|   color: var(--text-color);
 | ||
|   padding: 10rpx;
 | ||
|   transition: all 0.3s ease;
 | ||
| }
 | ||
| 
 | ||
| .header-icon:active {
 | ||
|   background: var(--hover-bg);
 | ||
|   transform: scale(0.95);
 | ||
|   border-radius: 8rpx;
 | ||
| }
 | ||
| 
 | ||
| /* 移动设备下为内容添加顶部间距 */
 | ||
| .top_bar + .statistics-content {
 | ||
|   margin-top: calc(var(--status-bar-height) + 88rpx);
 | ||
| }
 | ||
| 
 | ||
| /* 浏览器环境下为内容添加顶部间距 */
 | ||
| .chat-header + .statistics-content {
 | ||
|   margin-top: calc(var(--status-bar-height) + 88rpx);
 | ||
| }
 | ||
| 
 | ||
| .statistics-container {
 | ||
|   background: #f7f8fa;
 | ||
|   min-height: 100vh;
 | ||
| }
 | ||
| 
 | ||
| .statistics-content {
 | ||
|   padding: 32rpx 0;
 | ||
|   min-height: calc(100vh - 88rpx);
 | ||
| }
 | ||
| 
 | ||
|   .card {
 | ||
|     background: #fff;
 | ||
|     border-radius: 16rpx;
 | ||
|   box-shadow: 0 4rpx 16rpx 0 rgba(0, 0, 0, 0.04);
 | ||
|     margin: 0 32rpx 32rpx;
 | ||
|     padding: 32rpx;
 | ||
| }
 | ||
| .export-card {
 | ||
|   display: flex;
 | ||
|   justify-content: space-between;
 | ||
|   align-items: center;
 | ||
|   .export-title {
 | ||
|     font-size: 32rpx;
 | ||
|     font-weight: 600;
 | ||
|   }
 | ||
|   .btn-export {
 | ||
|     // background: #3c9cff;
 | ||
|     color: var(--tab-inactive);
 | ||
|     font-size: 28rpx;
 | ||
|     border: none;
 | ||
|     // padding: 4rpx 40rpx;
 | ||
|     border-radius: 8rpx;
 | ||
|   }
 | ||
| 
 | ||
|   .exports {
 | ||
|     display: flex;
 | ||
|     align-items: center;
 | ||
|     .btn-export {
 | ||
|       margin-right: 16rpx;
 | ||
|     }
 | ||
|   }
 | ||
| }
 | ||
| .stats-card {
 | ||
|   .stat-tabs {
 | ||
|     display: flex;
 | ||
|     margin-bottom: 32rpx;
 | ||
|     .stat-tab {
 | ||
|       flex: 1;
 | ||
|       text-align: center;
 | ||
|       font-size: 28rpx;
 | ||
|       color: var(--text-muted);
 | ||
|       padding-bottom: 16rpx;
 | ||
|       border-bottom: 4rpx solid transparent;
 | ||
|       transition: all 0.2s;
 | ||
|       &.active {
 | ||
|         color: var(--primary-color);
 | ||
|         font-weight: 600;
 | ||
|         border-color: var(--primary-color);
 | ||
|       }
 | ||
|     }
 | ||
|   }
 | ||
|   .tab-content {
 | ||
|     margin-top: 16rpx;
 | ||
|   }
 | ||
|   // 月统计网格布局
 | ||
|   .month-grid {
 | ||
|     display: grid;
 | ||
|     grid-template-columns: repeat(4, 1fr);
 | ||
|     gap: 12rpx;
 | ||
|     margin-bottom: 24rpx;
 | ||
|     
 | ||
|     .month-item {
 | ||
|       background: var(--gray-lighter);
 | ||
|       border-radius: 12rpx;
 | ||
|       padding: 20rpx 12rpx;
 | ||
|       text-align: center;
 | ||
|       border: 1rpx solid var(--border-light);
 | ||
|       transition: all 0.3s ease;
 | ||
|       cursor: pointer;
 | ||
|       
 | ||
|       .month-label {
 | ||
|         font-size: 26rpx;
 | ||
|         color: var(--text-secondary);
 | ||
|         font-weight: 500;
 | ||
|       }
 | ||
|       
 | ||
|       &.active {
 | ||
|         background: var(--primary-color);
 | ||
|         border-color: var(--primary-color);
 | ||
|         box-shadow: var(--shadow-md);
 | ||
|         transform: translateY(-2rpx);
 | ||
|         
 | ||
|         .month-label {
 | ||
|           color: var(--white);
 | ||
|           font-weight: 600;
 | ||
|         }
 | ||
|       }
 | ||
|       
 | ||
|       &:not(.active):active {
 | ||
|         background: var(--primary-light);
 | ||
|         transform: scale(0.98);
 | ||
|         
 | ||
|         .month-label {
 | ||
|           color: var(--white);
 | ||
|         }
 | ||
|       }
 | ||
|     }
 | ||
|   }
 | ||
| 
 | ||
|   .bar-tabs {
 | ||
|     display: flex;
 | ||
|     background: var(--gray-lighter);
 | ||
|     border-radius: 12rpx;
 | ||
|     padding: 6rpx;
 | ||
|     margin-bottom: 24rpx;
 | ||
|     position: relative;
 | ||
| 
 | ||
|     .bar-tab {
 | ||
|       flex: 1;
 | ||
|       text-align: center;
 | ||
|       font-size: 28rpx;
 | ||
|       color: var(--text-secondary);
 | ||
|       padding: 16rpx 12rpx;
 | ||
|       border-radius: 8rpx;
 | ||
|       margin: 0 2rpx;
 | ||
|       background: transparent;
 | ||
|       transition: all 0.3s ease;
 | ||
|       position: relative;
 | ||
|       font-weight: 500;
 | ||
| 
 | ||
|       &.active {
 | ||
|         color: var(--primary-color);
 | ||
|         background: var(--surface);
 | ||
|         font-weight: 600;
 | ||
|         box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.1);
 | ||
|         transform: translateY(-1rpx);
 | ||
|       }
 | ||
| 
 | ||
|       &:not(.active):active {
 | ||
|         background: rgba(60, 156, 255, 0.1);
 | ||
|       }
 | ||
|     }
 | ||
|   }
 | ||
| 
 | ||
|   .bar-tabs-scroll {
 | ||
|     background: #f8f9fa;
 | ||
|     border-radius: 12rpx;
 | ||
|     padding: 6rpx;
 | ||
|     margin-bottom: 24rpx;
 | ||
| 
 | ||
|     .bar-scroll {
 | ||
|     white-space: nowrap;
 | ||
| 
 | ||
|     .bar-tab {
 | ||
|       display: inline-block;
 | ||
|         min-width: 120rpx;
 | ||
|         margin: 0 4rpx;
 | ||
|         padding: 16rpx 20rpx;
 | ||
|         text-align: center;
 | ||
|         font-size: 28rpx;
 | ||
|         color: #666;
 | ||
|         border-radius: 8rpx;
 | ||
|         background: transparent;
 | ||
|         transition: all 0.3s ease;
 | ||
|         font-weight: 500;
 | ||
| 
 | ||
|         &.active {
 | ||
|           color: #3c9cff;
 | ||
|           background: #fff;
 | ||
|           font-weight: 600;
 | ||
|           box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.1);
 | ||
|           transform: translateY(-1rpx);
 | ||
|         }
 | ||
| 
 | ||
|         &:not(.active):active {
 | ||
|           background: rgba(60, 156, 255, 0.1);
 | ||
|         }
 | ||
|       }
 | ||
|     }
 | ||
|   }
 | ||
|   // 日统计数据显示卡片
 | ||
|   .day-data-card {
 | ||
|     margin-top: 24rpx;
 | ||
|     background: var(--surface);
 | ||
|     border-radius: 16rpx;
 | ||
|     box-shadow: var(--shadow-md);
 | ||
|     overflow: hidden;
 | ||
|     border: 1rpx solid var(--border);
 | ||
|     
 | ||
|     .day-data-header {
 | ||
|       background: var(--gradient-primary);
 | ||
|       padding: 32rpx 24rpx;
 | ||
|       color: var(--white);
 | ||
|       
 | ||
|       .day-data-title {
 | ||
|         display: flex;
 | ||
|         align-items: center;
 | ||
|         font-size: 32rpx;
 | ||
|         font-weight: 600;
 | ||
|         margin-bottom: 8rpx;
 | ||
|         color: var(--white);
 | ||
|         
 | ||
|         i {
 | ||
|           margin-right: 12rpx;
 | ||
|           font-size: 28rpx;
 | ||
|           color: var(--white);
 | ||
|         }
 | ||
|       }
 | ||
|       
 | ||
|       .day-data-subtitle {
 | ||
|         font-size: 24rpx;
 | ||
|         opacity: 0.9;
 | ||
|         color: var(--white);
 | ||
|       }
 | ||
|     }
 | ||
|     
 | ||
|     .day-data-content {
 | ||
|       padding: 24rpx;
 | ||
|       background: var(--surface);
 | ||
|       display: flex;
 | ||
|       flex-wrap: wrap;
 | ||
|       justify-content: space-between;
 | ||
|       
 | ||
|       .day-data-item {
 | ||
|         display: flex;
 | ||
|         align-items: center;
 | ||
|         padding: 20rpx 16rpx;
 | ||
|         width: 48%;
 | ||
|         margin-bottom: 16rpx;
 | ||
|         background: var(--gray-lighter);
 | ||
|         border-radius: 12rpx;
 | ||
|         border: 1rpx solid var(--border-light);
 | ||
|         box-sizing: border-box;
 | ||
|         
 | ||
|         &:nth-child(odd) {
 | ||
|           margin-right: 2%;
 | ||
|         }
 | ||
|         
 | ||
|         &:nth-child(even) {
 | ||
|           margin-left: 2%;
 | ||
|         }
 | ||
|         
 | ||
|         .day-data-icon {
 | ||
|           width: 48rpx;
 | ||
|           height: 48rpx;
 | ||
|           border-radius: 50%;
 | ||
|           display: flex;
 | ||
|           align-items: center;
 | ||
|           justify-content: center;
 | ||
|           margin-right: 12rpx;
 | ||
|           flex-shrink: 0;
 | ||
|           
 | ||
|           i {
 | ||
|             font-size: 20rpx;
 | ||
|           }
 | ||
|           
 | ||
|           &.default {
 | ||
|             background: var(--gray-light);
 | ||
|             color: var(--text-muted);
 | ||
|           }
 | ||
|           
 | ||
|           &.success {
 | ||
|             background: var(--success-light);
 | ||
|             color: var(--success);
 | ||
|           }
 | ||
|           
 | ||
|           &.warning {
 | ||
|             background: var(--warning-light);
 | ||
|             color: var(--warning);
 | ||
|           }
 | ||
|         }
 | ||
|         
 | ||
|         .day-data-info {
 | ||
|           flex: 1;
 | ||
|           min-width: 0;
 | ||
|           
 | ||
|           .day-data-label {
 | ||
|             font-size: 22rpx;
 | ||
|             color: var(--text-secondary);
 | ||
|             margin-bottom: 6rpx;
 | ||
|             white-space: nowrap;
 | ||
|             overflow: hidden;
 | ||
|             text-overflow: ellipsis;
 | ||
|           }
 | ||
|           
 | ||
|           .day-data-value {
 | ||
|             font-size: 24rpx;
 | ||
|             font-weight: 600;
 | ||
|             
 | ||
|             &.default {
 | ||
|               color: var(--text-color);
 | ||
|             }
 | ||
|             
 | ||
|             &.success {
 | ||
|               color: var(--success);
 | ||
|             }
 | ||
|             
 | ||
|             &.warning {
 | ||
|               color: var(--warning);
 | ||
|             }
 | ||
|           }
 | ||
|         }
 | ||
|       }
 | ||
|     }
 | ||
|   }
 | ||
| 
 | ||
|   .count-list {
 | ||
|     display: flex;
 | ||
|     flex-wrap: wrap;
 | ||
|     margin-top: 16rpx;
 | ||
|     justify-content: space-between;
 | ||
|     .count-item {
 | ||
|       display: flex;
 | ||
|       flex-direction: column;
 | ||
|       align-items: center;
 | ||
|       width: 48%;
 | ||
|       margin-bottom: 16rpx;
 | ||
|       padding: 24rpx;
 | ||
|       background: var(--gray-lighter);
 | ||
|       border-radius: 12rpx;
 | ||
|       box-sizing: border-box;
 | ||
|       border: 1rpx solid var(--border-light);
 | ||
|       .c-title {
 | ||
|         color: var(--text-secondary);
 | ||
|         margin-bottom: 8rpx;
 | ||
|         font-size: 20rpx;
 | ||
|       }
 | ||
|       .c-value {
 | ||
|         color: var(--text-color);
 | ||
|         font-size: 40rpx;
 | ||
|         font-weight: 500;
 | ||
|         margin-bottom: 8rpx;
 | ||
|       }
 | ||
|     }
 | ||
|   }
 | ||
| }
 | ||
| 
 | ||
| /* 日历样式 */
 | ||
| .calendar {
 | ||
|   background: var(--surface);
 | ||
|   border-radius: 12rpx;
 | ||
|   padding: 24rpx;
 | ||
|   margin-top: 16rpx;
 | ||
|   border: 1rpx solid var(--border);
 | ||
|   box-shadow: var(--shadow);
 | ||
| 
 | ||
|   .cal-title {
 | ||
|     text-align: center;
 | ||
|     font-weight: 600;
 | ||
|     font-size: 32rpx;
 | ||
|     color: var(--title-color);
 | ||
|     margin-bottom: 24rpx;
 | ||
|   }
 | ||
| 
 | ||
|   .cal-week-head {
 | ||
|     display: flex;
 | ||
|     margin-bottom: 16rpx;
 | ||
| 
 | ||
|     text {
 | ||
|       flex: 1;
 | ||
|       text-align: center;
 | ||
|       font-size: 26rpx;
 | ||
|       color: var(--text-muted);
 | ||
|       font-weight: 500;
 | ||
|       padding: 12rpx 0;
 | ||
|     }
 | ||
|   }
 | ||
| 
 | ||
|   .cal-body {
 | ||
|     .cal-row {
 | ||
|       display: flex;
 | ||
|       margin-bottom: 8rpx;
 | ||
| 
 | ||
|       .cal-cell {
 | ||
|         flex: 1;
 | ||
|         height: 64rpx;
 | ||
|         line-height: 64rpx;
 | ||
|         text-align: center;
 | ||
|         border-radius: 8rpx;
 | ||
|         margin: 0 4rpx;
 | ||
|         background: var(--gray-lighter);
 | ||
|         font-size: 28rpx;
 | ||
|         color: var(--text-color);
 | ||
|         transition: all 0.2s ease;
 | ||
|         position: relative;
 | ||
| 
 | ||
|         &:active {
 | ||
|           transform: scale(0.95);
 | ||
|         }
 | ||
| 
 | ||
|         &.selected {
 | ||
|           background: var(--primary-color);
 | ||
|           color: var(--white);
 | ||
|           font-weight: 600;
 | ||
|           box-shadow: var(--shadow-md);
 | ||
|         }
 | ||
| 
 | ||
|         &.empty {
 | ||
|           background: transparent;
 | ||
|           pointer-events: none;
 | ||
|         }
 | ||
| 
 | ||
|         &:not(.empty):not(.selected):hover {
 | ||
|           background: var(--primary-light);
 | ||
|           color: var(--white);
 | ||
|         }
 | ||
|       }
 | ||
|     }
 | ||
|   }
 | ||
| }
 | ||
| :deep() {
 | ||
|   .uni-scroll-view {
 | ||
|     overflow: hidden !important;
 | ||
|   }
 | ||
| }
 | ||
| </style>
 |