forked from TALL/check-work
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.
496 lines
17 KiB
496 lines
17 KiB
<template>
|
|
<div class="mt-5">
|
|
<div class="d-flex flex-nowrap table-head">
|
|
<div class="table-head-item" style="width: 16%">姓名</div>
|
|
<div class="table-head-item">早</div>
|
|
<div class="table-head-item">晚</div>
|
|
<div class="table-head-item" style="width: 30%">审核人</div>
|
|
</div>
|
|
<div id="scrollTo">
|
|
<div v-if="clockInfos && clockInfos.length">
|
|
<div v-for="(list, listIndex) in clockInfos" :key="listIndex" class="teble-box">
|
|
<div class="table-time px-2">{{ list.dateTime }}</div>
|
|
<a-table :pagination="false" :show-header="false" :columns="columns" :data-source="list.recordList">
|
|
<!-- 早 -->
|
|
<template slot="morning" slot-scope="text, record, index">
|
|
<div v-if="!record.isMine && !record.isChecker">
|
|
<span v-if="record.morningStatus">{{ $moment(record.morning - 0).format('HH:mm') }}</span>
|
|
<span v-else>未打卡</span>
|
|
</div>
|
|
|
|
<div v-else>
|
|
<div v-if="record.isMine">
|
|
<span v-if="record.morningStatus === 1" class="font-bold daka">
|
|
{{ $moment(record.morning - 0).format('HH:mm') }}
|
|
<div class="dian"></div>
|
|
</span>
|
|
<span v-else-if="record.morningStatus === 2" class="line-through text-red-500 font-bold">
|
|
{{ $moment(record.morning - 0).format('HH:mm') }}
|
|
</span>
|
|
<span v-else-if="record.morningStatus === 3" class="text-blue-500 font-bold">
|
|
{{ $moment(record.morning - 0).format('HH:mm') }}
|
|
</span>
|
|
<a-button
|
|
v-else
|
|
type="primary"
|
|
size="small"
|
|
:loading="morningLoading"
|
|
@click="checkTime(listIndex, index, 0, record.id, record.memberId, record.checkerId, list.dateTime)"
|
|
>
|
|
打卡
|
|
</a-button>
|
|
</div>
|
|
|
|
<div v-if="record.isChecker">
|
|
<div v-if="record.morningStatus && !showMorningTime">
|
|
<a-popconfirm
|
|
ok-text="取消"
|
|
cancel-text="修改"
|
|
ok-type=""
|
|
@confirm="cancel"
|
|
:visible="morningVisible"
|
|
@cancel="changeStatus(record.id, 0, 'morning')"
|
|
>
|
|
<a-icon slot="icon" type="" />
|
|
<a-button slot="title" size="small" @click="rejectStatus(record.id, 2, 'morning', record.morning)">确认</a-button>
|
|
<a-button class="ml-2" slot="title" size="small" @click="rejectStatus(record.id, 1, 'morning', record.morning)">
|
|
驳回
|
|
</a-button>
|
|
<span
|
|
class="font-bold daka"
|
|
:class="record.morningStatus === 2 ? 'line-through text-red-500' : 'text-green-500'"
|
|
@click="changeVisible(record.morningStatus, 'morningVisible')"
|
|
>
|
|
<div class="dian animate-ping" v-if="record.morningStatus === 1"></div>
|
|
<div class="dian" v-if="record.morningStatus === 1"></div>
|
|
{{ $moment(record.morning - 0).format('HH:mm') }}
|
|
</span>
|
|
</a-popconfirm>
|
|
</div>
|
|
<div v-if="!record.morningStatus && !showMorningTime">未打卡</div>
|
|
<!-- 修改时间 -->
|
|
<a-time-picker
|
|
placeholder="请选择"
|
|
style="width: 100%"
|
|
v-if="showMorningTime"
|
|
format="HH:mm"
|
|
class="px-2"
|
|
@change="timeChange"
|
|
@openChange="openChange($event, 'morning')"
|
|
/>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
<!-- 晚 -->
|
|
<template slot="night" slot-scope="text, record, index">
|
|
<div v-if="!record.isMine && !record.isChecker">
|
|
<span v-if="record.nightStatus">{{ $moment(record.night - 0).format('HH:mm') }}</span>
|
|
<span v-else>未打卡</span>
|
|
</div>
|
|
|
|
<div v-else>
|
|
<div v-if="record.isMine">
|
|
<span v-if="record.nightStatus === 1" class="font-bold daka">
|
|
{{ $moment(record.night - 0).format('HH:mm') }}
|
|
<div class="dian"></div>
|
|
</span>
|
|
<span v-else-if="record.nightStatus === 2" class="line-through text-red-500 font-bold">
|
|
{{ $moment(record.night - 0).format('HH:mm') }}
|
|
</span>
|
|
<span v-else-if="record.nightStatus === 3" class="text-blue-500 font-bold">
|
|
{{ $moment(record.night - 0).format('HH:mm') }}
|
|
</span>
|
|
<a-button
|
|
v-else
|
|
type="primary"
|
|
size="small"
|
|
:loading="nightLoading"
|
|
@click="checkTime(listIndex, index, 1, record.id, record.memberId, record.checkerId, list.dateTime)"
|
|
>
|
|
打卡
|
|
</a-button>
|
|
</div>
|
|
|
|
<div v-if="record.isChecker">
|
|
<div v-if="record.nightStatus && !showNightTime">
|
|
<a-popconfirm
|
|
ok-text="取消"
|
|
cancel-text="修改"
|
|
ok-type=""
|
|
@confirm="cancel"
|
|
:visible="visible"
|
|
@cancel="changeStatus(record.id, 0, 'night')"
|
|
>
|
|
<a-icon slot="icon" type="" />
|
|
<a-button slot="title" size="small" @click="rejectStatus(record.id, 2, 'night', record.night)">确认</a-button>
|
|
<a-button class="ml-2" slot="title" size="small" @click="rejectStatus(record.id, 1, 'night', record.night)">
|
|
驳回
|
|
</a-button>
|
|
<span
|
|
class="font-bold daka"
|
|
:class="record.nightStatus === 2 ? 'line-through text-red-500' : 'text-green-500'"
|
|
@click="changeVisible(record.nightStatus, 'visible')"
|
|
>
|
|
<div class="dian animate-ping" v-if="record.nightStatus === 1"></div>
|
|
<div class="dian" v-if="record.nightStatus === 1"></div>
|
|
{{ $moment(record.night - 0).format('HH:mm') }}
|
|
</span>
|
|
</a-popconfirm>
|
|
</div>
|
|
<div v-if="!record.nightStatus && !showNightTime">未打卡</div>
|
|
<!-- 修改时间 -->
|
|
<a-time-picker
|
|
placeholder="请选择"
|
|
style="width: 100%"
|
|
v-if="showNightTime"
|
|
format="HH:mm"
|
|
class="px-2"
|
|
@change="timeChange"
|
|
@openChange="openChange($event, 'night')"
|
|
/>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
<!-- 审核人 -->
|
|
<template slot="checkerName" slot-scope="text, record">
|
|
<div class="px-2">
|
|
<div v-if="!record.isMine || (record.isMine && record.morningStatus && record.nightStatus)">
|
|
{{ record.checkerName || members[0].name }}
|
|
</div>
|
|
<a-select
|
|
v-else
|
|
:default-value="record.lastCheckerId ? record.lastCheckerId : record.checkerId ? record.checkerId : members[0].memberId"
|
|
style="width: 100%"
|
|
@change="chooseChecker"
|
|
>
|
|
<a-select-option :value="member.memberId" v-for="member in members" :key="member.memberId">
|
|
{{ member.name }}
|
|
</a-select-option>
|
|
</a-select>
|
|
</div>
|
|
</template>
|
|
</a-table>
|
|
</div>
|
|
<div class="text-gray-400 text-xs m-3 flex flex-col justify-start">
|
|
<div class="flex align-center mb-2"><a-icon type="exclamation-circle" class="mr-1" />静态小红点:审核人未审核你的打卡状态</div>
|
|
<div class="mb-10 ml-4">动态小红点:某成员选择你为打卡人,需要你审核</div>
|
|
</div>
|
|
</div>
|
|
<a-empty class="mt-8 mb-8" description="暂无打卡信息" v-else />
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<script>
|
|
import { mapState, mapMutations } from 'vuex';
|
|
import { clockQuery, clockPunch, clockAudit } from '@/config/api';
|
|
|
|
const columns = [
|
|
{ title: '姓名', dataIndex: 'memberName', key: 'memberName', align: 'center', width: '16%' },
|
|
{ title: '早', dataIndex: 'morning', key: 'morning', scopedSlots: { customRender: 'morning' }, align: 'center', width: '27%' },
|
|
{ title: '晚', dataIndex: 'night', key: 'night', scopedSlots: { customRender: 'night' }, align: 'center', width: '27%' },
|
|
{
|
|
title: '审核人',
|
|
dataIndex: 'checkerName',
|
|
key: 'checkerName',
|
|
scopedSlots: { customRender: 'checkerName' },
|
|
align: 'center',
|
|
width: '30%',
|
|
},
|
|
];
|
|
|
|
export default {
|
|
data() {
|
|
return {
|
|
columns,
|
|
clockInfos: [],
|
|
options: null,
|
|
checkerId: undefined,
|
|
morningVisible: false,
|
|
visible: false,
|
|
placement: 'left',
|
|
timer: null,
|
|
showNightTime: false,
|
|
showMorningTime: false,
|
|
chooseTime: '',
|
|
auditOptions: null,
|
|
morningLoading: false,
|
|
nightLoading: false,
|
|
};
|
|
},
|
|
|
|
computed: mapState('home', ['projectId', 'members', 'startTime', 'endTime', 'memberIdList']),
|
|
|
|
mounted() {
|
|
this.timer = setInterval(async () => {
|
|
if (this.projectId) {
|
|
clearInterval(this.timer);
|
|
await this.setParams();
|
|
|
|
// 自动移动到目标位置
|
|
// document.querySelector('#scrollTo').scrollIntoView({
|
|
// behavior: 'smooth', // 平滑过渡
|
|
// block: 'start', // 上边框与视窗顶部平齐。默认值
|
|
// });
|
|
}
|
|
}, 300);
|
|
|
|
const time = this.$moment(Date.now()).format('YYYY-MM-DD');
|
|
if (!this.startTime) {
|
|
this.setStartTime(this.$moment(`${time} 00:00`).format('x') - 0);
|
|
}
|
|
if (!this.endTime) {
|
|
this.setEndTime(this.$moment(`${time} 23:59`).format('x') - 0);
|
|
}
|
|
},
|
|
|
|
methods: {
|
|
...mapMutations('home', ['setStartTime', 'setEndTime', 'setMemberIdList']),
|
|
|
|
async setParams() {
|
|
const { projectId, startTime, endTime, memberIdList } = this;
|
|
const params = { param: { projectId, memberIdList, startTime, endTime } };
|
|
await this.getClockQuery(params);
|
|
},
|
|
|
|
/**
|
|
* 查询考勤信息
|
|
* @param {string} projectId
|
|
* @param {array} memberIdList
|
|
* @param {string} startTime
|
|
* @param {string} endTime
|
|
*/
|
|
async getClockQuery(params) {
|
|
try {
|
|
const res = await clockQuery(params);
|
|
const { code, msg, data } = res.data;
|
|
if (code === 200) {
|
|
this.clockInfos = data;
|
|
} else {
|
|
this.$message.error(msg || '获取失败');
|
|
throw msg;
|
|
}
|
|
} catch (error) {
|
|
throw error || '获取失败';
|
|
}
|
|
},
|
|
|
|
// 选择审核人
|
|
chooseChecker(value) {
|
|
this.checkerId = value;
|
|
},
|
|
|
|
// 打卡
|
|
checkTime(listIndex, index, clockType, id, memberId, checkerId, clockTime) {
|
|
const time = Date.now();
|
|
const selectTime = this.$moment(time).format('HH:mm');
|
|
if (clockType === 0) {
|
|
this.clockInfos[listIndex].recordList[index].morning = selectTime;
|
|
} else {
|
|
this.clockInfos[listIndex].recordList[index].night = selectTime;
|
|
}
|
|
const dateTime = this.$moment(`${clockTime} ${selectTime}`).format('x') - 0;
|
|
const params = { param: { checkerId: this.checkerId || checkerId || this.members[0].memberId, clockType, dateTime, id, memberId } };
|
|
this.handleClockPunch(params);
|
|
},
|
|
|
|
/**
|
|
* 打卡
|
|
* @param {string} checkerId 审核员id
|
|
* @param {array} clockType 打卡类型(0-早,1-晚)
|
|
* @param {string} dateTime 打卡时间
|
|
* @param {string} id 记录id(没有则不传)
|
|
* @param {string} memberId 考勤信息中的成员id
|
|
*/
|
|
async handleClockPunch(params) {
|
|
try {
|
|
if (params.param.clockType === 0) {
|
|
this.morningLoading = true;
|
|
} else {
|
|
this.nightLoading = true;
|
|
}
|
|
const res = await clockPunch(params);
|
|
const { code, msg } = res.data;
|
|
if (code === 200) {
|
|
this.$message.success('打卡成功');
|
|
const options = { startTime: this.startTime, endTime: this.endTime, memberIdList: this.memberIdList };
|
|
this.setParams(options);
|
|
} else {
|
|
this.$message.error(msg || '打卡失败');
|
|
throw msg;
|
|
}
|
|
if (params.param.clockType === 0) {
|
|
this.morningLoading = false;
|
|
} else {
|
|
this.nightLoading = false;
|
|
}
|
|
} catch (error) {
|
|
if (params.param.clockType === 0) {
|
|
this.morningLoading = false;
|
|
} else {
|
|
this.nightLoading = false;
|
|
}
|
|
throw error || '打卡失败';
|
|
}
|
|
},
|
|
|
|
changeVisible(status, type) {
|
|
if (status !== 2) {
|
|
this[type] = true;
|
|
}
|
|
},
|
|
|
|
// 修改
|
|
changeStatus(id, type, timeType) {
|
|
if (timeType === 'morning') {
|
|
this.morningVisible = false;
|
|
this.showMorningTime = true;
|
|
} else {
|
|
this.visible = false;
|
|
this.showNightTime = true;
|
|
}
|
|
this.auditOptions = { id, type };
|
|
},
|
|
// 选择修改时间
|
|
timeChange(time) {
|
|
this.chooseTime = this.$moment(time).format('x');
|
|
},
|
|
async openChange(open, timeType) {
|
|
if (!open && this.chooseTime) {
|
|
this.auditOptions[timeType] = this.chooseTime;
|
|
this.auditOptions.projectId = this.projectId;
|
|
const params = { param: this.auditOptions };
|
|
await this.handleClockAudit(params);
|
|
}
|
|
if (!open && !this.chooseTime) {
|
|
this.showNightTime = false;
|
|
this.showMorningTime = false;
|
|
}
|
|
},
|
|
|
|
// 驳回
|
|
async rejectStatus(id, type, timeType, time) {
|
|
if (timeType === 'morning') {
|
|
this.morningVisible = false;
|
|
} else {
|
|
this.visible = false;
|
|
}
|
|
const params = { param: { id, type, [timeType]: time, projectId: this.projectId } };
|
|
await this.handleClockAudit(params);
|
|
},
|
|
|
|
// 取消
|
|
cancel() {
|
|
this.visible = false;
|
|
this.morningVisible = false;
|
|
},
|
|
|
|
/**
|
|
* 审核打卡
|
|
* @param {string} id 打卡记录id
|
|
* @param {string} morning 早打卡时间
|
|
* @param {string} night 晚打卡时间
|
|
* @param {string} projectId 项目id
|
|
* @param {string} type 审批类型(0-修改,1-驳回,2-通过)
|
|
*/
|
|
async handleClockAudit(params) {
|
|
try {
|
|
const res = await clockAudit(params);
|
|
const { code, msg } = res.data;
|
|
if (code === 200) {
|
|
this.$message.success(params.param.type === 0 ? '修改成功' : params.param.type === 1 ? '驳回成功' : '审核通过');
|
|
this.showNightTime = false;
|
|
this.showMorningTime = false;
|
|
this.auditOptions = null;
|
|
const options = { startTime: this.startTime, endTime: this.endTime, memberIdList: this.memberIdList };
|
|
this.setParams(options);
|
|
} else {
|
|
this.$message.error(msg || '审核失败');
|
|
throw msg;
|
|
}
|
|
} catch (error) {
|
|
throw error || '审核失败';
|
|
}
|
|
},
|
|
},
|
|
};
|
|
</script>
|
|
|
|
<style scoped>
|
|
.table-head {
|
|
border-bottom: 1px solid #e8e8e8;
|
|
background: #fafafa;
|
|
height: 36px;
|
|
line-height: 36px;
|
|
font-weight: bold;
|
|
}
|
|
|
|
.table-time {
|
|
height: 30px;
|
|
line-height: 30px;
|
|
border-bottom: 1px solid #e8e8e8;
|
|
background: #fafafa;
|
|
}
|
|
|
|
.table-head-item {
|
|
width: 27%;
|
|
text-align: center;
|
|
}
|
|
|
|
img {
|
|
width: 22px;
|
|
max-width: auto !important;
|
|
}
|
|
|
|
.teble-box >>> .ant-table-row-cell-break-word {
|
|
padding: 4px 0 !important;
|
|
}
|
|
|
|
.teble-box >>> .ant-table-row {
|
|
height: 41px !important;
|
|
}
|
|
|
|
.actions-box >>> .ant-drawer-content-wrapper {
|
|
height: auto !important;
|
|
}
|
|
|
|
.actions-box >>> .ant-drawer-wrapper-body {
|
|
background: #f5f5f5;
|
|
}
|
|
|
|
.actions-box >>> .ant-drawer-body {
|
|
padding: 0 !important;
|
|
}
|
|
|
|
.actions-box >>> .ant-drawer-body p {
|
|
height: 36px;
|
|
line-height: 36px;
|
|
background: #fcfcfc;
|
|
text-align: center;
|
|
}
|
|
|
|
.daka {
|
|
position: relative;
|
|
}
|
|
.dian {
|
|
position: absolute;
|
|
width: 6px;
|
|
height: 6px;
|
|
background-color: red;
|
|
border-radius: 50%;
|
|
top: 2px;
|
|
right: -10px;
|
|
}
|
|
</style>
|
|
|
|
<style>
|
|
.ant-popover-inner {
|
|
width: 256px;
|
|
}
|
|
|
|
.ant-popover-message {
|
|
position: absolute !important;
|
|
left: 2px;
|
|
top: 8px;
|
|
}
|
|
</style>
|
|
|