1259 lines
		
	
	
		
			32 KiB
		
	
	
	
		
			Vue
		
	
	
	
	
	
			
		
		
	
	
			1259 lines
		
	
	
		
			32 KiB
		
	
	
	
		
			Vue
		
	
	
	
	
	
| <template>
 | ||
|   <view class="attendance-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>
 | ||
| 
 | ||
|     <!-- 顶部日期与问候语 -->
 | ||
|     <view class="header">
 | ||
|       <!-- <view class="date">
 | ||
|         {{ currentDate }}
 | ||
|       </view>
 | ||
|       <view class="greeting">
 | ||
|         {{ greeting }}, {{ userName }}
 | ||
|       </view> -->
 | ||
|     </view>
 | ||
| 
 | ||
|     <!-- Tab切换 -->
 | ||
|     <view class="tab-container">
 | ||
|       <view
 | ||
|         class="tab-item"
 | ||
|         :class="{ active: activeTab === 'work' }"
 | ||
|         @tap="switchTab('work')"
 | ||
|       >
 | ||
|         上下班打卡
 | ||
|       </view>
 | ||
|       <view
 | ||
|         class="tab-item"
 | ||
|         :class="{ active: activeTab === 'out' }"
 | ||
|         @tap="switchTab('out')"
 | ||
|       >
 | ||
|         外出打卡
 | ||
|       </view>
 | ||
|     </view>
 | ||
| 
 | ||
|     <!-- 考勤摘要模块 -->
 | ||
|     <view class="attendance-summary flex-column">
 | ||
|       <view class="flex-between underline pb-30">
 | ||
|         <view class="status">
 | ||
|         <view class="label">上班时间</view>
 | ||
|         <view class="value">{{ workStart }}</view>
 | ||
|       </view>
 | ||
|       <view class="status">
 | ||
|         <view class="label">下班时间</view>
 | ||
|         <view class="value">{{ workEnd }}</view>
 | ||
|       </view>
 | ||
|       <view class="status">
 | ||
|         <view class="label">已打卡天数</view>
 | ||
|         <view class="value">{{ punchDays }}</view>
 | ||
|       </view>
 | ||
|       </view>
 | ||
|       <view class="flex mt-30 tools-bar" >
 | ||
|         <view @tap="goToRecord">记录</view>
 | ||
|         <view @tap="goToStatistics">统计</view>
 | ||
|         <view @tap="goToRule">规则</view>
 | ||
|       </view>
 | ||
|     </view>
 | ||
| 
 | ||
|     <!-- 上下班打卡区域 -->
 | ||
|     <view class="clockin-area" v-if="activeTab === 'work'">
 | ||
|       <view class="circle" @tap="handleWorkPunch">
 | ||
|         <view class="punch-time">{{ currentTime }}</view>
 | ||
|         <view class="punch-status" v-if="workRecorded">
 | ||
|           <text class="status-success">已打卡</text>
 | ||
|         </view>
 | ||
|         <view v-else class="punch-btn">
 | ||
|           {{ workPunchText }}
 | ||
|         </view>
 | ||
|       </view>
 | ||
|       <view class="location">
 | ||
|         <text class="iconfont icon-dizhi"></text>
 | ||
|         {{ location }}
 | ||
|         <button class="location-refresh" size="mini" @tap="refreshLocation">
 | ||
|           刷新定位
 | ||
|         </button>
 | ||
|       </view>
 | ||
|     </view>
 | ||
| 
 | ||
|     <!-- 外出打卡区域 -->
 | ||
|     <view class="out-area" v-if="activeTab === 'out'">
 | ||
|       <view class="out-form">
 | ||
|         <view class="form-item">
 | ||
|           <text class="label">开始时间</text>
 | ||
|           <picker
 | ||
|             mode="time"
 | ||
|             :value="startTime"
 | ||
|             @change="onStartTimeChange"
 | ||
|             class="time-picker"
 | ||
|           >
 | ||
|             <view class="picker-text">{{ startTime || "请选择开始时间" }}</view>
 | ||
|           </picker>
 | ||
|         </view>
 | ||
|         <view class="form-item">
 | ||
|           <text class="label">结束时间</text>
 | ||
|           <picker
 | ||
|             mode="time"
 | ||
|             :value="endTime"
 | ||
|             @change="onEndTimeChange"
 | ||
|             class="time-picker"
 | ||
|           >
 | ||
|             <view class="picker-text">{{ endTime || "请选择结束时间" }}</view>
 | ||
|           </picker>
 | ||
|         </view>
 | ||
|         <view class="form-item">
 | ||
|           <text class="label">外出时长</text>
 | ||
|           <view class="duration-display">
 | ||
|             <text class="duration-text">{{ calculatedDuration }}</text>
 | ||
|           </view>
 | ||
|         </view>
 | ||
|         <view class="form-item">
 | ||
|           <text class="label">外出事由</text>
 | ||
|           <textarea
 | ||
|             class="textarea"
 | ||
|             v-model="outReason"
 | ||
|             placeholder="请输入外出事由(8000字以内)"
 | ||
|             placeholder-style="color: #999"
 | ||
|             :maxlength="8000"
 | ||
|             :show-count="true"
 | ||
|             auto-height
 | ||
|           />
 | ||
|         </view>
 | ||
|       </view>
 | ||
|       <view class="out-punch-btn">
 | ||
|         <button
 | ||
|           class="punch-btn"
 | ||
|           :disabled="!canOutPunch"
 | ||
|           @tap="handleOutPunch"
 | ||
|         >
 | ||
|           提交外出申请
 | ||
|         </button>
 | ||
|       </view>
 | ||
|     </view>
 | ||
| 
 | ||
|     <!-- 打卡记录弹窗 -->
 | ||
|     <view class="record-modal" v-if="showRecordModal" @tap="closeRecordModal">
 | ||
|       <view class="modal-content" @tap.stop>
 | ||
|         <view class="modal-header">
 | ||
|           <view class="modal-title">打卡记录</view>
 | ||
|           <view class="close-btn" @tap="closeRecordModal">
 | ||
