feat: 新增考勤排班管理页面
This commit is contained in:
@@ -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();
|
||||
}
|
||||
|
||||
395
pages/user/attendance/index.vue
Normal file
395
pages/user/attendance/index.vue
Normal 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>
|
||||
Reference in New Issue
Block a user