227 lines
7.0 KiB
Vue
227 lines
7.0 KiB
Vue
|
|
<template>
|
||
|
|
<div class="hrm-page">
|
||
|
|
<section class="panel-grid triple">
|
||
|
|
<el-card class="metal-panel" shadow="hover">
|
||
|
|
<div slot="header" class="panel-header">
|
||
|
|
<span>班次</span>
|
||
|
|
<el-button size="mini" icon="el-icon-refresh" @click="loadShift">刷新</el-button>
|
||
|
|
</div>
|
||
|
|
<el-table :data="shiftList" v-loading="shiftLoading" height="320" stripe>
|
||
|
|
<el-table-column label="名称" prop="shiftName" min-width="140" />
|
||
|
|
<el-table-column label="时间段" min-width="180">
|
||
|
|
<template slot-scope="scope">{{ scope.row.startTime }} - {{ scope.row.endTime }}</template>
|
||
|
|
</el-table-column>
|
||
|
|
<el-table-column label="夜班" prop="nightShift" min-width="80">
|
||
|
|
<template slot-scope="scope">
|
||
|
|
<el-tag size="mini" :type="scope.row.nightShift ? 'warning' : 'info'">
|
||
|
|
{{ scope.row.nightShift ? '是' : '否' }}
|
||
|
|
</el-tag>
|
||
|
|
</template>
|
||
|
|
</el-table-column>
|
||
|
|
<el-table-column label="休息" prop="restMinutes" min-width="100" />
|
||
|
|
</el-table>
|
||
|
|
</el-card>
|
||
|
|
|
||
|
|
<el-card class="metal-panel" shadow="hover">
|
||
|
|
<div slot="header" class="panel-header">
|
||
|
|
<span>排班</span>
|
||
|
|
<div class="actions-inline">
|
||
|
|
<el-date-picker
|
||
|
|
v-model="scheduleQuery.date"
|
||
|
|
type="date"
|
||
|
|
placeholder="日期"
|
||
|
|
size="mini"
|
||
|
|
value-format="yyyy-MM-dd"
|
||
|
|
@change="loadSchedule"
|
||
|
|
/>
|
||
|
|
<el-button size="mini" type="primary" @click="loadSchedule">查询</el-button>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
<el-table :data="scheduleList" v-loading="scheduleLoading" height="320" stripe>
|
||
|
|
<el-table-column label="员工" prop="empId" min-width="100" />
|
||
|
|
<el-table-column label="日期" prop="workDate" min-width="120" />
|
||
|
|
<el-table-column label="班次" prop="shiftId" min-width="120" />
|
||
|
|
<el-table-column label="备注" prop="remark" min-width="160" show-overflow-tooltip />
|
||
|
|
</el-table>
|
||
|
|
</el-card>
|
||
|
|
|
||
|
|
<el-card class="metal-panel" shadow="hover">
|
||
|
|
<div slot="header" class="panel-header">
|
||
|
|
<span>打卡与考勤结果</span>
|
||
|
|
<div class="actions-inline">
|
||
|
|
<el-date-picker
|
||
|
|
v-model="punchQuery.range"
|
||
|
|
type="daterange"
|
||
|
|
start-placeholder="开始"
|
||
|
|
end-placeholder="结束"
|
||
|
|
size="mini"
|
||
|
|
value-format="yyyy-MM-dd"
|
||
|
|
@change="loadPunchAndAttend"
|
||
|
|
/>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
<div class="dual-tables">
|
||
|
|
<div class="table-half">
|
||
|
|
<div class="table-title">打卡记录</div>
|
||
|
|
<el-table :data="punchList" v-loading="punchLoading" height="230" stripe>
|
||
|
|
<el-table-column label="时间" prop="punchTime" min-width="150">
|
||
|
|
<template slot-scope="scope">{{ formatDate(scope.row.punchTime) }}</template>
|
||
|
|
</el-table-column>
|
||
|
|
<el-table-column label="来源" prop="source" min-width="100" />
|
||
|
|
<el-table-column label="定位/设备" prop="location" min-width="140" show-overflow-tooltip />
|
||
|
|
</el-table>
|
||
|
|
</div>
|
||
|
|
<div class="table-half">
|
||
|
|
<div class="table-title">考勤结果</div>
|
||
|
|
<el-table :data="attendList" v-loading="attendLoading" height="230" stripe>
|
||
|
|
<el-table-column label="日期" prop="workDate" min-width="120" />
|
||
|
|
<el-table-column label="出勤分钟" prop="attendMinutes" min-width="110" />
|
||
|
|
<el-table-column label="加班分钟" prop="overtimeMinutes" min-width="110" />
|
||
|
|
<el-table-column label="请假分钟" prop="leaveMinutes" min-width="110" />
|
||
|
|
<el-table-column label="异常" prop="exceptionMsg" min-width="140" show-overflow-tooltip>
|
||
|
|
<template slot-scope="scope">
|
||
|
|
<span :class="['exception-tag', scope.row.exceptionMsg ? 'is-error' : '']">
|
||
|
|
{{ scope.row.exceptionMsg || '正常' }}
|
||
|
|
</span>
|
||
|
|
</template>
|
||
|
|
</el-table-column>
|
||
|
|
</el-table>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
</el-card>
|
||
|
|
</section>
|
||
|
|
</div>
|
||
|
|
</template>
|
||
|
|
|
||
|
|
<script>
|
||
|
|
import { listShift, listSchedule, listPunch, listAttendCalc } from '@/api/hrm'
|
||
|
|
|
||
|
|
export default {
|
||
|
|
name: 'HrmAttendance',
|
||
|
|
data() {
|
||
|
|
return {
|
||
|
|
shiftList: [],
|
||
|
|
shiftLoading: false,
|
||
|
|
scheduleList: [],
|
||
|
|
scheduleLoading: false,
|
||
|
|
scheduleQuery: { date: '' },
|
||
|
|
punchList: [],
|
||
|
|
punchLoading: false,
|
||
|
|
punchQuery: { range: [] },
|
||
|
|
attendList: [],
|
||
|
|
attendLoading: false
|
||
|
|
}
|
||
|
|
},
|
||
|
|
created() {
|
||
|
|
this.loadShift()
|
||
|
|
this.loadSchedule()
|
||
|
|
this.loadPunchAndAttend()
|
||
|
|
},
|
||
|
|
methods: {
|
||
|
|
formatDate(val) {
|
||
|
|
if (!val) return ''
|
||
|
|
const date = new Date(val)
|
||
|
|
const pad = n => (n < 10 ? `0${n}` : n)
|
||
|
|
return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())} ${pad(date.getHours())}:${pad(date.getMinutes())}`
|
||
|
|
},
|
||
|
|
loadShift() {
|
||
|
|
this.shiftLoading = true
|
||
|
|
listShift({ pageNum: 1, pageSize: 200 })
|
||
|
|
.then(res => {
|
||
|
|
this.shiftList = res.rows || []
|
||
|
|
})
|
||
|
|
.finally(() => {
|
||
|
|
this.shiftLoading = false
|
||
|
|
})
|
||
|
|
},
|
||
|
|
loadSchedule() {
|
||
|
|
this.scheduleLoading = true
|
||
|
|
listSchedule({ ...this.scheduleQuery, pageNum: 1, pageSize: 200 })
|
||
|
|
.then(res => {
|
||
|
|
this.scheduleList = res.rows || []
|
||
|
|
})
|
||
|
|
.finally(() => {
|
||
|
|
this.scheduleLoading = false
|
||
|
|
})
|
||
|
|
},
|
||
|
|
loadPunchAndAttend() {
|
||
|
|
this.punchLoading = true
|
||
|
|
this.attendLoading = true
|
||
|
|
const [startTime, endTime] = this.punchQuery.range || []
|
||
|
|
listPunch({ pageNum: 1, pageSize: 200, startTime, endTime })
|
||
|
|
.then(res => {
|
||
|
|
this.punchList = res.rows || []
|
||
|
|
})
|
||
|
|
.finally(() => {
|
||
|
|
this.punchLoading = false
|
||
|
|
})
|
||
|
|
listAttendCalc({ pageNum: 1, pageSize: 200 })
|
||
|
|
.then(res => {
|
||
|
|
this.attendList = res.rows || []
|
||
|
|
})
|
||
|
|
.finally(() => {
|
||
|
|
this.attendLoading = false
|
||
|
|
})
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
</script>
|
||
|
|
|
||
|
|
<style lang="scss" scoped>
|
||
|
|
.hrm-page {
|
||
|
|
padding: 16px 20px 32px;
|
||
|
|
background: #f8f9fb;
|
||
|
|
}
|
||
|
|
.panel-grid {
|
||
|
|
display: grid;
|
||
|
|
grid-template-columns: repeat(3, 1fr);
|
||
|
|
gap: 14px;
|
||
|
|
}
|
||
|
|
.metal-panel {
|
||
|
|
border: 1px solid #d7d9df;
|
||
|
|
border-radius: 10px;
|
||
|
|
background: #fff;
|
||
|
|
}
|
||
|
|
.panel-header {
|
||
|
|
display: flex;
|
||
|
|
justify-content: space-between;
|
||
|
|
align-items: center;
|
||
|
|
font-weight: 600;
|
||
|
|
color: #303133;
|
||
|
|
}
|
||
|
|
.actions-inline {
|
||
|
|
display: flex;
|
||
|
|
gap: 8px;
|
||
|
|
align-items: center;
|
||
|
|
}
|
||
|
|
.dual-tables {
|
||
|
|
display: grid;
|
||
|
|
grid-template-columns: 1fr 1fr;
|
||
|
|
gap: 10px;
|
||
|
|
}
|
||
|
|
.table-half {
|
||
|
|
border: 1px dashed #e6e8ed;
|
||
|
|
border-radius: 8px;
|
||
|
|
padding: 8px;
|
||
|
|
}
|
||
|
|
.table-title {
|
||
|
|
font-weight: 600;
|
||
|
|
margin-bottom: 6px;
|
||
|
|
}
|
||
|
|
.exception-tag {
|
||
|
|
color: #409eff;
|
||
|
|
&.is-error {
|
||
|
|
color: #e76f51;
|
||
|
|
font-weight: 600;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
@media (max-width: 1200px) {
|
||
|
|
.panel-grid {
|
||
|
|
grid-template-columns: 1fr;
|
||
|
|
}
|
||
|
|
.dual-tables {
|
||
|
|
grid-template-columns: 1fr;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
</style>
|