|             <i class="fas fa-times"></i>
 | ||
|           </view>
 | ||
|         </view>
 | ||
|         <view class="modal-body">
 | ||
|           <view v-if="todayRecords.length > 0">
 | ||
|             <view
 | ||
|               class="record-item"
 | ||
|               v-for="(record, idx) in todayRecords"
 | ||
|               :key="idx"
 | ||
|             >
 | ||
|               <view class="record-left">
 | ||
|                 <view class="record-time">{{ formatRecordTime(record.time) }}</view>
 | ||
|                 <view class="record-type">{{ record.type }}</view>
 | ||
|                 <view v-if="record.reason" class="record-detail"
 | ||
|                   >事由:{{ record.reason }}</view
 | ||
|                 >
 | ||
|                 <view v-if="record.startTime" class="record-detail"
 | ||
|                   >开始时间:{{ record.startTime }}</view
 | ||
|                 >
 | ||
|                 <view v-if="record.endTime" class="record-detail"
 | ||
|                   >结束时间:{{ record.endTime }}</view
 | ||
|                 >
 | ||
|                 <view v-if="record.duration" class="record-detail"
 | ||
|                   >外出时长:{{ record.duration }}</view
 | ||
|                 >
 | ||
|               </view>
 | ||
|             </view>
 | ||
|           </view>
 | ||
|           <view v-else class="no-record">暂无打卡记录</view>
 | ||
|         </view>
 | ||
|       </view>
 | ||
|     </view>
 | ||
| 
 | ||
|     <!-- 考勤规则弹窗 -->
 | ||
|     <view class="rule-modal" v-if="showRuleModal" @tap="closeRuleModal">
 | ||
|       <view class="modal-content" @tap.stop>
 | ||
|         <view class="modal-header">
 | ||
|           <view class="modal-title">考勤规则</view>
 | ||
|           <view class="close-btn" @tap="closeRuleModal">
 | ||
|             <i class="fas fa-times"></i>
 | ||
|           </view>
 | ||
|         </view>
 | ||
|         <view class="modal-body">
 | ||
|           <view class="rule-sections">
 | ||
|             <!-- 考勤时间 -->
 | ||
|             <view class="rule-section">
 | ||
|               <view class="rule-header" @tap="toggleRuleSection('attendance')">
 | ||
|                 <view class="rule-title">
 | ||
|                   <i class="fas fa-clock"></i>
 | ||
|                   考勤时间
 | ||
|                 </view>
 | ||
|                 <view class="rule-arrow" :class="{ expanded: expandedRules.attendance }">
 | ||
|                   <i class="fas fa-chevron-down"></i>
 | ||
|                 </view>
 | ||
|               </view>
 | ||
|               <view class="rule-content" v-if="expandedRules.attendance">
 | ||
|                 <view class="rule-item">
 | ||
|                   <text class="rule-label">上班时间:</text>
 | ||
|                   <text class="rule-value">09:00</text>
 | ||
|                 </view>
 | ||
|                 <view class="rule-item">
 | ||
|                   <text class="rule-label">下班时间:</text>
 | ||
|                   <text class="rule-value">18:00</text>
 | ||
|                 </view>
 | ||
|                 <view class="rule-item">
 | ||
|                   <text class="rule-label">午休时间:</text>
 | ||
|                   <text class="rule-value">12:00 - 13:00</text>
 | ||
|                 </view>
 | ||
|                 <view class="rule-item">
 | ||
|                   <text class="rule-label">弹性时间:</text>
 | ||
|                   <text class="rule-value">±30分钟</text>
 | ||
|                 </view>
 | ||
|               </view>
 | ||
|             </view>
 | ||
| 
 | ||
|             <!-- 补卡规则 -->
 | ||
|             <view class="rule-section">
 | ||
|               <view class="rule-header" @tap="toggleRuleSection('supplement')">
 | ||
|                 <view class="rule-title">
 | ||
|                   <i class="fas fa-edit"></i>
 | ||
|                   补卡规则
 | ||
|                 </view>
 | ||
|                 <view class="rule-arrow" :class="{ expanded: expandedRules.supplement }">
 | ||
|                   <i class="fas fa-chevron-down"></i>
 | ||
|                 </view>
 | ||
|               </view>
 | ||
|               <view class="rule-content" v-if="expandedRules.supplement">
 | ||
|                 <view class="rule-item">
 | ||
|                   <text class="rule-label">补卡时限:</text>
 | ||
|                   <text class="rule-value">当日24:00前</text>
 | ||
|                 </view>
 | ||
|                 <view class="rule-item">
 | ||
|                   <text class="rule-label">补卡次数:</text>
 | ||
|                   <text class="rule-value">每月最多3次</text>
 | ||
|                 </view>
 | ||
|                 <view class="rule-item">
 | ||
|                   <text class="rule-label">补卡原因:</text>
 | ||
|                   <text class="rule-value">必须填写详细说明</text>
 | ||
|                 </view>
 | ||
|                 <view class="rule-item">
 | ||
|                   <text class="rule-label">审批流程:</text>
 | ||
|                   <text class="rule-value">直属领导审批</text>
 | ||
|                 </view>
 | ||
|               </view>
 | ||
|             </view>
 | ||
| 
 | ||
|             <!-- 更多规则 -->
 | ||
|             <view class="rule-section">
 | ||
|               <view class="rule-header" @tap="toggleRuleSection('more')">
 | ||
|                 <view class="rule-title">
 | ||
|                   <i class="fas fa-cog"></i>
 | ||
|                   更多规则
 | ||
|                 </view>
 | ||
|                 <view class="rule-arrow" :class="{ expanded: expandedRules.more }">
 | ||
|                   <i class="fas fa-chevron-down"></i>
 | ||
|                 </view>
 | ||
|               </view>
 | ||
|               <view class="rule-content" v-if="expandedRules.more">
 | ||
|                 <view class="rule-item">
 | ||
|                   <text class="rule-label">迟到处理:</text>
 | ||
|                   <text class="rule-value">超过30分钟算迟到</text>
 | ||
