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.

549 lines
19 KiB

4 years ago
<template>
<div class="mt-5">
<div class="d-flex flex-nowrap table-head">
<div class="table-head-item" style="width: 16%">姓名</div>
4 years ago
<div class="table-head-item"></div>
<div class="table-head-item"></div>
<div class="table-head-item" style="width: 30%">审核人</div>
4 years ago
</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" class="daka" :class="record.morningStatus === 2 ? 'line-through' : ''"
>{{ $moment(record.morning - 0).format('HH:mm') }}
<div class="dian dian-yellow" v-if="record.morningStatus === 1"></div
></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-gray-400 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>
<span v-else-if="today !== list.dateTime">未打卡</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 && !record.showMorningTime">
<a-popconfirm
ok-text="取消"
cancel-text="修改"
ok-type=""
@confirm="cancel(record, 'morningVisible')"
:visible="record.morningVisible"
@cancel="changeStatus(record.id, 0, record, 'morningVisible', 'showMorningTime', list.dateTime)"
>
<a-icon slot="icon" type="" />
<a-button
class="okBtn"
slot="title"
size="small"
@click="rejectStatus(record.id, 2, 'morning', record.morning, record, 'morningVisible')"
4 years ago
>确认</a-button
>
<a-button
type="danger"
class="ml-2"
slot="title"
size="small"
@click="rejectStatus(record.id, 1, 'morning', record.morning, record, 'morningVisible')"
4 years ago
>
驳回
</a-button>
<span
class="font-bold daka"
:class="record.morningStatus === 2 ? 'line-through text-gray-400' : 'text-green-500'"
@click="changeVisible(index, 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 && !record.showMorningTime">未打卡</div>
<!-- 修改时间 -->
<a-time-picker
placeholder="请选择"
style="width: 100%"
v-if="record.showMorningTime"
format="HH:mm"
class="px-2"
@change="timeChange"
@openChange="openChange($event, record, 'showMorningTime', 'morning')"
/>
</div>
</div>
</template>
<!-- -->
<template slot="night" slot-scope="text, record, index">
<div v-if="!record.isMine && !record.isChecker">
<span v-if="record.nightStatus" class="daka" :class="record.nightStatus === 2 ? 'line-through' : ''"
>{{ $moment(record.night - 0).format('HH:mm') }}
<div class="dian dian-yellow" v-if="record.nightStatus === 1"></div
></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-gray-400 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>
<span v-else-if="today !== list.dateTime">未打卡</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 && !record.showNightTime">
<a-popconfirm
ok-text="取消"
cancel-text="修改"
ok-type=""
@confirm="cancel(record, 'nightVisible')"
:visible="record.nightVisible"
@cancel="changeStatus(record.id, 0, record, 'nightVisible', 'showNightTime', list.dateTime)"
>
<a-icon slot="icon" type="" />
<a-button
type="primary"
slot="title"
size="small"
@click="rejectStatus(record.id, 2, 'night', record.night, record, 'nightVisible')"
4 years ago
>确认</a-button
>
<a-button
type="danger"
class="ml-2"
slot="title"
size="small"
@click="rejectStatus(record.id, 1, 'night', record.night, record, 'nightVisible')"
4 years ago
>
驳回
</a-button>
<span
class="font-bold daka"
:class="record.nightStatus === 2 ? 'line-through text-gray-400' : 'text-green-500'"
@click="changeVisible(index, record.nightStatus, 'nightVisible')"
>
<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 && !record.showNightTime">未打卡</div>
<!-- 修改时间 -->
<a-time-picker
placeholder="请选择"
style="width: 100%"
v-if="record.showNightTime"
format="HH:mm"
class="px-2"
@change="timeChange"
@openChange="openChange($event, record, 'showNightTime', '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) || today !== list.dateTime">
{{ record.checkerName || checkers[0].name }}
</div>
<a-select
v-else
:default-value="record.lastCheckerId ? record.lastCheckerId : record.checkerId ? record.checkerId : checkers[0].memberId"
style="width: 100%"
@change="chooseChecker"
>
<a-select-option :value="member.memberId" v-for="member in checkers" :key="member.memberId">
{{ member.name }}
</a-select-option>
</a-select>
</div>
</template>
</a-table>
</div>
4 years ago
</div>
<a-empty class="mt-8 mb-8" description="暂无打卡信息" v-else />
4 years ago
</div>
</div>
</template>
<script>
import { mapState, mapMutations } from 'vuex';
import { clockQuery, clockPunch, clockAudit } from '@/config/api';
4 years ago
4 years ago
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%' },
4 years ago
{
title: '审核人',
dataIndex: 'checkerName',
key: 'checkerName',
scopedSlots: { customRender: 'checkerName' },
align: 'center',
width: '30%',
4 years ago
},
];
export default {
data() {
return {
columns,
4 years ago
clockInfos: [],
options: null,
checkerId: undefined,
placement: 'left',
timer: null,
chooseTime: '',
auditOptions: null,
morningLoading: false,
nightLoading: false,
today: this.$moment(new Date()).format('YYYY-MM-DD'),
4 years ago
};
},
computed: mapState('home', ['projectId', 'members', 'startTime', 'endTime', 'memberIdList', 'roleId', 'checkers']),
4 years ago
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);
}
4 years ago
},
4 years ago
methods: {
...mapMutations('home', ['setStartTime', 'setEndTime', 'setMemberIdList']),
async setParams() {
4 years ago
const { projectId, startTime, endTime, memberIdList, roleId } = this;
const params = { param: { projectId, memberIdList, startTime, endTime, roleId } };
4 years ago
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) {
data.forEach(item => {
item.recordList.forEach(clcok => {
clcok.morningVisible = false;
clcok.nightVisible = false;
clcok.showNightTime = false;
clcok.showMorningTime = false;
});
});
this.clockInfos = [...data];
4 years ago
} else {
this.$message.error(msg || '获取失败');
throw msg;
}
} catch (error) {
throw error || '获取失败';
}
},
// 选择审核人
chooseChecker(value) {
this.checkerId = value;
},
// 打卡
checkTime(listIndex, index, clockType, id, memberId, checkerId, clockTime) {
4 years ago
const time = Date.now();
const selectTime = this.$moment(time).format('HH:mm');
if (clockType === 0) {
this.clockInfos[listIndex].recordList[index].morning = selectTime;
4 years ago
} else {
4 years ago
this.clockInfos[listIndex].recordList[index].night = selectTime;
}
const dateTime = this.$moment(`${clockTime} ${selectTime}`).format('x') - 0;
const params = { param: { checkerId: this.checkerId || checkerId || this.checkers[0].memberId, clockType, dateTime, id, memberId } };
4 years ago
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;
}
4 years ago
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);
4 years ago
} else {
this.$message.error(msg || '打卡失败');
4 years ago
throw msg;
}
if (params.param.clockType === 0) {
this.morningLoading = false;
} else {
this.nightLoading = false;
}
4 years ago
} catch (error) {
if (params.param.clockType === 0) {
this.morningLoading = false;
} else {
this.nightLoading = false;
}
throw error || '打卡失败';
4 years ago
}
},
4 years ago
changeVisible(index, status, type) {
4 years ago
if (status !== 0) {
this.clockInfos.forEach(item => {
item.recordList.forEach((clcok, i) => {
if (i === index && type === 'morningVisible') {
clcok.morningVisible = !clcok.morningVisible;
clcok.nightVisible = false;
} else if (i === index && type === 'nightVisible') {
clcok.morningVisible = false;
clcok.nightVisible = !clcok.nightVisible;
} else {
clcok.morningVisible = false;
clcok.nightVisible = false;
}
});
});
}
},
// 修改
changeStatus(id, type, record, visible, show, selectedDate) {
record[visible] = false;
record[show] = true;
this.auditOptions = { id, type, selectedDate };
},
// 选择修改时间
timeChange(time) {
let updateTime = this.$moment(time).format('HH:mm');
let updateDate = this.auditOptions.selectedDate + ' ' + updateTime;
this.chooseTime = this.$moment(updateDate).format('x');
},
async openChange(open, record, show, timeType) {
if (!open && this.chooseTime) {
this.auditOptions[timeType] = this.chooseTime;
this.auditOptions.projectId = this.projectId;
const params = { param: this.auditOptions };
await this.handleClockAudit(params, record, show);
}
if (!open && !this.chooseTime) {
record[show] = false;
}
},
// 驳回
async rejectStatus(id, type, timeType, time, record, show) {
record[show] = false;
const params = { param: { id, type, [timeType]: time, projectId: this.projectId } };
await this.handleClockAudit(params, record, show);
},
// 取消
cancel(record, type) {
record[type] = false;
},
/**
* 审核打卡
* @param {string} id 打卡记录id
* @param {string} morning 早打卡时间
* @param {string} night 晚打卡时间
* @param {string} projectId 项目id
* @param {string} type 审批类型(0-修改,1-驳回,2-通过)
*/
async handleClockAudit(params, record, show) {
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 ? '驳回成功' : '审核通过');
record[show] = false;
this.auditOptions = null;
this.chooseTime = '';
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 || '审核失败';
}
4 years ago
},
4 years ago
},
};
</script>
<style scoped>
.table-head {
border-bottom: 1px solid #e8e8e8;
background: #fafafa;
height: 36px;
line-height: 36px;
4 years ago
font-weight: bold;
}
.table-time {
height: 30px;
line-height: 30px;
4 years ago
border-bottom: 1px solid #e8e8e8;
background: #fafafa;
4 years ago
}
.table-head-item {
width: 27%;
4 years ago
text-align: center;
}
img {
width: 22px;
max-width: auto !important;
}
4 years ago
.teble-box >>> .ant-table-row-cell-break-word {
padding: 4px 0 !important;
}
.teble-box >>> .ant-table-row {
height: 41px !important;
4 years ago
}
.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;
}
.dian-yellow {
background-color: rgb(253, 230, 138);
}
4 years ago
.okBtn {
background-color: #10b981 !important;
color: #fff;
}
</style>
<style>
.ant-popover-inner {
width: 256px;
}
.ant-popover-message {
position: absolute !important;
left: 2px;
top: 8px;
}
4 years ago
.ant-popover-buttons button:nth-child(1) {
background-color: #3b82f6 !important;
color: #fff;
}
</style>