hn_platform_h5/src/pages/own/attendance-statistics/index.vue

418 lines
10 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<view class="page-container">
<!-- 导航栏 -->
<NavBarModal navBarTitle="考勤统计">
<template #left>
<view class="back-btn" @tap="handleBack">
<up-icon name="arrow-left" size="20" color="#fff" />
</view>
</template>
</NavBarModal>
<!-- 内容区域 -->
<view class="content-wrapper" :style="contentStyle">
<!-- 日历区域 -->
<view class="calendar-section">
<!-- 月份导航 -->
<view class="month-header">
<view class="month-nav-btn" @tap="handlePrevMonth">
<up-icon name="arrow-left" size="20" color="#333" />
</view>
<text class="month-text">{{ currentMonthText }}</text>
<view class="month-nav-btn" @tap="handleNextMonth">
<up-icon name="arrow-right" size="20" color="#333" />
</view>
</view>
<!-- 星期标题 -->
<view class="weekdays-header">
<view v-for="(day, index) in weekdays" :key="index" class="weekday-cell">
<text class="weekday-text">{{ day }}</text>
</view>
</view>
<!-- 日历网格 -->
<view class="calendar-grid">
<view
v-for="(date, index) in calendarDays"
:key="index"
class="date-cell"
:class="{
'other-month': !date.isCurrentMonth,
selected: date.isSelected,
'has-abnormal': date.hasAbnormal,
}"
@tap="handleDateClick(date)"
>
<text class="date-number">{{ date.day }}</text>
<text v-if="date.hasAbnormal" class="abnormal-label">异常</text>
</view>
</view>
</view>
<!-- 日期详情区域 -->
<view class="detail-section">
<text class="detail-date">{{ selectedDateText }}</text>
<view class="detail-content">
<view class="detail-line"></view>
<text class="detail-status">{{ selectedDateStatus }}</text>
</view>
</view>
</view>
</view>
</template>
<script setup>
import { ref, computed, onMounted } from 'vue'
import NavBarModal from '@/components/NavBarModal/index.vue'
import { getContentStyle } from '@/utils/safeArea'
import dayjs from 'dayjs'
/**
* 自有考勤统计页面
* 业务背景:用于查看自有人员的考勤统计数据
* 设计决策:
* 1. 使用日历视图展示考勤数据
* 2. 支持月份切换
* 3. 标记异常日期
* 4. 显示选中日期的考勤详情
*/
const contentStyle = computed(() => {
return getContentStyle({
includeNavBar: true,
includeStatusBar: true,
includeBottomSafeArea: true,
})
})
// 星期标题
const weekdays = ['日', '一', '二', '三', '四', '五', '六']
// 当前显示的月份
const currentMonth = ref(dayjs())
// 选中的日期
const selectedDate = ref(dayjs())
// 异常日期列表(示例数据,后续从接口获取)
const abnormalDates = ref([
'2025-11-30',
'2025-12-01',
// 可以添加更多异常日期
])
// 日期状态映射(示例数据,后续从接口获取)
const dateStatusMap = ref({
'2025-12-01': '上班未打卡',
'2025-11-30': '下班未打卡',
// 可以添加更多日期状态
})
// 当前月份文本
const currentMonthText = computed(() => {
return currentMonth.value.format('YYYY年MM月')
})
// 选中的日期文本
const selectedDateText = computed(() => {
return selectedDate.value.format('YYYY年MM月DD日')
})
// 选中日期的状态
const selectedDateStatus = computed(() => {
const dateKey = selectedDate.value.format('YYYY-MM-DD')
return dateStatusMap.value[dateKey] || '正常'
})
// 日历天数数组
const calendarDays = computed(() => {
const days = []
const startOfMonth = currentMonth.value.startOf('month')
const endOfMonth = currentMonth.value.endOf('month')
const startDay = startOfMonth.day() // 0-6, 0是星期日
// 上个月的日期
for (let i = startDay - 1; i >= 0; i--) {
const date = dayjs(startOfMonth).subtract(i + 1, 'day')
const dateKey = date.format('YYYY-MM-DD')
days.push({
date: date.toDate(),
day: date.date(),
isCurrentMonth: false,
isSelected: false,
hasAbnormal: abnormalDates.value.includes(dateKey),
})
}
// 当前月的日期
for (let i = 0; i < endOfMonth.date(); i++) {
const date = dayjs(startOfMonth).add(i, 'day')
const dateKey = date.format('YYYY-MM-DD')
const isSelected = date.isSame(selectedDate.value, 'day')
days.push({
date: date.toDate(),
day: date.date(),
isCurrentMonth: true,
isSelected,
hasAbnormal: abnormalDates.value.includes(dateKey),
})
}
// 下个月的日期补齐到42个格子6行x7列
const remainingDays = 42 - days.length
for (let i = 1; i <= remainingDays; i++) {
const date = dayjs(endOfMonth).add(i, 'day')
const dateKey = date.format('YYYY-MM-DD')
days.push({
date: date.toDate(),
day: date.date(),
isCurrentMonth: false,
isSelected: false,
hasAbnormal: abnormalDates.value.includes(dateKey),
})
}
return days
})
/**
* 处理日期点击
* @param {Object} dateObj - 日期对象
*/
const handleDateClick = (dateObj) => {
selectedDate.value = dayjs(dateObj.date)
// TODO: 加载该日期的详细考勤信息
loadDateDetail(dateObj.date)
}
/**
* 切换到上一个月
*/
const handlePrevMonth = () => {
currentMonth.value = dayjs(currentMonth.value).subtract(1, 'month')
// TODO: 重新加载该月的考勤数据
loadMonthData()
}
/**
* 切换到下一个月
*/
const handleNextMonth = () => {
currentMonth.value = dayjs(currentMonth.value).add(1, 'month')
// TODO: 重新加载该月的考勤数据
loadMonthData()
}
/**
* 加载月份数据
*/
const loadMonthData = () => {
// TODO: 调用接口获取该月的考勤数据
// const res = await getAttendanceData(currentMonth.value.format('YYYY-MM'))
// abnormalDates.value = res.abnormalDates
// dateStatusMap.value = res.dateStatusMap
}
/**
* 加载日期详情
* @param {Date} date - 日期对象
*/
const loadDateDetail = (date) => {
// TODO: 调用接口获取该日期的详细考勤信息
// const res = await getDateDetail(dayjs(date).format('YYYY-MM-DD'))
// 更新日期状态
}
/**
* 返回上一页
*/
const handleBack = () => {
uni.navigateBack()
}
onMounted(() => {
// 默认选中今天
selectedDate.value = dayjs()
// 加载当前月数据
loadMonthData()
})
</script>
<style lang="scss" scoped>
.page-container {
height: 100vh;
display: flex;
flex-direction: column;
background: #f5f7fa;
overflow: hidden;
}
.content-wrapper {
flex: 1;
display: flex;
flex-direction: column;
overflow-y: auto;
background: #fff;
}
.calendar-section {
padding: 32rpx;
background: #fff;
}
.month-header {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 32rpx;
}
.month-nav-btn {
display: flex;
align-items: center;
justify-content: center;
width: 64rpx;
height: 64rpx;
border-radius: 50%;
background: #f5f5f5;
transition: all 0.3s ease;
}
.month-nav-btn:active {
background: #e5e5e5;
transform: scale(0.95);
}
.month-text {
font-size: 36rpx;
color: #333;
font-weight: 500;
}
.weekdays-header {
display: flex;
margin-bottom: 16rpx;
}
.weekday-cell {
flex: 1;
display: flex;
align-items: center;
justify-content: center;
padding: 16rpx 0;
}
.weekday-text {
font-size: 28rpx;
color: #666;
font-weight: 500;
}
.calendar-grid {
display: flex;
flex-wrap: wrap;
}
.date-cell {
width: calc(100% / 7);
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 16rpx 0;
position: relative;
transition: all 0.3s ease;
}
.date-number {
font-size: 28rpx;
color: #333;
margin-bottom: 4rpx;
}
.date-cell.other-month .date-number {
color: #ccc;
}
.date-cell.selected {
background: #07c160;
// border-radius: 50%;
// width: 64rpx;
// height: 64rpx;
}
.date-cell.selected .date-number {
color: #fff;
font-weight: 600;
}
.abnormal-label {
font-size: 20rpx;
color: #e34d59;
line-height: 1;
}
.date-cell.selected .abnormal-label {
color: #fff;
}
.detail-section {
padding: 32rpx;
background: #fff;
border-top: 1rpx solid #f0f0f0;
margin-top: 24rpx;
}
.detail-date {
font-size: 32rpx;
color: #333;
font-weight: 500;
margin-bottom: 24rpx;
display: block;
}
.detail-content {
display: flex;
align-items: flex-start;
background: #f5f5f5;
border-radius: 16rpx;
padding: 32rpx;
position: relative;
}
.detail-line {
width: 4rpx;
height: 100%;
background: #e34d59;
border-radius: 2rpx;
margin-right: 24rpx;
position: absolute;
left: 32rpx;
top: 0;
}
.detail-status {
font-size: 28rpx;
color: #e34d59;
margin-left: 28rpx;
flex: 1;
}
.back-btn {
display: flex;
align-items: center;
justify-content: center;
width: 64rpx;
height: 64rpx;
border-radius: 50%;
background: rgba(255, 255, 255, 0.2);
transition: all 0.3s ease;
}
.back-btn:active {
background: rgba(255, 255, 255, 0.3);
transform: scale(0.95);
}
</style>