|                 </view>
 | ||
|                 <view class="rule-item">
 | ||
|                   <text class="rule-label">早退处理:</text>
 | ||
|                   <text class="rule-value">提前30分钟以上算早退</text>
 | ||
|                 </view>
 | ||
|                 <view class="rule-item">
 | ||
|                   <text class="rule-label">外出申请:</text>
 | ||
|                   <text class="rule-value">需提前1小时申请</text>
 | ||
|                 </view>
 | ||
|                 <view class="rule-item">
 | ||
|                   <text class="rule-label">请假制度:</text>
 | ||
|                   <text class="rule-value">按公司请假制度执行</text>
 | ||
|                 </view>
 | ||
|                 <view class="rule-item">
 | ||
|                   <text class="rule-label">加班规定:</text>
 | ||
|                   <text class="rule-value">超过18:30算加班</text>
 | ||
|                 </view>
 | ||
|               </view>
 | ||
|             </view>
 | ||
|           </view>
 | ||
|         </view>
 | ||
|       </view>
 | ||
|     </view>
 | ||
|   </view>
 | ||
| </template>
 | ||
| 
 | ||
| <script>
 | ||
| export default {
 | ||
|   data() {
 | ||
|     return {
 | ||
|       userName: "张三",
 | ||
|       currentDate: "",
 | ||
|       greeting: "",
 | ||
|       currentTime: "",
 | ||
|       workStart: "09:00",
 | ||
|       workEnd: "18:00",
 | ||
|       punchDays: 12,
 | ||
|       workRecorded: false,
 | ||
|       location: "正在定位中...",
 | ||
|       todayRecords: [],
 | ||
|       activeTab: "work", // 当前激活的tab
 | ||
|       // 外出打卡相关数据
 | ||
|       outReason: "",
 | ||
|       startTime: "",
 | ||
|       endTime: "",
 | ||
|       outRecorded: false,
 | ||
|       // 弹窗相关数据
 | ||
|       showRecordModal: false,
 | ||
|       showRuleModal: false,
 | ||
|       expandedRules: {
 | ||
|         attendance: true,  // 默认展开第一个
 | ||
|         supplement: false,
 | ||
|         more: false
 | ||
|       },
 | ||
|     };
 | ||
|   },
 | ||
|   onLoad() {
 | ||
|     this.updateDateTime();
 | ||
|     this.greeting = this.getGreeting();
 | ||
|     this.refreshLocation();
 | ||
|     this.loadTodayRecords();
 | ||
|     // 更新时间
 | ||
|     this.timer = setInterval(this.updateDateTime, 1000);
 | ||
|   },
 | ||
|   onUnload() {
 | ||
|     clearInterval(this.timer);
 | ||
|   },
 | ||
|   computed: {
 | ||
|     // 从全局数据获取设备信息
 | ||
|     isMobile() {
 | ||
|       return getApp().globalData.isMobile;
 | ||
|     },
 | ||
|     workPunchText() {
 | ||
|       // 根据今日打卡记录判断是上班还是下班
 | ||
|       const workRecords = this.todayRecords.filter(
 | ||
|         (record) => record.type === "上班打卡" || record.type === "下班打卡"
 | ||
|       );
 | ||
|       if (workRecords.length === 0) {
 | ||
|         return "上班打卡";
 | ||
|       } else if (workRecords.length === 1) {
 | ||
|         return "下班打卡";
 | ||
|       } else {
 | ||
|         return "今日已打卡";
 | ||
|       }
 | ||
|     },
 | ||
|     canOutPunch() {
 | ||
|       return (
 | ||
|         this.startTime && 
 | ||
|         this.endTime && 
 | ||
|         this.outReason.trim() &&
 | ||
|         this.calculatedDuration !== "时间无效"
 | ||
|       );
 | ||
|     },
 | ||
|     calculatedDuration() {
 | ||
|       if (!this.startTime || !this.endTime) {
 | ||
|         return "请选择开始和结束时间";
 | ||
|       }
 | ||
|       
 | ||
|       const start = this.parseTime(this.startTime);
 | ||
|       const end = this.parseTime(this.endTime);
 | ||
|       
 | ||
|       if (!start || !end) {
 | ||
|         return "时间格式错误";
 | ||
|       }
 | ||
|       
 | ||
|       if (end <= start) {
 | ||
|         return "结束时间必须晚于开始时间";
 | ||
|       }
 | ||
|       
 | ||
|       const diffMs = end - start;
 | ||
|       const hours = Math.floor(diffMs / (1000 * 60 * 60));
 | ||
|       const minutes = Math.floor((diffMs % (1000 * 60 * 60)) / (1000 * 60));
 | ||
|       
 | ||
|       if (hours > 0) {
 | ||
|         return `${hours}小时${minutes}分钟`;
 | ||
|       } else {
 | ||
|         return `${minutes}分钟`;
 | ||
|       }
 | ||
|     },
 | ||
|   },
 | ||
|   methods: {
 | ||
|     updateDateTime() {
 | ||
|       const now = new Date();
 | ||
|       this.currentDate = `${now.getFullYear()}-${(now.getMonth() + 1)
 | ||
|         .toString()
 | ||
|         .padStart(2, "0")}-${now.getDate().toString().padStart(2, "0")}`;
 | ||
|       this.currentTime = now.toTimeString().slice(0, 8);
 | ||
|     },
 | ||
|     getGreeting() {
 | ||
|       const hour = new Date().getHours();
 | ||
|       if (hour < 6) return "夜深了";
 | ||
|       if (hour < 9) return "早上好";
 | ||
|       if (hour < 12) return "上午好";
 | ||
|       if (hour < 14) return "中午好";
 | ||
|       if (hour < 18) return "下午好";
 | ||
|       if (hour < 22) return "晚上好";
 | ||
|       return "夜深了";
 | ||
|     },
 | ||
|     switchTab(tab) {
 | ||
|       this.activeTab = tab;
 | ||
|     },
 | ||
|     handleWorkPunch() {
 | ||
|       const now = new Date();
 | ||
|       const year = now.getFullYear();
 | ||
|       const month = String(now.getMonth() + 1).padStart(2, '0');
 | ||
|       const day = String(now.getDate()).padStart(2, '0');
 | ||
|       const time = `${year}-${month}-${day} ${this.currentTime}`;
 | ||
|       
 | ||
|       // 这里只做前端模拟,实际要调用接口
 | ||
|       let type = "";
 | ||
|       const workRecords = this.todayRecords.filter(
 | ||
|         (record) => record.type === "上班打卡" || record.type === "下班打卡"
 | ||
|       );
 | ||
| 
 | ||
|       if (workRecords.length === 0) {
 | ||
|         type = "上班打卡";
 | ||
|       } else if (workRecords.length === 1) {
 | ||
|         type = "下班打卡";
 | ||
|       } else {
 | ||
|         uni.showToast({ title: "今日已打卡完成", icon: "none" });
 | ||
|         return;
 | ||
|       }
 | ||
| 
 | ||
|       this.todayRecords.push({ time, type });
 | ||
|       this.workRecorded = true;
 | ||
|       uni.showToast({ title: `${type}成功`, icon: "success" });
 | ||
|       setTimeout(() => {
 | ||
|         this.workRecorded = false;
 | ||
|       }, 2000);
 | ||
|     },
 | ||
|     handleOutPunch() {
 | ||
|       if (!this.canOutPunch) {
 | ||
|         if (!this.startTime || !this.endTime) {
 | ||
|           uni.showToast({ title: "请选择开始和结束时间", icon: "none" });
 | ||
|         } else if (this.calculatedDuration === "结束时间必须晚于开始时间") {
 | ||
|           uni.showToast({ title: "结束时间必须晚于开始时间", icon: "none" });
 | ||
|         } else if (!this.outReason.trim()) {
 | ||
|           uni.showToast({ title: "请填写外出事由", icon: "none" });
 | ||
|         } else {
 | ||
|           uni.showToast({ title: "请填写完整信息", icon: "none" });
 | ||
|         }
 | ||
|         return;
 | ||
|       }
 | ||
| 
 | ||
|       const now = new Date();
 | ||
|       const year = now.getFullYear();
 | ||
|       const month = String(now.getMonth() + 1).padStart(2, '0');
 | ||
|       const day = String(now.getDate()).padStart(2, '0');
 | ||
|       const time = `${year}-${month}-${day} ${this.currentTime}`;
 | ||
|       
 | ||
|       const type = "外出申请";
 | ||
|       const record = {
 | ||
|         time,
 | ||
|         type,
 | ||
|         reason: this.outReason,
 | ||
|         startTime: this.startTime,
 | ||
|         endTime: this.endTime,
 | ||
|         duration: this.calculatedDuration,
 | ||
|       };
 | ||
| 
 | ||
|       this.todayRecords.push(record);
 | ||
|       this.outRecorded = true;
 | ||
|       uni.showToast({ title: "外出申请提交成功", icon: "success" });
 | ||
| 
 | ||
|       // 清空表单
 | ||
|       this.outReason = "";
 | ||
|       this.startTime = "";
 | ||
|       this.endTime = "";
 | ||
| 
 | ||
|       setTimeout(() => {
 | ||
|         this.outRecorded = false;
 | ||
|       }, 2000);
 | ||
|     },
 | ||
|     onStartTimeChange(e) {
 | ||
|       this.startTime = e.detail.value;
 | ||
|     },
 | ||
|     onEndTimeChange(e) {
 | ||
|       this.endTime = e.detail.value;
 | ||
|     },
 | ||
|     parseTime(timeStr) {
 | ||
|       if (!timeStr) return null;
 | ||
|       const [hours, minutes] = timeStr.split(':').map(Number);
 | ||
|       if (isNaN(hours) || isNaN(minutes)) return null;
 | ||
|       
 | ||
|       const today = new Date();
 | ||
|       today.setHours(hours, minutes, 0, 0);
 | ||
|       return today;
 | ||
|     },
 | ||
|     refreshLocation() {
 | ||
|       // 模拟定位
 | ||
|       this.location = "北京·中关村";
 | ||
|       // 若已集成定位api可替换
 | ||
|       // uni.getLocation({...});
 | ||
|     },
 | ||
|     loadTodayRecords() {
 | ||
|       // 模拟从本地加载今日打卡
 | ||
|       const today = new Date();
 | ||
|       const year = today.getFullYear();
 | ||
|       const month = String(today.getMonth() + 1).padStart(2, '0');
 | ||
|       const day = String(today.getDate()).padStart(2, '0');
 | ||
|       
 | ||
|       this.todayRecords = [
 | ||
|         {
 | ||
|           time: `${year}-${month}-${day} 09:15:30`,
 | ||
|           type: "上班打卡"
 | ||
|         },
 | ||
|         {
 | ||
|           time: `${year}-${month}-${day} 12:30:45`,
 | ||
|           type: "外出申请",
 | ||
|           reason: "客户拜访",
 | ||
|           startTime: "12:30",
 | ||
|           endTime: "14:00",
 | ||
|           duration: "1小时30分钟"
 | ||
|         },
 | ||
|         {
 | ||
|           time: `${year}-${month}-${day} 18:20:15`,
 | ||
|           type: "下班打卡"
 | ||
|         }
 | ||
|       ];
 | ||
|     },
 | ||
|     /**
 | ||
|      * 返回
 | ||
|      */
 | ||
|     goBack() {
 | ||
|       uni.navigateBack();
 | ||
|     },
 | ||
|     /**
 | ||
|      * 显示更多选项
 | ||
|      */
 | ||
|     showMoreOptions() {
 | ||
|       uni.showActionSheet({
 | ||
|         itemList: ["考勤记录", "设置", "帮助"],
 | ||
|         success: (res) => {
 | ||
|           switch (res.tapIndex) {
 | ||
|             case 0:
 | ||
|               // 考勤记录
 | ||
|               this.showRecordModal = true;
 | ||
|               break;
 | ||
|             case 1:
 | ||
|               // 设置
 | ||
|               uni.showToast({ title: "设置功能开发中", icon: "none" });
 | ||
|               break;
 | ||
|             case 2:
 | ||
|               // 帮助
 | ||
|               uni.showToast({ title: "帮助功能开发中", icon: "none" });
 | ||
|               break;
 | ||
|           }
 | ||
|         },
 | ||
|       });
 | ||
|     },
 | ||
|     /**
 | ||
|      * 显示打卡记录
 | ||
|      */
 | ||
|     goToRecord() {
 | ||
|       this.showRecordModal = true;
 | ||
|     },
 | ||
|     /**
 | ||
|      * 关闭打卡记录弹窗
 | ||
|      */
 | ||
|     closeRecordModal() {
 | ||
|       this.showRecordModal = false;
 | ||
|     },
 | ||
|     /**
 | ||
|      * 跳转到统计页面
 | ||
|      */
 | ||
|     goToStatistics() {
 | ||
|       uni.navigateTo({
 | ||
|         url: "/pages/hr/attendance/statistics",
 | ||
|       });
 | ||
|     },
 | ||
|     /**
 | ||
|      * 显示考勤规则弹窗
 | ||
|      */
 | ||
|     goToRule() {
 | ||
|       this.showRuleModal = true;
 | ||
|     },
 | ||
|     /**
 | ||
|      * 关闭考勤规则弹窗
 | ||
|      */
 | ||
|     closeRuleModal() {
 | ||
|       this.showRuleModal = false;
 | ||
|     },
 | ||
|     /**
 | ||
|      * 切换规则栏目展开状态
 | ||
|      */
 | ||
|     toggleRuleSection(section) {
 | ||
|       this.expandedRules[section] = !this.expandedRules[section];
 | ||
|     },
 | ||
|     /**
 | ||
|      * 格式化记录时间显示
 | ||
|      */
 | ||
|     formatRecordTime(timeStr) {
 | ||
|       if (!timeStr) return '';
 | ||
|       
 | ||
|       // 如果是完整的时间字符串(包含年月日)
 | ||
|       if (timeStr.includes('-') || timeStr.includes('/')) {
 | ||
|         const date = new Date(timeStr);
 | ||
|         return date.toLocaleString('zh-CN', {
 | ||
|           year: 'numeric',
 | ||
|           month: '2-digit',
 | ||
|           day: '2-digit',
 | ||
|           hour: '2-digit',
 | ||
|           minute: '2-digit',
 | ||
|           second: '2-digit'
 | ||
|         });
 | ||
|       }
 | ||
|       
 | ||
|       // 如果只是时间字符串,添加今天的日期
 | ||
|       const today = new Date();
 | ||
|       const year = today.getFullYear();
 | ||
|       const month = String(today.getMonth() + 1).padStart(2, '0');
 | ||
|       const day = String(today.getDate()).padStart(2, '0');
 | ||
|       
 | ||
|       return `${year}-${month}-${day} ${timeStr}`;
 | ||
|     },
 | ||
|   },
 | ||
| };
 | ||
