hcm前端2版

This commit is contained in:
2025-12-22 16:53:48 +08:00
parent 40f96069ab
commit 007c450f63
8 changed files with 1671 additions and 41 deletions

View File

@@ -4,7 +4,10 @@
<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 class="actions-inline">
<el-button size="mini" type="primary" icon="el-icon-plus" @click="openShiftDialog()">新增</el-button>
<el-button size="mini" icon="el-icon-refresh" @click="loadShift">刷新</el-button>
</div>
</div>
<el-table :data="shiftList" v-loading="shiftLoading" height="320" stripe>
<el-table-column label="名称" prop="shiftName" min-width="140" />
@@ -19,6 +22,12 @@
</template>
</el-table-column>
<el-table-column label="休息" prop="restMinutes" min-width="100" />
<el-table-column label="操作" width="140" fixed="right">
<template slot-scope="scope">
<el-button size="mini" type="text" @click.stop="openShiftDialog(scope.row)">编辑</el-button>
<el-button size="mini" type="text" @click.stop="delShift(scope.row)">删除</el-button>
</template>
</el-table-column>
</el-table>
</el-card>
@@ -31,17 +40,32 @@
type="date"
placeholder="日期"
size="mini"
value-format="yyyy-MM-dd"
style="width: 140px"
@change="loadSchedule"
/>
<el-button size="mini" type="primary" @click="loadSchedule">查询</el-button>
<el-button size="mini" icon="el-icon-plus" @click="openScheduleDialog()">新增</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="empId" min-width="120">
<template slot-scope="scope">
{{ renderEmp(scope.row.empId) }}
</template>
</el-table-column>
<el-table-column label="日期" prop="workDate" min-width="120" />
<el-table-column label="班次" prop="shiftId" min-width="120" />
<el-table-column label="班次" prop="shiftId" min-width="140">
<template slot-scope="scope">
{{ renderShift(scope.row.shiftId) }}
</template>
</el-table-column>
<el-table-column label="备注" prop="remark" min-width="160" show-overflow-tooltip />
<el-table-column label="操作" width="140" fixed="right">
<template slot-scope="scope">
<el-button size="mini" type="text" @click.stop="openScheduleDialog(scope.row)">编辑</el-button>
<el-button size="mini" type="text" @click.stop="delSchedule(scope.row)">删除</el-button>
</template>
</el-table-column>
</el-table>
</el-card>
@@ -67,8 +91,19 @@
<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="empId" min-width="120">
<template slot-scope="scope">
{{ renderEmp(scope.row.empId) }}
</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-column label="操作" width="120" fixed="right">
<template slot-scope="scope">
<el-button size="mini" type="text" @click.stop="openPunchDialog(scope.row)">编辑</el-button>
<el-button size="mini" type="text" @click.stop="delPunch(scope.row)">删除</el-button>
</template>
</el-table-column>
</el-table>
</div>
<div class="table-half">
@@ -90,16 +125,123 @@
</div>
</el-card>
</section>
<!-- 班次弹窗 -->
<el-dialog :visible.sync="shiftDialogVisible" title="班次" width="420px">
<el-form ref="shiftForm" :model="shiftForm" :rules="shiftRules" label-width="90px" size="small">
<el-form-item label="名称" prop="shiftName">
<el-input v-model="shiftForm.shiftName" placeholder="班次名称" />
</el-form-item>
<el-form-item label="开始时间" prop="startTime">
<el-time-picker v-model="shiftForm.startTime" placeholder="开始时间" style="width: 100%" value-format="HH:mm" />
</el-form-item>
<el-form-item label="结束时间" prop="endTime">
<el-time-picker v-model="shiftForm.endTime" placeholder="结束时间" style="width: 100%" value-format="HH:mm" />
</el-form-item>
<el-form-item label="夜班" prop="nightShift">
<el-switch v-model="shiftForm.nightShift" :active-value="1" :inactive-value="0" />
</el-form-item>
<el-form-item label="休息分钟" prop="restMinutes">
<el-input-number v-model="shiftForm.restMinutes" :min="0" :max="600" controls-position="right" style="width: 100%" />
</el-form-item>
<el-form-item label="备注">
<el-input v-model="shiftForm.remark" type="textarea" :rows="2" placeholder="备注" />
</el-form-item>
</el-form>
<div slot="footer">
<el-button @click="shiftDialogVisible = false">取消</el-button>
<el-button type="primary" :loading="shiftSubmitting" @click="submitShift">保存</el-button>
</div>
</el-dialog>
<!-- 排班弹窗 -->
<el-dialog :visible.sync="scheduleDialogVisible" title="排班" width="420px">
<el-form ref="scheduleForm" :model="scheduleForm" :rules="scheduleRules" label-width="90px" size="small">
<el-form-item label="日期" prop="workDate">
<el-date-picker v-model="scheduleForm.workDate" type="date" placeholder="选择日期" style="width: 100%" value-format="yyyy-MM-dd" />
</el-form-item>
<el-form-item label="员工" prop="empId">
<el-select v-model="scheduleForm.empId" placeholder="选择员工" filterable style="width: 100%">
<el-option v-for="emp in employeeOptions" :key="emp.empId" :label="`${emp.empName} (${emp.empCode || emp.empId})`" :value="emp.empId" />
</el-select>
</el-form-item>
<el-form-item label="班次" prop="shiftId">
<el-select v-model="scheduleForm.shiftId" placeholder="选择班次" filterable style="width: 100%">
<el-option v-for="shift in shiftList" :key="shift.shiftId" :label="`${shift.shiftName} ${shift.startTime || ''}-${shift.endTime || ''}`" :value="shift.shiftId" />
</el-select>
</el-form-item>
<el-form-item label="备注">
<el-input v-model="scheduleForm.remark" type="textarea" :rows="2" placeholder="备注" />
</el-form-item>
</el-form>
<div slot="footer">
<el-button @click="scheduleDialogVisible = false">取消</el-button>
<el-button type="primary" :loading="scheduleSubmitting" @click="submitSchedule">保存</el-button>
</div>
</el-dialog>
<!-- 打卡弹窗 -->
<el-dialog :visible.sync="punchDialogVisible" title="打卡" width="420px">
<el-form ref="punchForm" :model="punchForm" :rules="punchRules" label-width="90px" size="small">
<el-form-item label="员工" prop="empId">
<el-select v-model="punchForm.empId" placeholder="选择员工" filterable style="width: 100%">
<el-option v-for="emp in employeeOptions" :key="emp.empId" :label="`${emp.empName} (${emp.empCode || emp.empId})`" :value="emp.empId" />
</el-select>
</el-form-item>
<el-form-item label="时间" prop="punchTime">
<el-date-picker
v-model="punchForm.punchTime"
type="datetime"
placeholder="选择时间"
style="width: 100%"
value-format="yyyy-MM-dd HH:mm:ss"
/>
</el-form-item>
<el-form-item label="来源" prop="source">
<el-select v-model="punchForm.source" placeholder="选择来源" style="width: 100%">
<el-option label="手工" value="manual" />
<el-option label="设备" value="device" />
<el-option label="移动" value="mobile" />
</el-select>
</el-form-item>
<el-form-item label="定位/设备" prop="location">
<el-input v-model="punchForm.location" placeholder="如门禁A / GPS坐标" />
</el-form-item>
<el-form-item label="备注">
<el-input v-model="punchForm.remark" type="textarea" :rows="2" placeholder="备注" />
</el-form-item>
</el-form>
<div slot="footer">
<el-button @click="punchDialogVisible = false">取消</el-button>
<el-button type="primary" :loading="punchSubmitting" @click="submitPunch">保存</el-button>
</div>
</el-dialog>
</div>
</template>
<script>
import { listShift, listSchedule, listPunch, listAttendCalc } from '@/api/hrm'
import {
listEmployee,
listShift,
listSchedule,
listPunch,
listAttendCalc,
addShift,
updateShift,
delShift,
addSchedule,
updateSchedule,
delSchedule,
addPunch,
updatePunch,
delPunch
} from '@/api/hrm'
export default {
name: 'HrmAttendance',
data() {
return {
employeeOptions: [],
shiftList: [],
shiftLoading: false,
scheduleList: [],
@@ -109,10 +251,36 @@ export default {
punchLoading: false,
punchQuery: { range: [] },
attendList: [],
attendLoading: false
attendLoading: false,
shiftDialogVisible: false,
shiftForm: {},
shiftRules: {
shiftName: [{ required: true, message: '请输入名称', trigger: 'blur' }],
startTime: [{ required: true, message: '请选择开始时间', trigger: 'change' }],
endTime: [{ required: true, message: '请选择结束时间', trigger: 'change' }]
},
shiftSubmitting: false,
scheduleDialogVisible: false,
scheduleForm: {},
scheduleRules: {
workDate: [{ required: true, message: '请选择日期', trigger: 'change' }],
empId: [{ required: true, message: '请选择员工', trigger: 'change' }],
shiftId: [{ required: true, message: '请选择班次', trigger: 'change' }]
},
scheduleSubmitting: false,
punchDialogVisible: false,
punchForm: {},
punchRules: {
empId: [{ required: true, message: '请选择员工', trigger: 'change' }],
punchTime: [{ required: true, message: '请选择时间', trigger: 'change' }],
source: [{ required: true, message: '请选择来源', trigger: 'change' }],
location: [{ required: true, message: '请输入定位/设备', trigger: 'blur' }]
},
punchSubmitting: false
}
},
created() {
this.loadEmployees()
this.loadShift()
this.loadSchedule()
this.loadPunchAndAttend()
@@ -124,6 +292,12 @@ export default {
const pad = n => (n < 10 ? `0${n}` : n)
return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())} ${pad(date.getHours())}:${pad(date.getMinutes())}`
},
loadEmployees() {
listEmployee({ pageNum: 1, pageSize: 500 })
.then(res => {
this.employeeOptions = res.rows || []
})
},
loadShift() {
this.shiftLoading = true
listShift({ pageNum: 1, pageSize: 200 })
@@ -162,6 +336,112 @@ export default {
.finally(() => {
this.attendLoading = false
})
},
renderEmp(empId) {
const emp = this.employeeOptions.find(e => e.empId === empId)
if (!emp) return empId || ''
return `${emp.empName}${emp.empCode ? ` (${emp.empCode})` : ''}`
},
renderShift(shiftId) {
const shift = this.shiftList.find(s => s.shiftId === shiftId)
if (!shift) return shiftId || ''
return `${shift.shiftName}${shift.startTime ? ` ${shift.startTime}-${shift.endTime}` : ''}`
},
// 班次
openShiftDialog(row) {
this.shiftForm = row
? { ...row }
: { shiftName: '', startTime: '', endTime: '', nightShift: 0, restMinutes: 0, remark: '' }
this.shiftDialogVisible = true
this.$nextTick(() => this.$refs.shiftForm && this.$refs.shiftForm.clearValidate())
},
submitShift() {
this.$refs.shiftForm.validate(valid => {
if (!valid) return
this.shiftSubmitting = true
const api = this.shiftForm.shiftId ? updateShift : addShift
api(this.shiftForm)
.then(() => {
this.$message.success('已保存')
this.shiftDialogVisible = false
this.loadShift()
})
.finally(() => {
this.shiftSubmitting = false
})
})
},
delShift(row) {
this.$confirm('确认删除该班次吗?', '提示', { type: 'warning' }).then(() => {
delShift(row.shiftId).then(() => {
this.$message.success('已删除')
this.loadShift()
})
})
},
// 排班
openScheduleDialog(row) {
this.scheduleForm = row
? { ...row }
: { workDate: '', empId: '', shiftId: '', remark: '' }
this.scheduleDialogVisible = true
this.$nextTick(() => this.$refs.scheduleForm && this.$refs.scheduleForm.clearValidate())
},
submitSchedule() {
this.$refs.scheduleForm.validate(valid => {
if (!valid) return
this.scheduleSubmitting = true
const api = this.scheduleForm.scheduleId ? updateSchedule : addSchedule
api(this.scheduleForm)
.then(() => {
this.$message.success('已保存')
this.scheduleDialogVisible = false
this.loadSchedule()
})
.finally(() => {
this.scheduleSubmitting = false
})
})
},
delSchedule(row) {
this.$confirm('确认删除该排班吗?', '提示', { type: 'warning' }).then(() => {
delSchedule(row.scheduleId).then(() => {
this.$message.success('已删除')
this.loadSchedule()
})
})
},
// 打卡
openPunchDialog(row) {
this.punchForm = row
? { ...row }
: { empId: '', punchTime: '', source: 'manual', location: '', remark: '' }
this.punchDialogVisible = true
this.$nextTick(() => this.$refs.punchForm && this.$refs.punchForm.clearValidate())
},
submitPunch() {
this.$refs.punchForm.validate(valid => {
if (!valid) return
this.punchSubmitting = true
const api = this.punchForm.punchId ? updatePunch : addPunch
api(this.punchForm)
.then(() => {
this.$message.success('已保存')
this.punchDialogVisible = false
this.loadPunchAndAttend()
})
.finally(() => {
this.punchSubmitting = false
})
})
},
delPunch(row) {
this.$confirm('确认删除该打卡记录吗?', '提示', { type: 'warning' }).then(() => {
delPunch(row.punchId).then(() => {
this.$message.success('已删除')
this.loadPunchAndAttend()
})
})
}
}
}