You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
413 lines
13 KiB
413 lines
13 KiB
<template>
|
|
<view>
|
|
<!-- 标题 + 筛选 -->
|
|
<view class="check-work-title px-3 fixed top-0 z-10 w-full flex justify-between items-center bg-white">
|
|
<text>考勤管理</text>
|
|
|
|
<view>
|
|
<u-button class="mr-3" size="mini" type="primary" @click="isShow = !isShow">过滤</u-button>
|
|
<u-button size="mini" type="primary" @click="isShow = !isShow">导出</u-button>
|
|
</view>
|
|
</view>
|
|
|
|
<!-- 列表 -->
|
|
<view class="check-work-list">
|
|
<u-table class="table-head">
|
|
<u-tr>
|
|
<u-th>姓名</u-th>
|
|
<u-th>早</u-th>
|
|
<u-th>晚</u-th>
|
|
<u-th width="30%">审核人</u-th>
|
|
</u-tr>
|
|
</u-table>
|
|
|
|
<view v-if="clockInfos.length" v-for="(list, listIndex) in clockInfos" :key="listIndex">
|
|
<view class="table-time px-2">{{ list.dateTime }}</view>
|
|
|
|
<u-table class="table-body">
|
|
<u-tr v-if="list.recordList.length" v-for="(item, index) in list.recordList" :key="index">
|
|
<u-td>{{ item.memberName }}</u-td>
|
|
|
|
<u-td>
|
|
<template v-if="!item.morningStatus">
|
|
<view v-if="item.isMine === 1">
|
|
<u-button class="m-0" size="mini" type="primary" @click="showTimeSelect(0, item, list.dateTime)">早打卡</u-button>
|
|
</view>
|
|
|
|
<template v-else>
|
|
<view>未打卡</view>
|
|
</template>
|
|
</template>
|
|
|
|
<view v-else class="flex justify-center">
|
|
<view :class="{'relative': item.morningStatus === 1}">
|
|
<text :class="{'font-semibold': item.isMine === 1 || item.isChecker === 1,
|
|
'text-green-500': item.isChecker === 1 && (item.morningStatus === 1 || item.morningStatus === 3),
|
|
'line-through': item.morningStatus === 2,
|
|
'text-gray-400': item.morningStatus === 2 && (item.isMine === 1 || item.isChecker === 1),
|
|
'text-cyan': item.morningStatus === 3 && item.isMine === 1}"
|
|
@click="showActionPanel(item.morningStatus, 'morning', item.morning, item)"
|
|
>
|
|
{{ dayjs(+item.morning).format("HH:mm") }}
|
|
</text>
|
|
|
|
<!-- 小红点 -->
|
|
<template v-if="item.morningStatus === 1">
|
|
<u-badge v-if="item.isMine === 1" :is-dot="true"
|
|
type="error" size="mini" style="top: -2px; right: -10px;"></u-badge>
|
|
|
|
<!-- 小红点 -->
|
|
<template v-else-if="item.isChecker === 1">
|
|
<u-badge class="animate-ping" :is-dot="true" type="error" size="mini" style="top: -2px; right: -10px;"></u-badge>
|
|
<u-badge :is-dot="true" type="error" size="mini" style="top: -2px; right: -10px;"></u-badge>
|
|
</template>
|
|
|
|
<!-- 小红点 -->
|
|
<u-badge v-else :is-dot="true"
|
|
type="warning" size="mini" style="top: -2px; right: -10px;"></u-badge>
|
|
</template>
|
|
</view>
|
|
</view>
|
|
</u-td>
|
|
|
|
<u-td>
|
|
<template v-if="!item.nightStatus">
|
|
<view v-if="item.isMine === 1">
|
|
<u-button class="m-0" size="mini" type="primary" @click="showTimeSelect(1, item, list.dateTime)">晚打卡</u-button>
|
|
</view>
|
|
|
|
<template v-else>
|
|
<view>未打卡</view>
|
|
</template>
|
|
</template>
|
|
|
|
<view v-else class="flex justify-center">
|
|
<view :class="{'relative': item.nightStatus === 1}">
|
|
<text :class="{'font-semibold': item.isMine === 1 || item.isChecker === 1,
|
|
'text-green-500': item.isChecker === 1 && (item.nightStatus === 1 || item.nightStatus === 3),
|
|
'line-through': item.nightStatus === 2,
|
|
'text-gray-400': item.nightStatus === 2 && (item.isMine === 1 || item.isChecker === 1),
|
|
'text-cyan': item.nightStatus === 3 && item.isMine === 1}"
|
|
@click="showActionPanel(item.nightStatus, 'night', item.night, item)"
|
|
>
|
|
{{ dayjs(+item.night).format("HH:mm") }}
|
|
</text>
|
|
|
|
<!-- 小红点 -->
|
|
<template v-if="item.nightStatus === 1">
|
|
<u-badge v-if="item.isMine === 1" :is-dot="true"
|
|
type="error" size="mini" style="top: -2px; right: -10px;"></u-badge>
|
|
|
|
<!-- 小红点 -->
|
|
<template v-else-if="item.isChecker === 1">
|
|
<u-badge class="animate-ping" :is-dot="true" type="error" size="mini" style="top: -2px; right: -10px;"></u-badge>
|
|
<u-badge :is-dot="true" type="error" size="mini" style="top: -2px; right: -10px;"></u-badge>
|
|
</template>
|
|
|
|
<!-- 小红点 -->
|
|
<u-badge v-else :is-dot="true"
|
|
type="warning" size="mini" style="top: -2px; right: -10px;"></u-badge>
|
|
</template>
|
|
</view>
|
|
</view>
|
|
</u-td>
|
|
|
|
<u-td width="30%">
|
|
<view class="flex justify-center">
|
|
<view v-if="item.isMine === 1 && (!item.morningStatus && !item.nightStatus)" class="px-2 py-1 flex items-center border border-solid rounded border-gray-200" @click="show = true">
|
|
<text class="mr-3 whitespace-nowrap">{{ item.lastCheckerName ? item.lastCheckerName : item.checkerName ? item.checkerName : checkerName }}</text>
|
|
<u-icon size="24" name="arrow-down"></u-icon>
|
|
</view>
|
|
|
|
<view v-else>{{ item.lastCheckerName ? item.lastCheckerName : item.checkerName ? item.checkerName : '周勇' }}</view>
|
|
</view>
|
|
</u-td>
|
|
</u-tr>
|
|
</u-table>
|
|
</view>
|
|
</view>
|
|
|
|
<!-- 审核人列表 -->
|
|
<u-select v-model="show" :list="list" @confirm="confirm"></u-select>
|
|
|
|
<!-- 打卡弹框 -->
|
|
<u-modal v-model="punchModal" title="打卡" :showCancelButton="true" @confirm="confirmPunch">
|
|
<view class="px-4 text-sm">
|
|
<!-- 文字内容 -->
|
|
<view class="flex justify-between items-center">
|
|
<view>打卡原因</view>
|
|
<input class="text-sm text-right" v-model="remark" placeholder="请输入打卡原因" />
|
|
</view>
|
|
<!-- 时间选择 -->
|
|
<view class="my-4 flex justify-between items-center">
|
|
<view>打卡时间</view>
|
|
<view @click="showChangeTime">{{ dayjs(+selectedTime).format("HH:mm") }}</view>
|
|
</view>
|
|
</view>
|
|
</u-modal>
|
|
|
|
<!-- 审核操作按钮列表 -->
|
|
<u-action-sheet :list="actions" v-model="actionShow" @click="clickAction"></u-action-sheet>
|
|
|
|
<!-- 时间选择框 -->
|
|
<u-picker mode="time" v-model="showModal" :params="params" @confirm="changeTime"></u-picker>
|
|
|
|
<!-- 筛选框 -->
|
|
<SearchPopup v-if="isShow" :members="list" :show="isShow" @closePopup="closePopup" @getClockQuery="getClockQuery" @clockExport="clockExport"></SearchPopup>
|
|
</view>
|
|
</template>
|
|
|
|
<script setup>
|
|
import { computed, onMounted, ref } from 'vue';
|
|
import { useStore } from 'vuex';
|
|
import dayjs from 'dayjs';
|
|
import SearchPopup from '@/components/SearchPopup/SearchPopup.vue';
|
|
|
|
const props = defineProps({
|
|
clockParams: {
|
|
type: Object,
|
|
default: () => {}
|
|
}
|
|
})
|
|
|
|
const store = useStore();
|
|
const projectId = uni.$storage.getStorageSync('projectId');
|
|
const roleId = uni.$storage.getStorageSync('roleId');
|
|
const checkers = uni.$storage.getStorageSync('checkers'); // 检查人列表
|
|
const url = uni.$storage.getStorageSync('domain');
|
|
|
|
let clockInfos = ref([]); // 打卡列表
|
|
const show = ref(false); // 是否展示审核人列表
|
|
let list = ref([]); // 审核人列表
|
|
let actionShow = ref(false); // 审核操作按钮列表
|
|
const actions = ref([{ text: '修改' }, { text: '驳回' }, { text: '确认' }]); // 操作按钮列表
|
|
let examineParams = ref({}); // 审核参数
|
|
let punchModal = ref(false); // 打卡模态框
|
|
let remark = ref(null); // 打卡原因
|
|
let showModal = ref(false); // 修改时间模态框
|
|
const params = ref({year: false, month: false, day: false, hour: true, minute: true, timestamp: true})
|
|
let checkerId = ref(null);
|
|
let checkerName = ref(null);
|
|
let isShow = ref(false); // 是否打开筛选框
|
|
let timeType = ref(null); // 时间类型 0 早打卡 1 晚打卡
|
|
let actionType = ref(0); // 操作类型:0 打卡 1 审核
|
|
let currRecord = ref({}); // 当前点击的打卡记录
|
|
let selectedDate = ref(null); // 选中的日期
|
|
let selectedTime = ref(null); // 选中的时间
|
|
let startTime = dayjs().startOf('day').valueOf();
|
|
let endTime = dayjs().endOf('day').valueOf();
|
|
let memberIdList = ref([]);
|
|
|
|
onMounted(() => {
|
|
selectedTime.value = dayjs().valueOf();
|
|
list.value = [];
|
|
|
|
let checker_arr = JSON.parse(checkers);
|
|
|
|
checker_arr.forEach(item => {
|
|
list.value.push({
|
|
value: item.memberId,
|
|
label: item.name
|
|
})
|
|
|
|
if (item.name === '周勇') {
|
|
checkerId.value = item.memberId;
|
|
checkerName.value = item.name;
|
|
}
|
|
})
|
|
|
|
getClockQuery(props.clockParams);
|
|
})
|
|
|
|
/**
|
|
* 批量查询打卡信息
|
|
*/
|
|
async function getClockQuery(data) {
|
|
closePopup();
|
|
|
|
startTime = data ? dayjs(data.startTime).startOf('day').valueOf() : startTime;
|
|
endTime = data ? dayjs(data.endTime).endOf('day').valueOf() : endTime;
|
|
memberIdList.value = data ? data.memberIdList : memberIdList.value;
|
|
|
|
try {
|
|
const params = { projectId, roleId, memberIdList: memberIdList.value, startTime, endTime }
|
|
const data = await uni.$u.api.clockQuery(params, url);
|
|
|
|
data.forEach(list => {
|
|
let arr = [];
|
|
list.recordList.forEach(item => {
|
|
if (item.isMine === 1) {
|
|
arr.push(item);
|
|
}
|
|
})
|
|
|
|
list.recordList.forEach(item => {
|
|
if (item.isChecker === 1) {
|
|
arr.push(item);
|
|
}
|
|
})
|
|
|
|
list.recordList.forEach(item => {
|
|
if (!item.isMine && !item.isChecker) {
|
|
arr.push(item);
|
|
}
|
|
})
|
|
|
|
list.recordList = arr;
|
|
})
|
|
|
|
clockInfos.value = data;
|
|
} catch (error) {
|
|
console.log('error: ', error);
|
|
}
|
|
}
|
|
|
|
// 选择审核人结果
|
|
function confirm(e) {
|
|
checkerId.value = e[0].value;
|
|
checkerName.value = e[0].label;
|
|
}
|
|
|
|
// 打卡选择时间
|
|
function showTimeSelect(clockType, record, time) {
|
|
punchModal.value = true; // 显示打卡模态框
|
|
selectedDate.value = time; // 选中的日期
|
|
selectedTime.value = dayjs(time + " " + dayjs().format('HH:mm')).valueOf();
|
|
actionType.value = 0; // 打卡还是修改时间 0 打卡 1 审核人修改时间
|
|
timeType.value = clockType; // 0 早打卡 1 晚打卡
|
|
currRecord.value = record; // 当前打卡记录
|
|
}
|
|
|
|
// 打卡时间选择弹框
|
|
function showChangeTime() {
|
|
showModal.value = true;
|
|
}
|
|
|
|
// 打卡
|
|
async function punch() {
|
|
checkerId.value = currRecord.value.lastCheckerId ? currRecord.value.lastCheckerId : currRecord.value.checkerId ? currRecord.value.checkerId : checkerId.value;
|
|
|
|
try {
|
|
const params = {
|
|
checkerId: checkerId.value,
|
|
id: !currRecord.value.id ? null : currRecord.value.id,
|
|
memberId: currRecord.value.memberId,
|
|
dateTime: selectedTime.value,
|
|
clockType: timeType.value,
|
|
remark: remark.value
|
|
}
|
|
|
|
const data = await uni.$u.api.clockPunch(params, url);
|
|
|
|
getClockQuery(); // 打卡成功重新加载打卡信息
|
|
} catch (error) {
|
|
console.log('error: ', error);
|
|
}
|
|
}
|
|
|
|
// 点击待审核时间打开操作面板
|
|
async function showActionPanel(status, type, time, data) {
|
|
if (status !== 1 || data.isChecker !== 1) return;
|
|
actionShow.value = true;
|
|
timeType.value = type;
|
|
examineParams.value = { id: data.id, [type]: time, projectId, type: 0 };
|
|
}
|
|
|
|
// 选择的操作下班 0 修改 1 驳回 2 确认
|
|
function clickAction(index) {
|
|
if (index === 1) { // 驳回
|
|
examineParams.value.type = 1;
|
|
} else if (index === 2) { // 确认
|
|
examineParams.value.type = 2;
|
|
}
|
|
|
|
if (index === 0) {
|
|
// 修改
|
|
showModal.value = true;
|
|
actionType.value = 1;
|
|
} else {
|
|
handleClockAudit();
|
|
}
|
|
}
|
|
|
|
// 时间改变确认事件
|
|
function changeTime(e) {
|
|
const date = selectedDate.value + " " + e.hour + ":" + e.minute;
|
|
selectedTime.value = dayjs(date).valueOf();
|
|
examineParams.value[timeType.value] = dayjs(date).valueOf();
|
|
|
|
if (actionType.value === 1) {
|
|
handleClockAudit();
|
|
}
|
|
}
|
|
|
|
// 打卡弹框回调函数
|
|
function confirmPunch() {
|
|
punch();
|
|
}
|
|
|
|
// 审核
|
|
async function handleClockAudit() {
|
|
try {
|
|
const data = await uni.$u.api.clockAudit(examineParams.value, url);
|
|
|
|
getClockQuery();
|
|
} catch (error) {
|
|
console.log('error: ', error);
|
|
}
|
|
}
|
|
|
|
// 关闭过滤弹框
|
|
function closePopup(data) {
|
|
isShow.value = data;
|
|
}
|
|
|
|
// 导出
|
|
function clockExport() {
|
|
closePopup();
|
|
}
|
|
</script>
|
|
|
|
<style lang="scss" scoped>
|
|
.whitespace-nowrap {
|
|
white-space: nowrap;
|
|
}
|
|
|
|
.check-work-title {
|
|
height: 50px;
|
|
border-bottom: 1px solid #e8e8e8;
|
|
}
|
|
|
|
.check-work-list {
|
|
padding-top: 50px;
|
|
}
|
|
|
|
.u-table {
|
|
border-left: unset !important;
|
|
}
|
|
|
|
.table-head {
|
|
border-top: unset !important;
|
|
}
|
|
|
|
.u-th {
|
|
border-right: unset !important;
|
|
height: 36px;
|
|
background: #fafafa;
|
|
}
|
|
|
|
.u-td {
|
|
border-right: unset !important;
|
|
height: 40px;
|
|
}
|
|
|
|
.table-time {
|
|
height: 30px;
|
|
line-height: 30px;
|
|
background: #fafafa;
|
|
}
|
|
|
|
.text-cyan {
|
|
color: #3B83F6;
|
|
}
|
|
</style>
|
|
|