| </script>
 | ||
| 
 | ||
| <style lang="scss" scoped>
 | ||
| .attendance-container {
 | ||
|   min-height: 100vh;
 | ||
|   background: #f4f6fa;
 | ||
|   padding: 0;
 | ||
| }
 | ||
| 
 | ||
| /* 移动设备顶部状态栏 */
 | ||
| .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 + .header {
 | ||
|     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);
 | ||
| }
 | ||
| 
 | ||
| /* 浏览器环境下的固定定位 */
 | ||
| .attendance-container .chat-header:not(.top_bar .chat-header) {
 | ||
|   position: fixed;
 | ||
|   top: 0;
 | ||
|   left: 0;
 | ||
|   right: 0;
 | ||
|   z-index: 9999;
 | ||
|   height: calc(var(--status-bar-height) + 88rpx);
 | ||
|   padding-top: var(--status-bar-height);
 | ||
| }
 | ||
| 
 | ||
| /* 移动设备下的导航栏样式 */
 | ||
| .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;
 | ||
| }
 | ||
| 
 | ||
| .header-right:active {
 | ||
|   background-color: var(--surface-hover);
 | ||
|   border-radius: 8rpx;
 | ||
| }
 | ||
| 
 | ||
| /* 移动设备下的按钮悬停效果 */
 | ||
