完善考勤审批功能模块
This commit is contained in:
@@ -156,3 +156,57 @@ export function updateAbnormalAttendance(data) {
|
|||||||
data
|
data
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 获取综合部复审列表
|
||||||
|
export function getReviewedAttendanceList(params) {
|
||||||
|
return service({
|
||||||
|
url: '/performance/attendance/reviewed/list',
|
||||||
|
method: 'get',
|
||||||
|
params
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 综合部复审审批
|
||||||
|
export function reviewAttendance(data) {
|
||||||
|
return service({
|
||||||
|
url: '/performance/attendance/general/review',
|
||||||
|
method: 'put',
|
||||||
|
data
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取主管审批列表
|
||||||
|
export function getManagerAttendanceList(params) {
|
||||||
|
return service({
|
||||||
|
url: '/performance/attendance/manager/list',
|
||||||
|
method: 'get',
|
||||||
|
params
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 主管审批
|
||||||
|
export function approveManagerAttendance(data) {
|
||||||
|
return service({
|
||||||
|
url: '/performance/attendance/manager/approve',
|
||||||
|
method: 'put',
|
||||||
|
data
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 主管修改绩效
|
||||||
|
export function updateManagerAttendanceDetail(data) {
|
||||||
|
return service({
|
||||||
|
url: '/performance/attendance/manager/detail',
|
||||||
|
method: 'put',
|
||||||
|
data
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取财务查看考勤列表
|
||||||
|
export const getFinanceAttendanceList = (params) => {
|
||||||
|
return service({
|
||||||
|
url: '/performance/attendance/finance/list',
|
||||||
|
method: 'get',
|
||||||
|
params
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|||||||
@@ -36,8 +36,9 @@ import { computed, onMounted, reactive, ref } from "vue";
|
|||||||
import store from "../../store/index.js";
|
import store from "../../store/index.js";
|
||||||
import menuItems from "./components/menuItems.vue";
|
import menuItems from "./components/menuItems.vue";
|
||||||
// 引入本地路由表渲染导航栏
|
// 引入本地路由表渲染导航栏
|
||||||
import menuTable from "./menuTable.js";
|
// import menuTable from "./menuTable.js";
|
||||||
import { useRouter, useRoute } from "vue-router";
|
import { useRouter, useRoute } from "vue-router";
|
||||||
|
import {menuTable} from "../../router/generateMenu.js"
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
const activeMenu = computed(() => {
|
const activeMenu = computed(() => {
|
||||||
return route.path;
|
return route.path;
|
||||||
|
|||||||
@@ -14,5 +14,20 @@ const menuTable = [
|
|||||||
url: "/performance/attendance/general",
|
url: "/performance/attendance/general",
|
||||||
icon: "Notebook",
|
icon: "Notebook",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
title: "主管审批列表",
|
||||||
|
url: "/performance/attendance/manager",
|
||||||
|
icon: "Notebook",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "综合部复审列表",
|
||||||
|
url: "/performance/attendance/reviewed",
|
||||||
|
icon: "Notebook",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "财务考勤列表",
|
||||||
|
url: "/performance/attendance/finance",
|
||||||
|
icon: "Notebook",
|
||||||
|
},
|
||||||
];
|
];
|
||||||
export default menuTable;
|
export default menuTable;
|
||||||
|
|||||||
@@ -0,0 +1,66 @@
|
|||||||
|
import routerList from "./originalRoutingTable";
|
||||||
|
// 递归处理路由列表生成菜单数据
|
||||||
|
export const generateMenu = (routes, userRole) => {
|
||||||
|
const menuList = [];
|
||||||
|
|
||||||
|
// 递归处理路由
|
||||||
|
const processRoute = (route) => {
|
||||||
|
// 检查路由是否需要权限验证
|
||||||
|
if (route.meta && route.meta.requiresAuth) {
|
||||||
|
// 检查用户角色是否有权限访问
|
||||||
|
if (route.meta.roles && !route.meta.roles.includes(userRole)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const menuItem = {
|
||||||
|
title: route.meta.name,
|
||||||
|
path: route.path,
|
||||||
|
name: route.name,
|
||||||
|
meta: route.meta,
|
||||||
|
url: route.url || route.path
|
||||||
|
};
|
||||||
|
|
||||||
|
// 如果有子路由,递归处理
|
||||||
|
if (route.children && route.children.length > 0) {
|
||||||
|
const children = route.children
|
||||||
|
.map(child => processRoute(child))
|
||||||
|
.filter(child => child !== null);
|
||||||
|
|
||||||
|
if (children.length > 0) {
|
||||||
|
// 如果只有一个子路由,将其提升为一级菜单
|
||||||
|
if (children.length === 1) {
|
||||||
|
const child = children[0];
|
||||||
|
return {
|
||||||
|
title: child.meta.name,
|
||||||
|
path: child.path,
|
||||||
|
name: child.name,
|
||||||
|
meta: child.meta,
|
||||||
|
url: child.url || child.path
|
||||||
|
};
|
||||||
|
}
|
||||||
|
menuItem.children = children;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return menuItem;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 处理所有路由
|
||||||
|
routes.forEach(route => {
|
||||||
|
const menuItem = processRoute(route);
|
||||||
|
if (menuItem) {
|
||||||
|
menuList.push(menuItem);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return menuList;
|
||||||
|
};
|
||||||
|
export const menuTable = generateMenu(routerList, 1)
|
||||||
|
// 使用示例
|
||||||
|
/*
|
||||||
|
const userRole = 2; // 用户角色
|
||||||
|
const menuList = generateMenu(routerList, userRole);
|
||||||
|
console.log(menuList);
|
||||||
|
*/
|
||||||
@@ -1,15 +1,14 @@
|
|||||||
// 模板自带的功能路由表
|
// 模板自带的功能路由表
|
||||||
import login from "../views/login/index.vue";
|
|
||||||
import Layout from "../layout/index.vue";
|
|
||||||
const routerList = [
|
const routerList = [
|
||||||
{
|
{
|
||||||
path: "/login",
|
path: "/login",
|
||||||
name: login,
|
name: "login",
|
||||||
component: login,
|
component: () => import("../views/login/index.vue"),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "/",
|
path: "/",
|
||||||
component: Layout,
|
component: () => import("../layout/index.vue"),
|
||||||
name: "container",
|
name: "container",
|
||||||
redirect: "home",
|
redirect: "home",
|
||||||
meta: {
|
meta: {
|
||||||
@@ -31,7 +30,7 @@ const routerList = [
|
|||||||
{
|
{
|
||||||
path: "/group",
|
path: "/group",
|
||||||
name: "employe",
|
name: "employe",
|
||||||
component: Layout,
|
component: () => import("../layout/index.vue"),
|
||||||
meta: {
|
meta: {
|
||||||
requiresAuth: true, //有一些页面是否登录才能进去
|
requiresAuth: true, //有一些页面是否登录才能进去
|
||||||
name: "组别管理",
|
name: "组别管理",
|
||||||
@@ -49,27 +48,64 @@ const routerList = [
|
|||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/performance',
|
path: '/performance/attendance',
|
||||||
component: Layout,
|
component: () => import("../layout/index.vue"),
|
||||||
name: 'Performance',
|
redirect: '/performance/attendance/general',
|
||||||
|
name: 'PerformanceAttendance',
|
||||||
meta: {
|
meta: {
|
||||||
name: "绩效考勤",
|
|
||||||
title: '绩效考勤',
|
title: '绩效考勤',
|
||||||
icon: 'el-icon-s-data',
|
icon: 'el-icon-s-data',
|
||||||
roles: [2] // 只有role=2的用户可以查看
|
name: "绩效考勤",
|
||||||
|
requiresAuth: true,
|
||||||
|
roles: [1, 2, 3] // 所有角色都可以访问
|
||||||
},
|
},
|
||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
path: 'attendance/general',
|
path: '/general',
|
||||||
name: 'AttendanceGeneral',
|
name: 'GeneralAttendance',
|
||||||
component: () => import('../views/performance/attendance/general/index.vue'),
|
component: () => import('../views/performance/attendance/general/index.vue'),
|
||||||
meta: {
|
meta: {
|
||||||
name: "综合部初审列表",
|
|
||||||
title: '综合部初审列表',
|
title: '综合部初审列表',
|
||||||
roles: [2] // 只有role=2的用户可以查看
|
name: "综合部初审列表",
|
||||||
|
requiresAuth: true,
|
||||||
|
// roles: [2] // 只有role=2的用户可以查看
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/manager',
|
||||||
|
name: 'ManagerAttendance',
|
||||||
|
component: () => import('../views/performance/attendance/manager/index.vue'),
|
||||||
|
meta: {
|
||||||
|
title: '主管审批列表',
|
||||||
|
name: "主管审批列表",
|
||||||
|
requiresAuth: true,
|
||||||
|
// roles: [1] // 只有role=1的用户可以查看
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/reviewed',
|
||||||
|
name: 'ReviewedAttendance',
|
||||||
|
component: () => import('../views/performance/attendance/reviewed/index.vue'),
|
||||||
|
meta: {
|
||||||
|
title: '综合部复审列表',
|
||||||
|
name: "综合部复审列表",
|
||||||
|
requiresAuth: true,
|
||||||
|
// roles: [2] // 只有role=2的用户可以查看
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/finance',
|
||||||
|
name: 'FinanceAttendance',
|
||||||
|
component: () => import('../views/performance/attendance/finance/index.vue'),
|
||||||
|
meta: {
|
||||||
|
title: '财务查看考勤列表',
|
||||||
|
name: "财务查看考勤列表",
|
||||||
|
requiresAuth: true,
|
||||||
|
// roles: [3] // 只有role=3的用户可以查看
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
export default routerList;
|
export default routerList;
|
||||||
|
|||||||
@@ -0,0 +1,475 @@
|
|||||||
|
<template>
|
||||||
|
<div class="attendance-finance-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>
|
||||||
|
<el-table-column prop="monthlyPerformance" label="月度绩效" width="100" align="center" show-overflow-tooltip></el-table-column>
|
||||||
|
<el-table-column prop="penaltyAmount" label="违纪扣奖" width="100" align="center" show-overflow-tooltip />
|
||||||
|
<el-table-column label="操作" width="120" fixed="right" align="center">
|
||||||
|
<template #default="scope">
|
||||||
|
<el-button
|
||||||
|
type="primary"
|
||||||
|
link
|
||||||
|
size="small"
|
||||||
|
@click="handleViewAbnormal(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>考勤结果:{{ getAbnormalResultLabel(scope.row.realResult) }}</div>
|
||||||
|
</template>
|
||||||
|
<template v-else-if="scope.row.type === 'checkin'">
|
||||||
|
<div>缺卡次数:{{ scope.row.missedCount }}次</div>
|
||||||
|
</template>
|
||||||
|
<template v-else-if="scope.row.type === 'report'">
|
||||||
|
<div>日志状态:{{ scope.row.reportCount === 0 ? '未写日志' : '正常' }}</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 } from "element-plus";
|
||||||
|
import { Search } from '@element-plus/icons-vue';
|
||||||
|
import dayjs from 'dayjs';
|
||||||
|
import {
|
||||||
|
getFinanceAttendanceList,
|
||||||
|
getAbnormalAttendanceDetail,
|
||||||
|
} from "../../../../api/modules/index";
|
||||||
|
|
||||||
|
// 权限检查
|
||||||
|
const userInfo = JSON.parse(sessionStorage.getItem("userInfo") || "{}");
|
||||||
|
// if (userInfo.role !== 3) {
|
||||||
|
// ElMessage.error("您没有权限访问此页面");
|
||||||
|
// window.history.back();
|
||||||
|
// }
|
||||||
|
|
||||||
|
// 列表数据
|
||||||
|
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 getFinanceAttendanceList(params);
|
||||||
|
if (res.errcode === 0 && res.result && res.result.data) {
|
||||||
|
originalList.value = res.result.data;
|
||||||
|
attendanceList.value = res.result.data;
|
||||||
|
} else {
|
||||||
|
ElMessage.error(res.errmsg || "获取列表失败");
|
||||||
|
}
|
||||||
|
} 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 abnormalDialogVisible = ref(false);
|
||||||
|
const abnormalList = ref([]);
|
||||||
|
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 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("获取异常考勤数据失败");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 页面加载时设置默认日期范围并获取数据
|
||||||
|
onMounted(() => {
|
||||||
|
setDefaultDateRange();
|
||||||
|
fetchList();
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.attendance-finance-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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
: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;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -82,13 +82,6 @@
|
|||||||
<el-table-column prop="annualPerformance" label="年度绩效" width="100" 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="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 prop="penaltyAmount" label="违纪扣奖" width="100" align="center" show-overflow-tooltip />
|
||||||
<el-table-column prop="flag" label="审批状态" width="100" align="center">
|
|
||||||
<template #default="scope">
|
|
||||||
<el-tag :type="getFlagTagType(scope.row.flag)" size="small">
|
|
||||||
{{ getFlagLabel(scope.row.flag) }}
|
|
||||||
</el-tag>
|
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
<el-table-column label="操作" width="200" fixed="right" align="center">
|
<el-table-column label="操作" width="200" fixed="right" align="center">
|
||||||
<template #default="scope">
|
<template #default="scope">
|
||||||
<el-button
|
<el-button
|
||||||
@@ -104,7 +97,6 @@
|
|||||||
link
|
link
|
||||||
size="small"
|
size="small"
|
||||||
@click="handleApprove(scope.row)"
|
@click="handleApprove(scope.row)"
|
||||||
v-if="scope.row.flag === 0"
|
|
||||||
>
|
>
|
||||||
审批
|
审批
|
||||||
</el-button>
|
</el-button>
|
||||||
@@ -221,16 +213,6 @@
|
|||||||
<el-input-number v-model="editForm.holidayOvertime" :min="0" :precision="1" :step="0.5" />
|
<el-input-number v-model="editForm.holidayOvertime" :min="0" :precision="1" :step="0.5" />
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-col>
|
</el-col>
|
||||||
<el-col :span="12">
|
|
||||||
<el-form-item label="年度绩效" prop="annualPerformance">
|
|
||||||
<el-input-number v-model="editForm.annualPerformance" :min="0" :max="100" :precision="1" :step="0.5" />
|
|
||||||
</el-form-item>
|
|
||||||
</el-col>
|
|
||||||
<el-col :span="12">
|
|
||||||
<el-form-item label="月度绩效" prop="monthlyPerformance">
|
|
||||||
<el-input-number v-model="editForm.monthlyPerformance" :min="0" :max="100" :precision="1" :step="0.5" />
|
|
||||||
</el-form-item>
|
|
||||||
</el-col>
|
|
||||||
<el-col :span="12">
|
<el-col :span="12">
|
||||||
<el-form-item label="违纪扣奖" prop="penaltyAmount">
|
<el-form-item label="违纪扣奖" prop="penaltyAmount">
|
||||||
<el-input-number v-model="editForm.penaltyAmount" :min="0" :precision="2" :step="100" />
|
<el-input-number v-model="editForm.penaltyAmount" :min="0" :precision="2" :step="100" />
|
||||||
@@ -271,6 +253,7 @@
|
|||||||
<template #default="scope">
|
<template #default="scope">
|
||||||
<template v-if="scope.row.type === 'abnormal'">
|
<template v-if="scope.row.type === 'abnormal'">
|
||||||
<div>考勤类型:{{ scope.row.checkType === 'OnDuty' ? '上班' : '下班' }}</div>
|
<div>考勤类型:{{ scope.row.checkType === 'OnDuty' ? '上班' : '下班' }}</div>
|
||||||
|
<div>考勤时间:{{ scope.row.baseCheckTime }}</div>
|
||||||
<div class="result-row">
|
<div class="result-row">
|
||||||
考勤结果:
|
考勤结果:
|
||||||
<el-select
|
<el-select
|
||||||
@@ -289,10 +272,32 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<template v-else-if="scope.row.type === 'checkin'">
|
<template v-else-if="scope.row.type === 'checkin'">
|
||||||
<div>缺卡次数:{{ scope.row.missedCount }}次</div>
|
<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>
|
||||||
<template v-else-if="scope.row.type === 'report'">
|
<template v-else-if="scope.row.type === 'report'">
|
||||||
<div>日志状态:{{ scope.row.reportCount === 0 ? '未提交' : '已提交' }}</div>
|
<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>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
@@ -302,8 +307,8 @@
|
|||||||
<div v-for="(mod, index) in scope.row.modifications" :key="index" class="modification-item">
|
<div v-for="(mod, index) in scope.row.modifications" :key="index" class="modification-item">
|
||||||
<div>修改人:{{ mod.modifiedBy }}</div>
|
<div>修改人:{{ mod.modifiedBy }}</div>
|
||||||
<div>修改时间:{{ mod.modifiedAt }}</div>
|
<div>修改时间:{{ mod.modifiedAt }}</div>
|
||||||
<div>修改前:{{ mod.originalValue }}</div>
|
<div>修改前:{{ getModificationValue(mod.originalValue, scope.row.type) }}</div>
|
||||||
<div>修改后:{{ mod.modifiedValue }}</div>
|
<div>修改后:{{ getModificationValue(mod.modifiedValue, scope.row.type) }}</div>
|
||||||
<div v-if="mod.isFinally" class="finally-tag">最终修改</div>
|
<div v-if="mod.isFinally" class="finally-tag">最终修改</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -407,8 +412,6 @@ const editForm = ref({
|
|||||||
workdayOvertime: 0,
|
workdayOvertime: 0,
|
||||||
restdayOvertime: 0,
|
restdayOvertime: 0,
|
||||||
holidayOvertime: 0,
|
holidayOvertime: 0,
|
||||||
annualPerformance: 0,
|
|
||||||
monthlyPerformance: 0,
|
|
||||||
penaltyAmount: 0
|
penaltyAmount: 0
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -433,8 +436,6 @@ const editRules = {
|
|||||||
workdayOvertime: [{ type: 'number', message: '请输入数字' }],
|
workdayOvertime: [{ type: 'number', message: '请输入数字' }],
|
||||||
restdayOvertime: [{ type: 'number', message: '请输入数字' }],
|
restdayOvertime: [{ type: 'number', message: '请输入数字' }],
|
||||||
holidayOvertime: [{ type: 'number', message: '请输入数字' }],
|
holidayOvertime: [{ type: 'number', message: '请输入数字' }],
|
||||||
annualPerformance: [{ type: 'number', message: '请输入数字' }],
|
|
||||||
monthlyPerformance: [{ type: 'number', message: '请输入数字' }],
|
|
||||||
penaltyAmount: [{ type: 'number', message: '请输入数字' }]
|
penaltyAmount: [{ type: 'number', message: '请输入数字' }]
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -554,8 +555,6 @@ const handleEdit = (row) => {
|
|||||||
workdayOvertime: row.workdayOvertime || 0,
|
workdayOvertime: row.workdayOvertime || 0,
|
||||||
restdayOvertime: row.restdayOvertime || 0,
|
restdayOvertime: row.restdayOvertime || 0,
|
||||||
holidayOvertime: row.holidayOvertime || 0,
|
holidayOvertime: row.holidayOvertime || 0,
|
||||||
annualPerformance: row.annualPerformance || 0,
|
|
||||||
monthlyPerformance: row.monthlyPerformance || 0,
|
|
||||||
penaltyAmount: row.penaltyAmount || 0
|
penaltyAmount: row.penaltyAmount || 0
|
||||||
};
|
};
|
||||||
editDialogVisible.value = true;
|
editDialogVisible.value = true;
|
||||||
@@ -610,6 +609,12 @@ const handleApprove = (row) => {
|
|||||||
const abnormalDialogVisible = ref(false);
|
const abnormalDialogVisible = ref(false);
|
||||||
const abnormalList = ref([]);
|
const abnormalList = ref([]);
|
||||||
|
|
||||||
|
// 在 data 中添加新的响应式变量
|
||||||
|
const currentAbnormalUser = ref({
|
||||||
|
id: '',
|
||||||
|
userid: ''
|
||||||
|
});
|
||||||
|
|
||||||
// 获取异常类型标签
|
// 获取异常类型标签
|
||||||
const getAbnormalTypeLabel = (type) => {
|
const getAbnormalTypeLabel = (type) => {
|
||||||
const typeMap = {
|
const typeMap = {
|
||||||
@@ -656,51 +661,111 @@ const getAbnormalResultTag = (result) => {
|
|||||||
return resultMap[result] || '';
|
return resultMap[result] || '';
|
||||||
};
|
};
|
||||||
|
|
||||||
// 查看异常考勤
|
// 修改查看异常考勤方法
|
||||||
const handleViewAbnormal = async (row) => {
|
const handleViewAbnormal = async (row) => {
|
||||||
try {
|
try {
|
||||||
const year = dateRange.value.getFullYear();
|
const year = dateRange.value.getFullYear();
|
||||||
const month = dateRange.value.getMonth() + 1;
|
const month = dateRange.value.getMonth() + 1;
|
||||||
const res = await getAbnormalAttendanceDetail(row.userid, year, month);
|
const res = await getAbnormalAttendanceDetail(row.userid, year, month);
|
||||||
if (res.errcode === 0) {
|
if (res.errcode === 0) {
|
||||||
abnormalList.value = res.result;
|
currentAbnormalUser.value = {
|
||||||
|
id: res.result.id,
|
||||||
|
userid: res.result.userid
|
||||||
|
};
|
||||||
|
abnormalList.value = res.result.data;
|
||||||
abnormalDialogVisible.value = true;
|
abnormalDialogVisible.value = true;
|
||||||
} else {
|
} else {
|
||||||
ElMessage.error(res.errmsg || '获取异常考勤失败');
|
ElMessage.error(res.errmsg || "获取异常考勤数据失败");
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
ElMessage.error('获取异常考勤失败');
|
ElMessage.error("获取异常考勤数据失败");
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// 修改异常考勤
|
// 修改异常考勤
|
||||||
const handleUpdateAbnormal = async (record) => {
|
const handleUpdateAbnormal = async (record) => {
|
||||||
|
// 保存原始值
|
||||||
|
const originalValue = record.realResult;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
await ElMessageBox.confirm(
|
||||||
|
'确认修改该考勤记录?',
|
||||||
|
'提示',
|
||||||
|
{
|
||||||
|
confirmButtonText: '确定',
|
||||||
|
cancelButtonText: '取消',
|
||||||
|
type: 'warning',
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
const year = dateRange.value.getFullYear();
|
const year = dateRange.value.getFullYear();
|
||||||
const month = dateRange.value.getMonth() + 1;
|
const month = dateRange.value.getMonth() + 1;
|
||||||
const data = {
|
const data = {
|
||||||
id: record.id,
|
id: currentAbnormalUser.value.id,
|
||||||
userid: record.userid,
|
userid: currentAbnormalUser.value.userid,
|
||||||
year,
|
year,
|
||||||
month,
|
month,
|
||||||
data: {
|
data: abnormalList.value
|
||||||
realResult: record.realResult
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
const res = await updateAbnormalAttendance(data);
|
const res = await updateAbnormalAttendance(data);
|
||||||
if (res.errcode === 0) {
|
if (res.errcode === 0) {
|
||||||
ElMessage.success("修改成功");
|
ElMessage.success("修改成功");
|
||||||
|
fetchList()
|
||||||
// 重新获取异常考勤数据
|
// 重新获取异常考勤数据
|
||||||
const detailRes = await getAbnormalAttendanceDetail(record.userid, year, month);
|
const detailRes = await getAbnormalAttendanceDetail(currentAbnormalUser.value.userid, year, month);
|
||||||
if (detailRes.errcode === 0) {
|
if (detailRes.errcode === 0) {
|
||||||
abnormalList.value = detailRes.result;
|
currentAbnormalUser.value = {
|
||||||
|
id: detailRes.result.id,
|
||||||
|
userid: detailRes.result.userid
|
||||||
|
};
|
||||||
|
abnormalList.value = detailRes.result.data;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
// 如果修改失败,恢复原值
|
||||||
|
record.realResult = originalValue;
|
||||||
ElMessage.error(res.errmsg || "修改失败");
|
ElMessage.error(res.errmsg || "修改失败");
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} 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("修改失败");
|
ElMessage.error("修改失败");
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 获取修改记录的值显示
|
||||||
|
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;
|
||||||
};
|
};
|
||||||
|
|
||||||
// 页面加载时设置默认日期范围并获取数据
|
// 页面加载时设置默认日期范围并获取数据
|
||||||
|
|||||||
@@ -0,0 +1,656 @@
|
|||||||
|
<template>
|
||||||
|
<div class="attendance-manager-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>
|
||||||
|
<el-table-column prop="monthlyPerformance" label="月度绩效" width="100" align="center" show-overflow-tooltip></el-table-column>
|
||||||
|
<el-table-column prop="penaltyAmount" label="违纪扣奖" width="100" align="center" show-overflow-tooltip />
|
||||||
|
<el-table-column label="操作" width="280" 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>
|
||||||
|
<el-button
|
||||||
|
type="warning"
|
||||||
|
link
|
||||||
|
size="small"
|
||||||
|
@click="handleEditPerformance(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>考勤结果:{{ getAbnormalResultLabel(scope.row.realResult) }}</div>
|
||||||
|
</template>
|
||||||
|
<template v-else-if="scope.row.type === 'checkin'">
|
||||||
|
<div>缺卡次数:{{ scope.row.missedCount }}次</div>
|
||||||
|
</template>
|
||||||
|
<template v-else-if="scope.row.type === 'report'">
|
||||||
|
<div>日志状态:{{ scope.row.reportCount === 0 ? '未写日志' : '正常' }}</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>
|
||||||
|
|
||||||
|
<!-- 修改绩效对话框 -->
|
||||||
|
<el-dialog
|
||||||
|
v-model="performanceDialogVisible"
|
||||||
|
title="修改绩效"
|
||||||
|
width="500px"
|
||||||
|
>
|
||||||
|
<el-form
|
||||||
|
ref="performanceFormRef"
|
||||||
|
:model="performanceForm"
|
||||||
|
label-width="100px"
|
||||||
|
:rules="performanceRules"
|
||||||
|
>
|
||||||
|
<el-form-item label="年度绩效" prop="annualPerformance">
|
||||||
|
<el-input-number
|
||||||
|
v-model="performanceForm.annualPerformance"
|
||||||
|
:min="0"
|
||||||
|
:max="100"
|
||||||
|
:precision="1"
|
||||||
|
:step="0.5"
|
||||||
|
style="width: 200px"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="月度绩效" prop="monthlyPerformance">
|
||||||
|
<el-input-number
|
||||||
|
v-model="performanceForm.monthlyPerformance"
|
||||||
|
:min="0"
|
||||||
|
:max="100"
|
||||||
|
:precision="1"
|
||||||
|
:step="0.5"
|
||||||
|
style="width: 200px"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
<template #footer>
|
||||||
|
<span class="dialog-footer">
|
||||||
|
<el-button @click="performanceDialogVisible = false">取消</el-button>
|
||||||
|
<el-button type="primary" @click="submitPerformanceForm">确定</el-button>
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
</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 {
|
||||||
|
getManagerAttendanceList,
|
||||||
|
updateManagerAttendanceDetail,
|
||||||
|
approveManagerAttendance,
|
||||||
|
getAbnormalAttendanceDetail,
|
||||||
|
} from "../../../../api/modules/index";
|
||||||
|
|
||||||
|
// 权限检查
|
||||||
|
const userInfo = JSON.parse(sessionStorage.getItem("userInfo") || "{}");
|
||||||
|
// if (userInfo.role !== 2) {
|
||||||
|
// ElMessage.error("您没有权限访问此页面");
|
||||||
|
// window.history.back();
|
||||||
|
// }
|
||||||
|
|
||||||
|
// 列表数据
|
||||||
|
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 getManagerAttendanceList(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 approveManagerAttendance([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 performanceDialogVisible = ref(false);
|
||||||
|
const performanceFormRef = ref(null);
|
||||||
|
const performanceForm = ref({
|
||||||
|
id: '',
|
||||||
|
annualPerformance: 0,
|
||||||
|
monthlyPerformance: 0
|
||||||
|
});
|
||||||
|
|
||||||
|
const performanceRules = {
|
||||||
|
annualPerformance: [
|
||||||
|
{ required: true, message: '请输入年度绩效', trigger: 'blur' },
|
||||||
|
{ type: 'number', min: 0, max: 100, message: '年度绩效必须在0-100之间', trigger: 'blur' }
|
||||||
|
],
|
||||||
|
monthlyPerformance: [
|
||||||
|
{ required: true, message: '请输入月度绩效', trigger: 'blur' },
|
||||||
|
{ type: 'number', min: 0, max: 100, message: '月度绩效必须在0-100之间', trigger: 'blur' }
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
// 打开修改绩效对话框
|
||||||
|
const handleEditPerformance = (row) => {
|
||||||
|
performanceForm.value = {
|
||||||
|
id: row.id,
|
||||||
|
annualPerformance: row.annualPerformance || 0,
|
||||||
|
monthlyPerformance: row.monthlyPerformance || 0
|
||||||
|
};
|
||||||
|
performanceDialogVisible.value = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 提交绩效表单
|
||||||
|
const submitPerformanceForm = async () => {
|
||||||
|
if (!performanceFormRef.value) return;
|
||||||
|
|
||||||
|
await performanceFormRef.value.validate(async (valid) => {
|
||||||
|
if (valid) {
|
||||||
|
try {
|
||||||
|
const res = await updateManagerAttendanceDetail(performanceForm.value);
|
||||||
|
if (res.errcode === 0) {
|
||||||
|
ElMessage.success("修改成功");
|
||||||
|
performanceDialogVisible.value = false;
|
||||||
|
fetchList();
|
||||||
|
} else {
|
||||||
|
ElMessage.error(res.errmsg || "修改失败");
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
ElMessage.error("修改失败");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// 页面加载时设置默认日期范围并获取数据
|
||||||
|
onMounted(() => {
|
||||||
|
setDefaultDateRange();
|
||||||
|
fetchList();
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.attendance-manager-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;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dialog-footer {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,653 @@
|
|||||||
|
<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") || "{}");
|
||||||
|
// if (userInfo.role !== 2) {
|
||||||
|
// ElMessage.error("您没有权限访问此页面");
|
||||||
|
// window.history.back();
|
||||||
|
// }
|
||||||
|
|
||||||
|
// 列表数据
|
||||||
|
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>
|
||||||
Reference in New Issue
Block a user