Files
delivery-uniapp/pages/user/attendance/index.vue
2026-01-24 19:07:34 +08:00

395 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>
<s-layout title="考勤排班" class="set-userinfo-wrap attendance-page">
<view class="calendar-wrap">
<wu-calendar :insert="true" :color="themeColor" :actBadgeColor="themeColor" :selected="selectedDays"
@change="calendarChange"></wu-calendar>
</view>
<!-- 今日排班 -->
<view class="card section">
<view class="section-title">今日排班</view>
<view class="shifts">
<view class="shift">
<view class="shift-name">早班</view>
<view class="shift-time">00:00-02:00</view>
</view>
<view class="shift">
<view class="shift-name">中班</view>
<view class="shift-time">14:00-23:59</view>
</view>
<view class="shift">
<view class="shift-name">晚班</view>
<view class="shift-time">14:00-23:59</view>
</view>
</view>
</view>
<!-- 考勤结果 -->
<view class="card section">
<view class="section-header">
<view class="section-title">考勤结果</view>
<view :class="['status-text', attendanceDetail.status === '异常' ? 'status-danger' : 'status-normal']">
{{attendanceDetail.status}}</view>
</view>
<!-- 示例每个班次的上下线记录 -->
<view class="attendance-block" v-for="(rec, idx) in attendanceDetail.records" :key="idx">
<view class="attendance-row">
<view class="attendance-left">
<view class="attendance-shift">{{rec.shiftName || '早班'}}</view>
<view class="attendance-logs">
<view>上线<text class="time">{{rec.upTime || '--:--'}}</text> <text class="hint"
v-if="rec.upReq">要求{{rec.upReq}}之前</text></view>
<view>下线<text class="time">{{rec.downTime || '--:--'}}</text> <text class="hint"
v-if="rec.downReq">要求{{rec.downReq}}之后</text></view>
</view>
</view>
<view class="attendance-right">
<view class="action-link" @click="openModifyModal(rec)">修改考勤</view>
<view v-if="rec.badge" class="badge" :class="rec.badgeType">{{rec.badgeText}}</view>
</view>
</view>
</view>
</view>
<!-- 出勤记录在线时长等 -->
<view class="card section">
<view class="section-title">出勤记录</view>
<view class="work-time">在线时长<text class="time">{{attendanceDetail.onlineTime}}</text></view>
</view>
<!-- 修改考勤弹窗使用 uView Plus up-popup -->
<up-popup :show="modifyModalVisible" mode="bottom" :round="16" safeAreaInsetBottom @close="closeModifyModal">
<view class="modify-content" style="padding:18px; width: 100%;">
<view class="modal-header" style="display:flex;justify-content:space-between;align-items:center;">
<text class="title">申请修改考勤</text>
<view @click="closeModifyModal"><up-icon name="close" color="#999" size="18" bold></up-icon></view>
</view>
<view class="modal-body">
<view class="form-item">
<text class="label">排班班次 </text>
<text class="muted">{{applyForm.shiftName}}</text>
</view>
<view class="form-item">
<text class="label">班次排班时间 </text>
<text class="muted">{{applyForm.shiftTime}}</text>
</view>
<view class="form-item">
<text class="label">首次上线时间</text>
<up-datetime-picker hasInput v-model="applyForm.firstUp" mode="datetime" placeholder="年/月/日 时 分" />
</view>
<view class="form-item">
<text class="label">最后下线时间</text>
<up-datetime-picker hasInput v-model="applyForm.lastDown" mode="datetime" placeholder="年/月/日 时 分" />
</view>
<view class="form-item">
<text class="label">申请缘由(必填)</text>
<up-textarea v-model="applyForm.reason" placeholder="请填写申请缘由" :autoHeight="false" />
</view>
</view>
<view class="modal-footer" style="margin-top:18px;">
<up-button type="primary" text="提交申请" @click="submitApply"></up-button>
</view>
</view>
</up-popup>
<view style="height:80rpx;"></view>
</s-layout>
</template>
<script setup>
import {
ref,
reactive,
onMounted
} from 'vue'
// 主题色(日历选中颜色)——可根据设计替换
const themeColor = '#2fcf7a'
// 被标记的日期,用于在日历上显示徽章(异常用红点)
const selectedDays = ref([])
const abnormalDates = ref([])
// 当前选择日期
const currentDate = ref('')
// 考勤详情演示数据结构
const attendanceDetail = reactive({
status: '正常',
onlineTime: '0小时0分钟',
records: []
})
// 点击日历或月份切换触发
const calendarChange = (e) => {
console.log('calendar change ->', e)
// wu-calendar 在 setEmit 中暴露的字段名可能是 fulldate 或 fullDate根据返回结构兼容处理
const date = (e && (e.fulldate || e.fullDate || e.fullDate)) || ''
currentDate.value = date
loadAttendanceForDate(date)
}
function loadAttendanceForDate(date) {
if (!date) {
attendanceDetail.status = '正常'
attendanceDetail.records = []
attendanceDetail.onlineTime = '0小时0分钟'
return
}
// 简单示例逻辑:如果在异常日期列表中,则标记为异常并展示示例记录
if (abnormalDates.value.includes(date)) {
attendanceDetail.status = '异常'
attendanceDetail.onlineTime = '3小时27分钟'
attendanceDetail.records = [{
shiftName: '早班',
upTime: '00:26',
downTime: '13:35',
upReq: '00:00',
downReq: '23:59',
badge: true,
badgeType: 'badge-danger',
badgeText: '未按时出勤'
},
{
shiftName: '中班',
upTime: '00:26',
downTime: '23:30',
upReq: '00:00',
downReq: '23:00',
badge: true,
badgeType: 'badge-danger',
badgeText: '未按时出勤'
}
]
} else {
attendanceDetail.status = '正常'
attendanceDetail.onlineTime = '8小时12分钟'
attendanceDetail.records = [{
shiftName: '早班',
upTime: '00:00',
downTime: '08:00',
badge: false
}]
}
}
function modifyAttendance(record) {
// 占位:实际应弹窗/跳转到修改考勤页面
uni.showToast({
title: '修改考勤(演示)',
icon: 'none'
})
}
// ---------- 修改考勤弹窗逻辑 ----------
const modifyModalVisible = ref(false)
const applyForm = reactive({
shiftName: '',
shiftTime: '',
firstUp: '',
lastDown: '',
reason: ''
})
function openModifyModal(record) {
// 填充表单默认值(来自 record
applyForm.shiftName = record.shiftName || '班次'
applyForm.shiftTime = (record.upTime || '--:--') + '-' + (record.downTime || '--:--')
applyForm.firstUp = record.upTime || ''
applyForm.lastDown = record.downTime || ''
applyForm.reason = ''
modifyModalVisible.value = true
}
function closeModifyModal() {
modifyModalVisible.value = false
}
function onFirstUpChange(e) {
// e.detail.value 返回类似 "2022-05-10 00:26"(平台可能略有差异)
applyForm.firstUp = e.detail.value
}
function onLastDownChange(e) {
applyForm.lastDown = e.detail.value
}
function submitApply() {
if (!applyForm.reason || !applyForm.reason.trim()) {
uni.showToast({ title: '请输入申请缘由', icon: 'none' })
return
}
// 这里应该调用接口提交申请,目前用演示提示
console.log('提交修改考勤申请:', JSON.parse(JSON.stringify(applyForm)))
uni.showToast({ title: '提交申请成功', icon: 'success' })
modifyModalVisible.value = false
}
onMounted(() => {
// 演示:标记当前月份的若干异常日期(真实项目应从接口拉取)
const now = new Date()
const y = now.getFullYear()
const m = String(now.getMonth() + 1).padStart(2, '0')
const demoAbnormals = ['04', '05', '06', '10'] // 只是示例日期日号
abnormalDates.value = demoAbnormals.map(d => `${y}-${m}-${d}`)
// 将异常日转换为 wu-calendar 可识别的 selected 数组项(显示红点)
selectedDays.value = abnormalDates.value.map(d => ({
date: d,
badge: true,
badgeColor: '#ff4d4f'
}))
// 默认加载今天的数据
const today = `${y}-${m}-${String(now.getDate()).padStart(2, '0')}`
currentDate.value = today
loadAttendanceForDate(today)
})
</script>
<style scoped>
.attendance-page .calendar-wrap {
padding: 0 12px;
}
.card {
background: #fff;
padding: 12px;
margin-top: 12px;
}
.section-title {
font-size: 14px;
color: #333;
margin-bottom: 8px;
}
.shifts {
display: flex;
justify-content: space-between;
}
.shift {
flex: 1;
display: flex;
flex-direction: column;
}
.shift+.shift {
margin-left: 12px;
}
.shift-name {
font-weight: 600;
color: #666;
}
.shift-time {
color: #333;
margin-top: 6px;
}
.section-header {
display: flex;
justify-content: space-between;
align-items: center;
}
.status-text {
font-weight: 700;
}
.status-normal {
color: #2fbd6f;
}
.status-danger {
color: #ff4d4f;
}
.attendance-block {
margin-top: 8px;
border-top: 1px solid #f2f2f2;
padding-top: 8px;
}
.attendance-row {
display: flex;
justify-content: space-between;
align-items: flex-start;
padding: 8px 0;
}
.attendance-left {
flex: 1;
}
.attendance-right {
display: flex;
flex-direction: column;
align-items: flex-end;
}
.attendance-shift {
font-weight: 600;
margin-bottom: 6px;
}
.attendance-logs .time {
color: #333;
font-weight: 600;
}
.hint {
color: #ff4d4f;
margin-left: 6px;
}
.action-link {
color: #3c9cff;
margin-bottom: 6px;
}
.badge {
padding: 4px 8px;
border-radius: 4px;
font-size: 12px;
}
.badge-danger {
background: #fff0f0;
color: #ff4d4f;
border: 1px solid #ffd6d6;
}
.work-time {
margin-top: 8px;
color: #666;
}
/* 修改考勤弹窗up-popup 内部内容) */
.modify-content .modal-header .title {
font-weight: 700;
font-size: 16px;
}
.modify-content .modal-body {
margin-top: 12px;
}
.modify-content .form-item {
margin-top: 12px;
}
.modify-content .label {
color: #333;
margin-bottom: 6px;
}
.modify-content .muted {
color: #999;
margin-left: 6px;
}
.modify-content .modal-footer {
margin-top: 18px;
}
</style>