feat: 新增考勤排班管理页面

This commit is contained in:
admin
2026-01-24 19:07:34 +08:00
parent 8c1224999d
commit c89af4c6a3
99 changed files with 13547 additions and 14 deletions

View File

@@ -24,7 +24,6 @@
<text class="stats-title">今日预计收入</text>
<text class="stats-value"> {{ formatMoney(todayIncome) }} </text>
<view class="stats-link" @tap="sheep.$router.go('/pages/user/account/index')">
<!-- @click="openAccount" -->
我的账户 <uni-icons type="right" size="13"></uni-icons>
</view>
</view>
@@ -40,7 +39,7 @@
<!-- shortcuts -->
<view class="shortcuts">
<view class="shortcut" @click="openAttendance">
<view class="shortcut" @tap="sheep.$router.go('/pages/user/attendance/index')">
<image class="shortcut-icon" src="/static/img/order1.png" mode="aspectFit" />
<text class="shortcut-text">考勤排班</text>
</view>
@@ -219,18 +218,6 @@
uni.navigateBack();
}
function openAccount() {
uni.navigateTo({
url: '/pages/public/webview?type=account'
});
}
function openAttendance() {
uni.navigateTo({
url: '/pages/public/faq'
});
}
function login() {
showAuthModal();
}

View File

@@ -0,0 +1,395 @@
<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>