hrm前端一版

This commit is contained in:
2025-12-22 10:57:47 +08:00
parent 6858648b07
commit 40f96069ab
7 changed files with 1784 additions and 0 deletions

View File

@@ -0,0 +1,226 @@
<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>