| .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);
 | ||
| }
 | ||
| 
 | ||
| /* 浏览器环境下为内容添加顶部间距 */
 | ||
| .attendance-container .chat-header:not(.top_bar .chat-header) + .header {
 | ||
|   margin-top: calc(var(--status-bar-height) + 88rpx);
 | ||
| }
 | ||
| 
 | ||
| @supports (padding: max(0px)) {
 | ||
|   .attendance-container .chat-header:not(.top_bar .chat-header) {
 | ||
|     height: calc(var(--status-bar-height) + 88rpx + env(safe-area-inset-top));
 | ||
|     padding-top: calc(var(--status-bar-height) + env(safe-area-inset-top));
 | ||
|   }
 | ||
| 
 | ||
|   .attendance-container .chat-header:not(.top_bar .chat-header) + .header {
 | ||
|     margin-top: calc(
 | ||
|       var(--status-bar-height) + 88rpx + env(safe-area-inset-top)
 | ||
|     );
 | ||
|   }
 | ||
| }
 | ||
| .header {
 | ||
|   padding: 48rpx 36rpx 12rpx 36rpx;
 | ||
| //   background: linear-gradient(90deg, #5497ff 0%, #88bafe 100%);
 | ||
|   color: #fff;
 | ||
|   .date {
 | ||
|     font-size: 28rpx;
 | ||
|     margin-bottom: 8rpx;
 | ||
|     opacity: 0.9;
 | ||
|   }
 | ||
|   .greeting {
 | ||
|     font-size: 40rpx;
 | ||
|     font-weight: bold;
 | ||
|   }
 | ||
| }
 | ||
| 
 | ||
