395 lines
10 KiB
Vue
395 lines
10 KiB
Vue
<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> |