Files
salaryAccountingBackend/src/views/performance/attendance/reviewed/index.vue
T

601 lines
19 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<div class="attendance-reviewed-container">
<el-card>
<template #header>
<div class="card-header">
<span class="title">综合部复审列表</span>
<div class="header-right">
<el-date-picker v-model="dateRange" type="month" placeholder="选择月份" :default-time="defaultTime"
:shortcuts="dateShortcuts" @change="handleDateChange" style="width: 200px; margin-right: 8px;" />
<el-input v-model="searchKeyword" placeholder="请输入姓名搜索" clearable @input="handleSearch"
style="width: 200px; margin-right: 8px;">
<template #prefix>
<el-icon>
<Search />
</el-icon>
</template>
</el-input>
</div>
</div>
</template>
<el-table :data="attendanceList" style="width: 100%" height="calc(100vh - 280px)" border stripe
:header-cell-style="{
background: '#f5f7fa',
color: '#606266',
fontWeight: 'bold',
textAlign: 'center',
padding: '12px 0'
}" :cell-style="{
padding: '8px 0'
}">
<el-table-column label="头像" width="80" align="center" fixed="left">
<template #default="scope">
<el-avatar :size="40" :src="scope.row.avatar" />
</template>
</el-table-column>
<el-table-column prop="name" label="姓名" width="100" show-overflow-tooltip fixed="left" />
<el-table-column prop="workDays" label="工作天数" width="100" align="center" />
<el-table-column prop="status" label="状态" width="100" align="center">
<template #default="scope">
<el-tag :type="scope.row.status === 0 ? 'success' : 'warning'" size="small">
{{ scope.row.status === 0 ? '正式' : '试用' }}
</el-tag>
</template>
</el-table-column>
<el-table-column prop="personalLeave" label="事假" width="100" align="center" show-overflow-tooltip />
<el-table-column prop="sickLeave" label="病假" width="100" align="center" show-overflow-tooltip />
<el-table-column prop="maternityLeave" label="产假" width="100" align="center" show-overflow-tooltip />
<el-table-column prop="paternityLeave" label="陪产假" width="100" align="center" show-overflow-tooltip />
<el-table-column prop="marriageLeave" label="婚假" width="100" align="center" show-overflow-tooltip />
<el-table-column prop="bereavementLeave" label="丧假" width="100" align="center" show-overflow-tooltip />
<el-table-column prop="annualLeave" label="年休假" width="100" align="center" show-overflow-tooltip />
<el-table-column prop="managementAnnualLeave" label="年休假(管理岗)" width="120" align="center"
show-overflow-tooltip />
<el-table-column prop="compensatoryLeave" label="调休假" width="100" align="center" show-overflow-tooltip />
<el-table-column prop="early" label="早退次数" width="100" align="center" show-overflow-tooltip />
<el-table-column prop="late" label="迟到次数" width="100" align="center" show-overflow-tooltip />
<el-table-column prop="seriousLate" label="严重迟到" width="100" align="center" show-overflow-tooltip />
<el-table-column prop="absenteeism" label="旷工次数" width="100" align="center" show-overflow-tooltip />
<el-table-column prop="missedCount" label="缺卡次数" width="100" align="center" show-overflow-tooltip />
<el-table-column prop="absenceCount" label="缺勤次数" width="100" align="center" show-overflow-tooltip />
<el-table-column prop="missingLogCount" label="未提交日志" width="100" align="center" show-overflow-tooltip />
<el-table-column prop="workdayOvertime" label="工作日加班" width="120" align="center" show-overflow-tooltip />
<el-table-column prop="restdayOvertime" label="休息日加班" width="120" align="center" show-overflow-tooltip />
<el-table-column prop="holidayOvertime" label="节假日加班" width="120" align="center" show-overflow-tooltip />
<el-table-column prop="annualPerformance" label="年度绩效" width="100" align="center" show-overflow-tooltip />
<el-table-column prop="monthlyPerformance" label="月度绩效" width="100" align="center" show-overflow-tooltip />
<el-table-column prop="penaltyAmount" label="违纪扣奖" width="100" align="center" show-overflow-tooltip />
<el-table-column label="操作" width="200" fixed="right" align="center">
<template #default="scope">
<el-button type="primary" link size="small" @click="handleViewAbnormal(scope.row)">
详情
</el-button>
<el-button type="success" link size="small" @click="handleApprove(scope.row)">
审批
</el-button>
</template>
</el-table-column>
</el-table>
</el-card>
<!-- 异常考勤详情对话框 -->
<el-dialog v-model="abnormalDialogVisible" title="异常考勤详情" width="800px">
<div v-for="(records, date) in abnormalList" :key="date" class="abnormal-date-group">
<div class="date-header">{{ date }}</div>
<el-table :data="records" style="width: 100%" border stripe>
<el-table-column prop="type" label="异常类型" width="120">
<template #default="scope">
<el-tag :type="getAbnormalTypeTag(scope.row.type)">
{{ getAbnormalTypeLabel(scope.row.type) }}
</el-tag>
</template>
</el-table-column>
<el-table-column label="异常详情" min-width="200">
<template #default="scope">
<template v-if="scope.row.type === 'abnormal'">
<div>考勤类型{{ scope.row.checkType === 'OnDuty' ? '上班' : '下班' }}</div>
<div>考勤时间{{ scope.row.baseCheckTime }}</div>
<div class="result-row">
考勤结果
<el-select v-model="scope.row.realResult" size="small" style="width: 100px; margin: 0 8px;"
@change="handleUpdateAbnormal(scope.row)">
<el-option label="正常" value="Normal" />
<el-option label="早退" value="Early" />
<el-option label="迟到" value="Late" />
<el-option label="严重迟到" value="SeriousLate" />
<el-option label="旷工迟到" value="Absenteeism" />
<el-option label="未打卡" value="NotSigned" />
</el-select>
</div>
</template>
<template v-else-if="scope.row.type === 'checkin'">
<div class="result-row">
缺卡次数
<el-input-number v-model="scope.row.missedCount" size="small" style="width: 100px; margin: 0 8px;"
:min="0" :max="10" @change="handleUpdateAbnormal(scope.row)" />
</div>
</template>
<template v-else-if="scope.row.type === 'report'">
<div class="result-row">
日志状态
<el-select v-model="scope.row.reportCount" size="small" style="width: 100px; margin: 0 8px;"
@change="handleUpdateAbnormal(scope.row)">
<el-option label="未写日志" :value="0" />
<el-option label="正常" :value="1" />
</el-select>
</div>
</template>
</template>
</el-table-column>
<el-table-column label="修改记录" min-width="300">
<template #default="scope">
<div v-if="scope.row.modifications && scope.row.modifications.length > 0">
<div v-for="(mod, index) in scope.row.modifications" :key="index" class="modification-item">
<div>修改人{{ mod.modifiedBy }}</div>
<div>修改时间{{ mod.modifiedAt }}</div>
<div>修改前{{ getModificationValue(mod.originalValue, scope.row.type) }}</div>
<div>修改后{{ getModificationValue(mod.modifiedValue, scope.row.type) }}</div>
<div v-if="mod.isFinally" class="finally-tag">最终修改</div>
</div>
</div>
<span v-else>无修改记录</span>
</template>
</el-table-column>
</el-table>
</div>
</el-dialog>
</div>
</template>
<script setup>
import { ref, onMounted } from "vue";
import { ElMessage, ElMessageBox } from "element-plus";
import { Search } from '@element-plus/icons-vue';
import dayjs from 'dayjs';
import {
getReviewedAttendanceList,
updateAttendanceDetail,
reviewAttendance,
getAbnormalAttendanceDetail,
updateAbnormalAttendance,
} from "../../../../api/modules/index";
// 权限检查
const userInfo = JSON.parse(sessionStorage.getItem("userInfo") || "{}");
// 列表数据
const attendanceList = ref([]);
const originalList = ref([]);
const searchKeyword = ref('');
// 日期范围
const dateRange = ref(null);
const defaultTime = new Date(2000, 1, 1, 0, 0, 0);
// 日期快捷选项
const dateShortcuts = [
{
text: '上个月',
value: () => {
const now = new Date();
return new Date(now.getFullYear(), now.getMonth() - 1, 1);
},
},
{
text: '本月',
value: () => {
const now = new Date();
return new Date(now.getFullYear(), now.getMonth(), 1);
},
},
{
text: '下个月',
value: () => {
const now = new Date();
return new Date(now.getFullYear(), now.getMonth() + 1, 1);
},
},
];
// 设置默认日期范围(上个月)
const setDefaultDateRange = () => {
const now = new Date();
dateRange.value = new Date(now.getFullYear(), now.getMonth() - 1, 1);
};
// 处理日期变化
const handleDateChange = (val) => {
if (val) {
fetchList();
}
};
// 获取列表数据
const fetchList = async () => {
try {
const params = {};
if (dateRange.value) {
params.year = dateRange.value.getFullYear();
params.month = dateRange.value.getMonth() + 1; // 月份从0开始,需要+1
}
const res = await getReviewedAttendanceList(params);
if (res.errcode === 0) {
originalList.value = res.result;
attendanceList.value = res.result;
}
} catch (error) {
ElMessage.error("获取列表失败");
}
};
// 搜索
const handleSearch = () => {
if (!searchKeyword.value) {
attendanceList.value = originalList.value;
return;
}
attendanceList.value = originalList.value.filter(item =>
item.name.toLowerCase().includes(searchKeyword.value.toLowerCase())
);
};
// 获取审批状态标签
const getFlagLabel = (flag) => {
const flagMap = {
0: '待审批',
1: '已通过',
2: '已驳回'
};
return flagMap[flag] || '未知';
};
// 获取审批状态标签样式
const getFlagTagType = (flag) => {
const flagMap = {
0: 'warning',
1: 'success',
2: 'danger'
};
return flagMap[flag] || '';
};
// 审批
const handleApprove = (row) => {
ElMessageBox.confirm(
'确认审批通过该考勤记录?',
'提示',
{
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning',
}
).then(async () => {
try {
const res = await reviewAttendance([row.id]);
if (res.errcode === 0) {
ElMessage.success("审批成功");
fetchList();
}
} catch (error) {
ElMessage.error("审批失败");
}
}).catch(() => {
ElMessage.info('已取消审批');
});
};
// 异常考勤对话框
const abnormalDialogVisible = ref(false);
const abnormalList = ref([]);
// 在 data 中添加新的响应式变量
const currentAbnormalUser = ref({
id: '',
userid: ''
});
// 获取异常类型标签
const getAbnormalTypeLabel = (type) => {
const typeMap = {
'abnormal': '考勤异常',
'checkin': '签到签退',
'report': '日志异常'
};
return typeMap[type] || '未知';
};
// 获取异常类型标签样式
const getAbnormalTypeTag = (type) => {
const typeMap = {
'abnormal': 'danger',
'checkin': 'warning',
'report': 'info'
};
return typeMap[type] || '';
};
// 获取异常结果标签
const getAbnormalResultLabel = (result) => {
const resultMap = {
'Normal': '正常',
'Early': '早退',
'Late': '迟到',
'SeriousLate': '严重迟到',
'Absenteeism': '旷工迟到',
'NotSigned': '未打卡'
};
return resultMap[result] || '未知';
};
// 获取异常结果标签样式
const getAbnormalResultTag = (result) => {
const resultMap = {
'Normal': 'success',
'Early': 'warning',
'Late': 'warning',
'SeriousLate': 'danger',
'Absenteeism': 'danger',
'NotSigned': 'danger'
};
return resultMap[result] || '';
};
// 获取修改记录的值显示
const getModificationValue = (value, type) => {
if (type === 'abnormal') {
const resultMap = {
'Normal': '正常',
'Early': '早退',
'Late': '迟到',
'SeriousLate': '严重迟到',
'Absenteeism': '旷工迟到',
'NotSigned': '未打卡'
};
return resultMap[value] || value;
} else if (type === 'checkin') {
return `${value}次`;
} else if (type === 'report') {
return value == 0 ? '未写日志' : '正常';
}
return value;
};
// 修改查看异常考勤方法
const handleViewAbnormal = async (row) => {
try {
const year = dateRange.value.getFullYear();
const month = dateRange.value.getMonth() + 1;
const res = await getAbnormalAttendanceDetail(row.userid, year, month);
if (res.errcode === 0) {
currentAbnormalUser.value = {
id: res.result.id,
userid: res.result.userid
};
abnormalList.value = res.result.data;
abnormalDialogVisible.value = true;
} else {
ElMessage.error(res.errmsg || "获取异常考勤数据失败");
}
} catch (error) {
ElMessage.error("获取异常考勤数据失败");
}
};
// 修改异常考勤
const handleUpdateAbnormal = async (record) => {
// 保存原始值
const originalValue = record.realResult;
try {
await ElMessageBox.confirm(
'确认修改该考勤记录?',
'提示',
{
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning',
}
);
const year = dateRange.value.getFullYear();
const month = dateRange.value.getMonth() + 1;
const data = {
id: currentAbnormalUser.value.id,
userid: currentAbnormalUser.value.userid,
year,
month,
data: abnormalList.value
};
const res = await updateAbnormalAttendance(data);
if (res.errcode === 0) {
ElMessage.success("修改成功");
fetchList()
// 重新获取异常考勤数据
const detailRes = await getAbnormalAttendanceDetail(currentAbnormalUser.value.userid, year, month);
if (detailRes.errcode === 0) {
currentAbnormalUser.value = {
id: detailRes.result.id,
userid: detailRes.result.userid
};
abnormalList.value = detailRes.result.data;
}
} else {
// 如果修改失败,恢复原值
record.realResult = originalValue;
ElMessage.error(res.errmsg || "修改失败");
}
} catch (error) {
if (error === 'cancel') {
// 用户取消时恢复原值并刷新列表
record.realResult = originalValue;
const year = dateRange.value.getFullYear();
const month = dateRange.value.getMonth() + 1;
const detailRes = await getAbnormalAttendanceDetail(currentAbnormalUser.value.userid, year, month);
if (detailRes.errcode === 0) {
currentAbnormalUser.value = {
id: detailRes.result.id,
userid: detailRes.result.userid
};
abnormalList.value = detailRes.result.data;
}
ElMessage.info('已取消修改');
} else {
// 其他错误时也恢复原值
record.realResult = originalValue;
ElMessage.error("修改失败");
}
}
};
// 页面加载时设置默认日期范围并获取数据
onMounted(() => {
setDefaultDateRange();
fetchList();
});
</script>
<style lang="scss" scoped>
.attendance-reviewed-container {
height: calc(100vh - 120px);
padding: 20px;
background-color: var(--el-bg-color-page);
:deep(.el-card) {
height: 100%;
display: flex;
flex-direction: column;
border-radius: 8px;
box-shadow: var(--el-box-shadow-light);
.el-card__body {
flex: 1;
overflow: hidden;
padding: 0;
display: flex;
flex-direction: column;
}
}
:deep(.el-table) {
flex: 1;
.el-table__body-wrapper {
height: 100%;
}
.el-table__cell {
padding: 8px 0;
}
.el-table__header {
th {
background-color: #f5f7fa;
font-weight: bold;
color: #606266;
height: 48px;
padding: 12px 0;
}
}
.el-table__row {
height: 56px;
}
.el-table__fixed-right {
height: 100% !important;
box-shadow: -2px 0 8px rgba(0, 0, 0, 0.15);
}
.el-table__fixed {
height: 100% !important;
box-shadow: 2px 0 8px rgba(0, 0, 0, 0.15);
}
}
.card-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 16px 20px;
border-bottom: 1px solid var(--el-border-color-light);
background-color: var(--el-bg-color);
.title {
font-size: 16px;
font-weight: 600;
color: var(--el-text-color-primary);
}
.header-right {
display: flex;
align-items: center;
gap: 8px;
}
}
}
:deep(.el-dialog__body) {
padding: 20px;
}
:deep(.el-dialog__header) {
margin: 0;
padding: 20px;
border-bottom: 1px solid var(--el-border-color-light);
background-color: var(--el-bg-color);
}
:deep(.el-dialog__footer) {
padding: 20px;
border-top: 1px solid var(--el-border-color-light);
background-color: var(--el-bg-color);
}
:deep(.el-dialog) {
border-radius: 8px;
overflow: hidden;
}
.abnormal-date-group {
margin-bottom: 20px;
.date-header {
font-size: 16px;
font-weight: bold;
margin-bottom: 10px;
padding: 8px;
background-color: #f5f7fa;
border-radius: 4px;
}
.modification-item {
padding: 8px;
border-bottom: 1px solid #ebeef5;
position: relative;
&:last-child {
border-bottom: none;
}
.finally-tag {
position: absolute;
top: 8px;
right: 8px;
background-color: #67c23a;
color: white;
padding: 2px 6px;
border-radius: 4px;
font-size: 12px;
}
}
}
.result-row {
display: flex;
align-items: center;
margin-top: 4px;
}
</style>