| .tab-container {
 | ||
|   background: #fff;
 | ||
|   margin: -32rpx 36rpx 0 36rpx;
 | ||
|   border-radius: 16rpx 16rpx 0 0;
 | ||
|   border-bottom: 1rpx solid #efefefef;
 | ||
|   display: flex;
 | ||
|   box-shadow: 0 -6rpx 18rpx 0 rgba(74, 144, 226, 0.04);
 | ||
|   position: relative;
 | ||
|   z-index: 1;
 | ||
|   .tab-item {
 | ||
|     flex: 1;
 | ||
|     text-align: center;
 | ||
|     padding: 24rpx 0;
 | ||
|     font-size: 28rpx;
 | ||
|     color: #9eaab7;
 | ||
|     position: relative;
 | ||
|     transition: all 0.3s ease;
 | ||
|     &.active {
 | ||
|       color: #388bff;
 | ||
|       font-weight: bold;
 | ||
|       &::after {
 | ||
|         content: "";
 | ||
|         position: absolute;
 | ||
|         bottom: 0;
 | ||
|         left: 50%;
 | ||
|         transform: translateX(-50%);
 | ||
|         width: 60rpx;
 | ||
|         height: 4rpx;
 | ||
|         background: #388bff;
 | ||
|         border-radius: 2rpx;
 | ||
|       }
 | ||
|     }
 | ||
|   }
 | ||
| }
 | ||
| 
 | ||
| .attendance-summary {
 | ||
|   background: #fff;
 | ||
|   box-shadow: 0 6rpx 18rpx 0 rgba(74, 144, 226, 0.04);
 | ||
|   border-radius: 16rpx;
 | ||
|   padding: 36rpx 30rpx;
 | ||
|   margin: -32rpx 36rpx 32rpx 36rpx;
 | ||
|   display: flex;
 | ||
|   justify-content: space-between;
 | ||
|   .status {
 | ||
|     text-align: center;
 | ||
|     margin-top: 30rpx;
 | ||
|     .label {
 | ||
|       color: #9eaab7;
 | ||
|       font-size: 24rpx;
 | ||
|     }
 | ||
|     .value {
 | ||
|       color: #2d4259;
 | ||
|       font-size: 32rpx;
 | ||
|       margin-top: 8rpx;
 | ||
|       font-weight: bold;
 | ||
|     }
 | ||
|   }
 | ||
| 
 | ||
|   .tools-bar {
 | ||
|     font-size: 28rpx;
 | ||
|     color: #388bff;
 | ||
|     font-weight: bold;
 | ||
|     display: flex;
 | ||
|     justify-content: space-around;
 | ||
|     align-items: center;
 | ||
|   }
 | ||
| }
 | ||
| 
 | ||
| .clockin-area {
 | ||
|   display: flex;
 | ||
|   flex-direction: column;
 | ||
|   align-items: center;
 | ||
|   margin: 240rpx 0 0 0;
 | ||
|   .circle {
 | ||
|     width: 320rpx;
 | ||
|     height: 320rpx;
 | ||
|     border-radius: 50%;
 | ||
|     background: #fff;
 | ||
|     box-shadow: 0 8rpx 18rpx 0 rgba(74, 144, 226, 0.06);
 | ||
|     display: flex;
 | ||
|     flex-direction: column;
 | ||
|     align-items: center;
 | ||
|     justify-content: center;
 | ||
|     position: relative;
 | ||
|     .punch-time {
 | ||
|       font-size: 60rpx;
 | ||
|       font-weight: bold;
 | ||
|       color: #2d4259;
 | ||
|       margin-bottom: 8rpx;
 | ||
|     }
 | ||
|     .punch-status {
 | ||
|       margin-top: 16rpx;
 | ||
|       .status-success {
 | ||
|         color: #66c27c;
 | ||
|         font-size: 32rpx;
 | ||
|       }
 | ||
|     }
 | ||
|     .punch-btn {
 | ||
|       border: none;
 | ||
|       color: #388bff;
 | ||
|       font-size: 32rpx;
 | ||
|     }
 | ||
|   }
 | ||
|   .location {
 | ||
|     margin-top: 26rpx;
 | ||
|     color: #90a4b7;
 | ||
|     font-size: 24rpx;
 | ||
|     display: flex;
 | ||
|     align-items: center;
 | ||
|     .iconfont {
 | ||
|       margin-right: 10rpx;
 | ||
|       font-size: 26rpx;
 | ||
|     }
 | ||
|     .location-refresh {
 | ||
|       background: none;
 | ||
|       color: #388bff;
 | ||
|       margin-left: 18rpx;
 | ||
|       font-size: 22rpx;
 | ||
|       border: none;
 | ||
|       padding: 0 10rpx;
 | ||
|     }
 | ||
|   }
 | ||
| }
 | ||
| 
 | ||
| .out-area {
 | ||
|   margin: 24rpx 36rpx 0 36rpx;
 | ||
|   background: #fff;
 | ||
|   border-radius: 16rpx;
 | ||
|   box-shadow: 0 8rpx 18rpx 0 rgba(74, 144, 226, 0.06);
 | ||
|   padding: 36rpx 30rpx;
 | ||
| 
 | ||
|   .out-form {
 | ||
|     .form-item {
 | ||
|       margin-bottom: 32rpx;
 | ||
|       .label {
 | ||
|         display: block;
 | ||
|         font-size: 28rpx;
 | ||
|         color: #2d4259;
 | ||
|         margin-bottom: 16rpx;
 | ||
|         font-weight: 500;
 | ||
|       }
 | ||
|       .input {
 | ||
|         width: 100%;
 | ||
|         height: 80rpx;
 | ||
|         background: #f8f9fa;
 | ||
|         border: 1rpx solid #e9ecef;
 | ||
|         border-radius: 12rpx;
 | ||
|         padding: 0 24rpx;
 | ||
|         font-size: 28rpx;
 | ||
|         color: #2d4259;
 | ||
|         box-sizing: border-box;
 | ||
|         &:focus {
 | ||
|           border-color: #388bff;
 | ||
|           background: #fff;
 | ||
|         }
 | ||
|       }
 | ||
|       .textarea {
 | ||
|         width: 100%;
 | ||
|         min-height: 120rpx;
 | ||
|         background: #f8f9fa;
 | ||
|         border: 1rpx solid #e9ecef;
 | ||
|         border-radius: 12rpx;
 | ||
|         padding: 20rpx 24rpx;
 | ||
|         font-size: 28rpx;
 | ||
|         color: #2d4259;
 | ||
|         box-sizing: border-box;
 | ||
|         line-height: 1.5;
 | ||
|         &:focus {
 | ||
|           border-color: #388bff;
 | ||
|           background: #fff;
 | ||
|         }
 | ||
|       }
 | ||
|       .duration-display {
 | ||
|         width: 100%;
 | ||
|         height: 80rpx;
 | ||
|         background: #f8f9fa;
 | ||
|         border: 1rpx solid #e9ecef;
 | ||
|         border-radius: 12rpx;
 | ||
|         display: flex;
 | ||
|         align-items: center;
 | ||
|         padding: 0 24rpx;
 | ||
|         box-sizing: border-box;
 | ||
|         .duration-text {
 | ||
|           font-size: 28rpx;
 | ||
|           color: #2d4259;
 | ||
|           font-weight: 500;
 | ||
|         }
 | ||
|       }
 | ||
|       .time-picker {
 | ||
|         width: 100%;
 | ||
|         height: 80rpx;
 | ||
|         background: #f8f9fa;
 | ||
|         border: 1rpx solid #e9ecef;
 | ||
|         border-radius: 12rpx;
 | ||
|         display: flex;
 | ||
|         align-items: center;
 | ||
|         padding: 0 24rpx;
 | ||
|         box-sizing: border-box;
 | ||
|         .picker-text {
 | ||
|           font-size: 28rpx;
 | ||
|           color: #2d4259;
 | ||
|         }
 | ||
|         &:active {
 | ||
|           border-color: #388bff;
 | ||
|           background: #fff;
 | ||
|         }
 | ||
|       }
 | ||
|     }
 | ||
|   }
 | ||
| 
 | ||
|   .out-punch-btn {
 | ||
|     margin-top: 40rpx;
 | ||
|     text-align: center;
 | ||
|     .punch-btn {
 | ||
|       background: linear-gradient(90deg, #388bff 0%, #62b3ff 100%);
 | ||
|       border: none;
 | ||
|       color: #fff;
 | ||
|       font-size: 32rpx;
 | ||
|       border-radius: 64rpx;
 | ||
|       width: 200rpx;
 | ||
|       height: 80rpx;
 | ||
|       box-shadow: 0 4rpx 10rpx 0 rgba(56, 139, 255, 0.17);
 | ||
|       &:disabled {
 | ||
|         background: #e9ecef;
 | ||
|         color: #9eaab7;
 | ||
|         box-shadow: none;
 | ||
|       }
 | ||
|     }
 | ||
|   }
 | ||
| }
 | ||
| 
 | ||
| .record-section {
 | ||
|   margin: 48rpx 36rpx 0 36rpx;
 | ||
|   background: #fff;
 | ||
|   border-radius: 16rpx;
 | ||
|   box-shadow: 0 4rpx 10rpx 0 rgba(74, 144, 226, 0.04);
 | ||
|   padding: 30rpx 24rpx;
 | ||
|   .record-title {
 | ||
|     font-size: 30rpx;
 | ||
|     font-weight: bold;
 | ||
|     margin-bottom: 18rpx;
 | ||
|     color: #2d4259;
 | ||
|   }
 | ||
|   .record-item {
 | ||
|     padding: 24rpx 0;
 | ||
|     border-bottom: 1px solid #f2f3f8;
 | ||
|     &:last-child {
 | ||
|       border-bottom: none;
 | ||
|     }
 | ||
|     .record-left {
 | ||
|       .record-time {
 | ||
|         font-size: 28rpx;
 | ||
|         color: #2d4259;
 | ||
|         font-weight: 500;
 | ||
|         margin-bottom: 8rpx;
 | ||
|       }
 | ||
|       .record-type {
 | ||
|         color: #388bff;
 | ||
|         font-size: 26rpx;
 | ||
|         font-weight: 500;
 | ||
|         margin-bottom: 8rpx;
 | ||
|       }
 | ||
|       .record-detail {
 | ||
|         font-size: 24rpx;
 | ||
|         color: #9eaab7;
 | ||
|         margin-bottom: 4rpx;
 | ||
|         line-height: 1.4;
 | ||
|       }
 | ||
|     }
 | ||
|   }
 | ||
|   .no-record {
 | ||
|     color: #aaa;
 | ||
|     text-align: center;
 | ||
|     padding: 26rpx 0;
 | ||
|     font-size: 28rpx;
 | ||
|   }
 | ||
| }
 | ||
| 
 | ||
| /* 打卡记录弹窗 */
 | ||
| .record-modal {
 | ||
|   position: fixed;
 | ||
|   top: 0;
 | ||
|   left: 0;
 | ||
|   right: 0;
 | ||
|   bottom: 0;
 | ||
|   background: rgba(0, 0, 0, 0.5);
 | ||
|   z-index: 10000;
 | ||
|   display: flex;
 | ||
|   align-items: center;
 | ||
|   justify-content: center;
 | ||
|   padding: 40rpx;
 | ||
|   box-sizing: border-box;
 | ||
| }
 | ||
| 
 | ||
| .modal-content {
 | ||
|   background: #fff;
 | ||
|   border-radius: 16rpx;
 | ||
|   width: 100%;
 | ||
|   max-width: 600rpx;
 | ||
|   max-height: 80vh;
 | ||
|   overflow: hidden;
 | ||
|   box-shadow: 0 20rpx 40rpx rgba(0, 0, 0, 0.15);
 | ||
| }
 | ||
| 
 | ||
| .modal-header {
 | ||
|   display: flex;
 | ||
|   align-items: center;
 | ||
|   justify-content: space-between;
 | ||
|   padding: 30rpx 40rpx;
 | ||
|   border-bottom: 1rpx solid #f0f0f0;
 | ||
|   background: #fff;
 | ||
|   position: sticky;
 | ||
|   top: 0;
 | ||
|   z-index: 1;
 | ||
| }
 | ||
| 
 | ||
| .modal-title {
 | ||
|   font-size: 32rpx;
 | ||
|   font-weight: 600;
 | ||
|   color: #2d4259;
 | ||
| }
 | ||
| 
 | ||
| .close-btn {
 | ||
|   width: 60rpx;
 | ||
|   height: 60rpx;
 | ||
|   display: flex;
 | ||
|   align-items: center;
 | ||
|   justify-content: center;
 | ||
|   border-radius: 50%;
 | ||
|   background: #f5f5f5;
 | ||
|   color: #666;
 | ||
|   font-size: 28rpx;
 | ||
|   transition: all 0.2s ease;
 | ||
| }
 | ||
| 
 | ||
| .close-btn:active {
 | ||
|   background: #e0e0e0;
 | ||
|   transform: scale(0.95);
 | ||
| }
 | ||
| 
 | ||
| .modal-body {
 | ||
|   padding: 0 40rpx 40rpx 40rpx;
 | ||
|   max-height: 60vh;
 | ||
|   overflow-y: auto;
 | ||
| }
 | ||
| 
 | ||
| .modal-body .record-item {
 | ||
|   padding: 24rpx 0;
 | ||
|   border-bottom: 1rpx solid #f2f3f8;
 | ||
|   &:last-child {
 | ||
|     border-bottom: none;
 | ||
|   }
 | ||
|   .record-left {
 | ||
|     .record-time {
 | ||
|       font-size: 28rpx;
 | ||
|       color: #2d4259;
 | ||
|       font-weight: 500;
 | ||
|       margin-bottom: 8rpx;
 | ||
|     }
 | ||
|     .record-type {
 | ||
|       color: #388bff;
 | ||
|       font-size: 26rpx;
 | ||
|       font-weight: 500;
 | ||
|       margin-bottom: 8rpx;
 | ||
|     }
 | ||
|     .record-detail {
 | ||
|       font-size: 24rpx;
 | ||
|       color: #9eaab7;
 | ||
|       margin-bottom: 4rpx;
 | ||
|       line-height: 1.4;
 | ||
|     }
 | ||
|   }
 | ||
| }
 | ||
| 
 | ||
| .modal-body .no-record {
 | ||
|   color: #aaa;
 | ||
|   text-align: center;
 | ||
|   padding: 60rpx 0;
 | ||
|   font-size: 28rpx;
 | ||
| }
 | ||
| 
 | ||
| /* 考勤规则弹窗 */
 | ||
| .rule-modal {
 | ||
|   position: fixed;
 | ||
|   top: 0;
 | ||
|   left: 0;
 | ||
|   right: 0;
 | ||
|   bottom: 0;
 | ||
|   background: rgba(0, 0, 0, 0.5);
 | ||
|   z-index: 10000;
 | ||
|   display: flex;
 | ||
|   align-items: center;
 | ||
|   justify-content: center;
 | ||
|   padding: 40rpx;
 | ||
|   box-sizing: border-box;
 | ||
| }
 | ||
| 
 | ||
| .rule-sections {
 | ||
|   .rule-section {
 | ||
|     margin-bottom: 20rpx;
 | ||
|     border-radius: 12rpx;
 | ||
|     overflow: hidden;
 | ||
|     background: #f8f9fa;
 | ||
|     border: 1rpx solid #e9ecef;
 | ||
|     
 | ||
|     .rule-header {
 | ||
|       display: flex;
 | ||
|       align-items: center;
 | ||
|       justify-content: space-between;
 | ||
|       padding: 24rpx 30rpx;
 | ||
|       background: #fff;
 | ||
|       border-bottom: 1rpx solid #e9ecef;
 | ||
|       cursor: pointer;
 | ||
|       transition: all 0.2s ease;
 | ||
|       
 | ||
|       &:active {
 | ||
|         background: #f5f5f5;
 | ||
|       }
 | ||
|       
 | ||
|       .rule-title {
 | ||
|         display: flex;
 | ||
|         align-items: center;
 | ||
|         font-size: 30rpx;
 | ||
|         font-weight: 600;
 | ||
|         color: #2d4259;
 | ||
|         
 | ||
|         i {
 | ||
|           margin-right: 16rpx;
 | ||
|           color: #388bff;
 | ||
|           font-size: 28rpx;
 | ||
|         }
 | ||
|       }
 | ||
|       
 | ||
|       .rule-arrow {
 | ||
|         color: #9eaab7;
 | ||
|         font-size: 24rpx;
 | ||
|         transition: transform 0.3s ease;
 | ||
|         
 | ||
|         &.expanded {
 | ||
|           transform: rotate(180deg);
 | ||
|         }
 | ||
|       }
 | ||
|     }
 | ||
|     
 | ||
|     .rule-content {
 | ||
|       padding: 0 30rpx 24rpx 30rpx;
 | ||
|       background: #fff;
 | ||
|       
 | ||
|       .rule-item {
 | ||
|         display: flex;
 | ||
|         justify-content: space-between;
 | ||
|         align-items: center;
 | ||
|         padding: 16rpx 0;
 | ||
|         border-bottom: 1rpx solid #f0f0f0;
 | ||
|         
 | ||
|         &:last-child {
 | ||
|           border-bottom: none;
 | ||
|         }
 | ||
|         
 | ||
|         .rule-label {
 | ||
|           font-size: 26rpx;
 | ||
|           color: #666;
 | ||
|           flex: 1;
 | ||
|         }
 | ||
|         
 | ||
|         .rule-value {
 | ||
|           font-size: 26rpx;
 | ||
|           color: #2d4259;
 | ||
|           font-weight: 500;
 | ||
|           text-align: right;
 | ||
|         }
 | ||
|       }
 | ||
|     }
 | ||
|   }
 | ||
| }
 | ||
| </style>
 |