hcm前端2版
This commit is contained in:
@@ -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()
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -218,6 +218,10 @@ export default {
|
||||
})
|
||||
},
|
||||
loadFlowActions() {
|
||||
if (this.flowQuery.instId && !/^\d+$/.test(this.flowQuery.instId)) {
|
||||
this.$message.warning('实例ID需为数字')
|
||||
return
|
||||
}
|
||||
this.flowLoading = true
|
||||
listFlowAction({ pageNum: 1, pageSize: 10, instId: this.flowQuery.instId })
|
||||
.then(res => {
|
||||
|
||||
@@ -48,7 +48,7 @@
|
||||
<el-button size="mini" type="primary" icon="el-icon-search" @click="loadEmployee">查询</el-button>
|
||||
</div>
|
||||
</div>
|
||||
<el-table :data="employeeList" v-loading="empLoading" height="700" stripe>
|
||||
<el-table :data="employeeList" v-loading="empLoading" height="700" stripe @row-click="openDetail">
|
||||
<el-table-column label="工号" prop="empNo" min-width="110" />
|
||||
<el-table-column label="姓名" prop="empName" min-width="120" />
|
||||
<el-table-column label="性别" prop="gender" min-width="80" />
|
||||
@@ -66,11 +66,248 @@
|
||||
</el-table>
|
||||
</el-card>
|
||||
</section>
|
||||
|
||||
<el-drawer
|
||||
title="员工档案"
|
||||
:visible.sync="detailVisible"
|
||||
size="60%"
|
||||
append-to-body
|
||||
>
|
||||
<div v-if="detailEmp" class="detail-wrap">
|
||||
<div class="basic-grid">
|
||||
<el-card shadow="hover" class="metal-panel">
|
||||
<div slot="header" class="panel-header">基础信息</div>
|
||||
<el-descriptions :column="2" size="small" border>
|
||||
<el-descriptions-item label="工号">{{ detailEmp.empNo }}</el-descriptions-item>
|
||||
<el-descriptions-item label="姓名">{{ detailEmp.empName }}</el-descriptions-item>
|
||||
<el-descriptions-item label="性别">{{ detailEmp.gender }}</el-descriptions-item>
|
||||
<el-descriptions-item label="手机">{{ detailEmp.mobile }}</el-descriptions-item>
|
||||
<el-descriptions-item label="雇佣类型">{{ detailEmp.employmentType }}</el-descriptions-item>
|
||||
<el-descriptions-item label="状态">
|
||||
<el-tag :type="statusType(detailEmp.status)">{{ detailEmp.status }}</el-tag>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="入职日期">{{ formatDate(detailEmp.hireDate) }}</el-descriptions-item>
|
||||
<el-descriptions-item label="备注">{{ detailEmp.remark || '-' }}</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
</el-card>
|
||||
</div>
|
||||
|
||||
<el-card shadow="hover" class="metal-panel" style="margin-top:12px">
|
||||
<div slot="header" class="panel-header">
|
||||
<span>组织/岗位关系</span>
|
||||
<el-button size="mini" type="primary" icon="el-icon-plus" @click="openRelDialog()">新增</el-button>
|
||||
</div>
|
||||
<el-table :data="relList" v-loading="relLoading" size="mini">
|
||||
<el-table-column label="组织" prop="orgId" min-width="160">
|
||||
<template slot-scope="scope">
|
||||
{{ renderOrg(scope.row.orgId) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="岗位" prop="positionId" min-width="150">
|
||||
<template slot-scope="scope">
|
||||
{{ renderPosition(scope.row.positionId) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="主岗" prop="isPrimary" width="80">
|
||||
<template slot-scope="scope">
|
||||
<el-tag size="mini" :type="scope.row.isPrimary ? 'success' : 'info'">{{ scope.row.isPrimary ? '是' : '否' }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="开始" prop="startDate" min-width="120">
|
||||
<template slot-scope="scope">{{ formatDate(scope.row.startDate, 'date') }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="结束" prop="endDate" min-width="120">
|
||||
<template slot-scope="scope">{{ formatDate(scope.row.endDate, 'date') }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="备注" prop="remark" min-width="140" show-overflow-tooltip />
|
||||
<el-table-column label="操作" width="140" fixed="right">
|
||||
<template slot-scope="scope">
|
||||
<el-button size="mini" type="text" @click="openRelDialog(scope.row)">编辑</el-button>
|
||||
<el-button size="mini" type="text" @click="delRel(scope.row)">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</el-card>
|
||||
|
||||
<el-card shadow="hover" class="metal-panel" style="margin-top:12px">
|
||||
<div slot="header" class="panel-header">
|
||||
<span>劳动合同</span>
|
||||
<el-button size="mini" type="primary" icon="el-icon-plus" @click="openContractDialog()">新增</el-button>
|
||||
</div>
|
||||
<el-table :data="contractList" v-loading="contractLoading" size="mini">
|
||||
<el-table-column label="编号" prop="contractNo" min-width="120" />
|
||||
<el-table-column label="类型" prop="contractType" min-width="120" />
|
||||
<el-table-column label="开始" prop="startDate" min-width="120">
|
||||
<template slot-scope="scope">{{ formatDate(scope.row.startDate, 'date') }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="结束" prop="endDate" min-width="120">
|
||||
<template slot-scope="scope">{{ formatDate(scope.row.endDate, 'date') }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="状态" prop="status" min-width="100" />
|
||||
<el-table-column label="备注" prop="remark" min-width="140" show-overflow-tooltip />
|
||||
<el-table-column label="操作" width="140" fixed="right">
|
||||
<template slot-scope="scope">
|
||||
<el-button size="mini" type="text" @click="openContractDialog(scope.row)">编辑</el-button>
|
||||
<el-button size="mini" type="text" @click="delContractRow(scope.row)">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</el-card>
|
||||
|
||||
<el-card shadow="hover" class="metal-panel" style="margin-top:12px">
|
||||
<div slot="header" class="panel-header">
|
||||
<span>证书</span>
|
||||
<el-button size="mini" type="primary" icon="el-icon-plus" @click="openCertDialog()">新增</el-button>
|
||||
</div>
|
||||
<el-table :data="certList" v-loading="certLoading" size="mini">
|
||||
<el-table-column label="名称" prop="certName" min-width="140" />
|
||||
<el-table-column label="证书编号" prop="certNo" min-width="140" />
|
||||
<el-table-column label="签发机构" prop="issuedBy" min-width="140" />
|
||||
<el-table-column label="有效期自" prop="validFrom" min-width="120">
|
||||
<template slot-scope="scope">{{ formatDate(scope.row.validFrom, 'date') }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="有效期至" prop="validTo" min-width="120">
|
||||
<template slot-scope="scope">{{ formatDate(scope.row.validTo, 'date') }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="备注" prop="remark" min-width="140" show-overflow-tooltip />
|
||||
<el-table-column label="操作" width="140" fixed="right">
|
||||
<template slot-scope="scope">
|
||||
<el-button size="mini" type="text" @click="openCertDialog(scope.row)">编辑</el-button>
|
||||
<el-button size="mini" type="text" @click="delCertRow(scope.row)">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</el-card>
|
||||
</div>
|
||||
|
||||
<div v-else class="preview-placeholder">请选择员工查看档案</div>
|
||||
</el-drawer>
|
||||
|
||||
<el-dialog
|
||||
title="合同"
|
||||
:visible.sync="contractDialogVisible"
|
||||
width="480px"
|
||||
append-to-body
|
||||
>
|
||||
<el-form :model="contractForm" label-width="100px" size="small">
|
||||
<el-form-item label="合同编号">
|
||||
<el-input v-model="contractForm.contractNo" />
|
||||
</el-form-item>
|
||||
<el-form-item label="类型">
|
||||
<el-input v-model="contractForm.contractType" />
|
||||
</el-form-item>
|
||||
<el-form-item label="开始日期">
|
||||
<el-date-picker v-model="contractForm.startDate" type="date" placeholder="开始" style="width: 100%" />
|
||||
</el-form-item>
|
||||
<el-form-item label="结束日期">
|
||||
<el-date-picker v-model="contractForm.endDate" type="date" placeholder="结束" style="width: 100%" />
|
||||
</el-form-item>
|
||||
<el-form-item label="状态">
|
||||
<el-input v-model="contractForm.status" />
|
||||
</el-form-item>
|
||||
<el-form-item label="备注">
|
||||
<el-input v-model="contractForm.remark" type="textarea" :rows="2" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<div slot="footer" class="dialog-footer">
|
||||
<el-button @click="contractDialogVisible = false">取消</el-button>
|
||||
<el-button type="primary" :loading="contractSubmitting" @click="submitContract">保存</el-button>
|
||||
</div>
|
||||
</el-dialog>
|
||||
|
||||
<el-dialog
|
||||
title="证书"
|
||||
:visible.sync="certDialogVisible"
|
||||
width="480px"
|
||||
append-to-body
|
||||
>
|
||||
<el-form :model="certForm" label-width="100px" size="small">
|
||||
<el-form-item label="证书名称">
|
||||
<el-input v-model="certForm.certName" />
|
||||
</el-form-item>
|
||||
<el-form-item label="证书编号">
|
||||
<el-input v-model="certForm.certNo" />
|
||||
</el-form-item>
|
||||
<el-form-item label="签发机构">
|
||||
<el-input v-model="certForm.issuedBy" />
|
||||
</el-form-item>
|
||||
<el-form-item label="有效期自">
|
||||
<el-date-picker v-model="certForm.validFrom" type="date" placeholder="开始" style="width: 100%" />
|
||||
</el-form-item>
|
||||
<el-form-item label="有效期至">
|
||||
<el-date-picker v-model="certForm.validTo" type="date" placeholder="结束" style="width: 100%" />
|
||||
</el-form-item>
|
||||
<el-form-item label="备注">
|
||||
<el-input v-model="certForm.remark" type="textarea" :rows="2" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<div slot="footer" class="dialog-footer">
|
||||
<el-button @click="certDialogVisible = false">取消</el-button>
|
||||
<el-button type="primary" :loading="certSubmitting" @click="submitCert">保存</el-button>
|
||||
</div>
|
||||
</el-dialog>
|
||||
|
||||
<el-dialog
|
||||
title="组织/岗位关系"
|
||||
:visible.sync="relDialogVisible"
|
||||
width="480px"
|
||||
append-to-body
|
||||
>
|
||||
<el-form ref="relFormRef" :model="relForm" :rules="relRules" label-width="100px" size="small">
|
||||
<el-form-item label="组织" prop="orgId">
|
||||
<el-select v-model="relForm.orgId" filterable placeholder="选择组织" style="width: 100%">
|
||||
<el-option
|
||||
v-for="org in flatOrgOptions"
|
||||
:key="org.orgId"
|
||||
:label="org.label"
|
||||
:value="org.orgId"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="岗位" prop="positionId">
|
||||
<el-select v-model="relForm.positionId" filterable placeholder="选择岗位" style="width: 100%">
|
||||
<el-option v-for="pos in positionOptions" :key="pos.positionId" :label="pos.positionName || pos.positionId" :value="pos.positionId" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="主岗" prop="isPrimary">
|
||||
<el-switch v-model="relForm.isPrimary" :active-value="1" :inactive-value="0" />
|
||||
</el-form-item>
|
||||
<el-form-item label="开始日期">
|
||||
<el-date-picker v-model="relForm.startDate" type="date" placeholder="开始" style="width: 100%" />
|
||||
</el-form-item>
|
||||
<el-form-item label="结束日期">
|
||||
<el-date-picker v-model="relForm.endDate" type="date" placeholder="结束" style="width: 100%" />
|
||||
</el-form-item>
|
||||
<el-form-item label="备注">
|
||||
<el-input v-model="relForm.remark" type="textarea" :rows="2" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<div slot="footer" class="dialog-footer">
|
||||
<el-button @click="relDialogVisible = false">取消</el-button>
|
||||
<el-button type="primary" :loading="relSubmitting" @click="submitRel">保存</el-button>
|
||||
</div>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { listOrg, listEmployee } from '@/api/hrm'
|
||||
import {
|
||||
listOrg,
|
||||
listEmployee,
|
||||
listPosition,
|
||||
listEmpOrgPosition,
|
||||
addEmpOrgPosition,
|
||||
updateEmpOrgPosition,
|
||||
delEmpOrgPosition,
|
||||
listContract,
|
||||
addContract,
|
||||
updateContract,
|
||||
delContract,
|
||||
listCertificate,
|
||||
addCertificate,
|
||||
updateCertificate,
|
||||
delCertificate
|
||||
} from '@/api/hrm'
|
||||
|
||||
export default {
|
||||
name: 'HrmOrgEmployee',
|
||||
@@ -81,22 +318,54 @@ export default {
|
||||
orgSelected: null,
|
||||
employeeList: [],
|
||||
empLoading: false,
|
||||
empQuery: { empName: '', status: undefined, mainOrgId: undefined }
|
||||
empQuery: { empName: '', status: undefined, mainOrgId: undefined },
|
||||
detailVisible: false,
|
||||
detailEmp: null,
|
||||
contractList: [],
|
||||
contractLoading: false,
|
||||
contractDialogVisible: false,
|
||||
contractSubmitting: false,
|
||||
contractForm: {},
|
||||
certList: [],
|
||||
certLoading: false,
|
||||
certDialogVisible: false,
|
||||
certSubmitting: false,
|
||||
certForm: {},
|
||||
relList: [],
|
||||
relLoading: false,
|
||||
relDialogVisible: false,
|
||||
relSubmitting: false,
|
||||
relForm: {},
|
||||
relRules: {
|
||||
orgId: [{ required: true, message: '请选择组织', trigger: 'change' }],
|
||||
positionId: [{ required: true, message: '请选择岗位', trigger: 'change' }],
|
||||
isPrimary: [{ required: true, message: '请选择是否主岗', trigger: 'change' }]
|
||||
},
|
||||
positionOptions: [],
|
||||
positionLoading: false,
|
||||
flatOrgOptions: []
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.loadOrg()
|
||||
this.loadEmployee()
|
||||
this.loadPositions()
|
||||
},
|
||||
methods: {
|
||||
loadTree() {
|
||||
// 兼容旧调用,直接复用 loadOrg
|
||||
this.loadOrg()
|
||||
},
|
||||
statusType(status) {
|
||||
if (!status) return 'info'
|
||||
const map = { pending: 'warning', draft: 'info', approved: 'success', rejected: 'danger', active: 'success', inactive: 'info' }
|
||||
return map[status] || 'info'
|
||||
},
|
||||
formatDate(val) {
|
||||
formatDate(val, mode = 'datetime') {
|
||||
if (!val) return ''
|
||||
const d = new Date(val)
|
||||
const p = n => (n < 10 ? `0${n}` : n)
|
||||
if (mode === 'date') return `${d.getFullYear()}-${p(d.getMonth() + 1)}-${p(d.getDate())}`
|
||||
return `${d.getFullYear()}-${p(d.getMonth() + 1)}-${p(d.getDate())} ${p(d.getHours())}:${p(d.getMinutes())}`
|
||||
},
|
||||
loadOrg() {
|
||||
@@ -107,6 +376,7 @@ export default {
|
||||
this.orgTree = this.buildTree(rows)
|
||||
if (!this.orgSelected && this.orgTree.length) this.orgSelected = this.orgTree[0].orgId
|
||||
this.empQuery.mainOrgId = this.orgSelected
|
||||
this.buildFlatOrgOptions(this.orgTree)
|
||||
this.loadEmployee()
|
||||
})
|
||||
.finally(() => {
|
||||
@@ -126,6 +396,17 @@ export default {
|
||||
})
|
||||
return roots
|
||||
},
|
||||
buildFlatOrgOptions(orgs) {
|
||||
const flat = []
|
||||
const walk = list => {
|
||||
list.forEach(o => {
|
||||
flat.push({ orgId: o.orgId, label: `${o.orgName || o.orgId}${o.parentName ? ` / ${o.parentName}` : ''}` })
|
||||
if (o.children && o.children.length) walk(o.children)
|
||||
})
|
||||
}
|
||||
walk(orgs || [])
|
||||
this.flatOrgOptions = flat
|
||||
},
|
||||
handleOrgClick(node) {
|
||||
this.orgSelected = node.orgId
|
||||
this.empQuery.mainOrgId = node.orgId
|
||||
@@ -141,6 +422,147 @@ export default {
|
||||
.finally(() => {
|
||||
this.empLoading = false
|
||||
})
|
||||
},
|
||||
openDetail(row) {
|
||||
this.detailEmp = row
|
||||
this.detailVisible = true
|
||||
this.loadContracts(row.empId)
|
||||
this.loadCertificates(row.empId)
|
||||
this.loadEmpOrgRel(row.empId)
|
||||
},
|
||||
loadContracts(empId) {
|
||||
this.contractLoading = true
|
||||
listContract({ empId, pageNum: 1, pageSize: 50 })
|
||||
.then(res => {
|
||||
this.contractList = res.rows || []
|
||||
})
|
||||
.finally(() => {
|
||||
this.contractLoading = false
|
||||
})
|
||||
},
|
||||
openContractDialog(row) {
|
||||
this.contractForm = row ? { ...row } : { empId: this.detailEmp?.empId, status: 'active' }
|
||||
this.contractDialogVisible = true
|
||||
},
|
||||
submitContract() {
|
||||
if (!this.detailEmp) return
|
||||
this.contractSubmitting = true
|
||||
const api = this.contractForm.contractId ? updateContract : addContract
|
||||
api({ ...this.contractForm, empId: this.detailEmp.empId })
|
||||
.then(() => {
|
||||
this.$message.success('已保存')
|
||||
this.contractDialogVisible = false
|
||||
this.loadContracts(this.detailEmp.empId)
|
||||
})
|
||||
.finally(() => {
|
||||
this.contractSubmitting = false
|
||||
})
|
||||
},
|
||||
delContractRow(row) {
|
||||
this.$confirm('确认删除该合同吗?', '提示', { type: 'warning' }).then(() => {
|
||||
delContract(row.contractId).then(() => {
|
||||
this.$message.success('已删除')
|
||||
this.loadContracts(this.detailEmp.empId)
|
||||
})
|
||||
})
|
||||
},
|
||||
loadCertificates(empId) {
|
||||
if (!empId) return
|
||||
this.certLoading = true
|
||||
listCertificate({ empId, pageNum: 1, pageSize: 200 })
|
||||
.then(res => {
|
||||
this.certList = res.rows || []
|
||||
})
|
||||
.finally(() => {
|
||||
this.certLoading = false
|
||||
})
|
||||
},
|
||||
loadPositions() {
|
||||
this.positionLoading = true
|
||||
listPosition({ pageNum: 1, pageSize: 200 })
|
||||
.then(res => {
|
||||
this.positionOptions = res.rows || []
|
||||
})
|
||||
.finally(() => {
|
||||
this.positionLoading = false
|
||||
})
|
||||
},
|
||||
loadEmpOrgRel(empId) {
|
||||
if (!empId) return
|
||||
this.relLoading = true
|
||||
listEmpOrgPosition({ empId, pageNum: 1, pageSize: 200 })
|
||||
.then(res => {
|
||||
this.relList = res.rows || []
|
||||
})
|
||||
.finally(() => {
|
||||
this.relLoading = false
|
||||
})
|
||||
},
|
||||
openRelDialog(row) {
|
||||
if (!this.detailEmp) return
|
||||
this.relForm = row
|
||||
? { ...row }
|
||||
: {
|
||||
empId: this.detailEmp.empId,
|
||||
orgId: this.detailEmp.mainOrgId || this.orgSelected || (this.orgTree[0] && this.orgTree[0].orgId),
|
||||
positionId: '',
|
||||
isPrimary: 0,
|
||||
startDate: '',
|
||||
endDate: '',
|
||||
remark: ''
|
||||
}
|
||||
this.relDialogVisible = true
|
||||
this.$nextTick(() => this.$refs.relFormRef && this.$refs.relFormRef.clearValidate())
|
||||
},
|
||||
submitRel() {
|
||||
this.$refs.relFormRef.validate(valid => {
|
||||
if (!valid) return
|
||||
this.relSubmitting = true
|
||||
const api = this.relForm.relId ? updateEmpOrgPosition : addEmpOrgPosition
|
||||
api(this.relForm)
|
||||
.then(() => {
|
||||
this.$message.success('已保存')
|
||||
this.relDialogVisible = false
|
||||
this.loadEmpOrgRel(this.detailEmp.empId)
|
||||
})
|
||||
.finally(() => {
|
||||
this.relSubmitting = false
|
||||
})
|
||||
})
|
||||
},
|
||||
delRel(row) {
|
||||
this.$confirm('确认删除该关系吗?', '提示', { type: 'warning' }).then(() => {
|
||||
delEmpOrgPosition(row.relId).then(() => {
|
||||
this.$message.success('已删除')
|
||||
this.loadEmpOrgRel(this.detailEmp.empId)
|
||||
})
|
||||
})
|
||||
},
|
||||
openCertDialog(row) {
|
||||
this.certForm = row ? { ...row } : { empId: this.detailEmp?.empId }
|
||||
this.certDialogVisible = true
|
||||
},
|
||||
submitCert() {
|
||||
if (!this.detailEmp) return
|
||||
this.certSubmitting = true
|
||||
const api = this.certForm.certId ? updateCertificate : addCertificate
|
||||
api({ ...this.certForm, empId: this.detailEmp.empId })
|
||||
.then(() => {
|
||||
this.$message.success('已保存')
|
||||
this.certDialogVisible = false
|
||||
this.loadCertificates(this.detailEmp.empId)
|
||||
})
|
||||
.finally(() => {
|
||||
this.certSubmitting = false
|
||||
})
|
||||
},
|
||||
delCertRow(row) {
|
||||
this.$confirm('确认删除该证书吗?', '提示', { type: 'warning' }).then(() => {
|
||||
delCertificate(row.certId).then(() => {
|
||||
this.$message.success('已删除')
|
||||
this.loadCertificates(this.detailEmp.empId)
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -173,6 +595,22 @@ export default {
|
||||
gap: 8px;
|
||||
align-items: center;
|
||||
}
|
||||
.detail-wrap {
|
||||
padding-right: 4px;
|
||||
}
|
||||
.basic-grid {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr;
|
||||
gap: 12px;
|
||||
}
|
||||
.preview-placeholder {
|
||||
color: #a0a3ad;
|
||||
font-size: 13px;
|
||||
padding: 12px;
|
||||
background: #fafafa;
|
||||
border: 1px dashed #ebeef5;
|
||||
border-radius: 6px;
|
||||
}
|
||||
.custom-tree-node {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
@@ -1,10 +1,40 @@
|
||||
<template>
|
||||
<div class="hrm-page">
|
||||
<section class="stats-grid">
|
||||
<el-card class="metal-panel kpi-card" shadow="hover">
|
||||
<div class="kpi-title">薪酬方案数</div>
|
||||
<div class="kpi-value">{{ payPlanList.length }}</div>
|
||||
<div class="kpi-sub">启用 {{ payPlanList.filter(i => i.status === 'active').length }} 个</div>
|
||||
</el-card>
|
||||
<el-card class="metal-panel kpi-card" shadow="hover">
|
||||
<div class="kpi-title">薪酬批次数</div>
|
||||
<div class="kpi-value">{{ payRunList.length }}</div>
|
||||
<div class="kpi-sub">审批中 {{ payRunList.filter(i => i.status === 'pending').length }} 个</div>
|
||||
</el-card>
|
||||
<el-card class="metal-panel kpi-card" shadow="hover">
|
||||
<div class="kpi-title">工资条总额(应发)</div>
|
||||
<div class="kpi-value">¥{{ formatNumber(payslipList.reduce((s, i) => s + (Number(i.amountGross) || 0), 0)) }}</div>
|
||||
<div class="kpi-sub">实发 ¥{{ formatNumber(payslipList.reduce((s, i) => s + (Number(i.amountNet) || 0), 0)) }}</div>
|
||||
</el-card>
|
||||
<el-card class="metal-panel kpi-card" shadow="hover">
|
||||
<div class="kpi-title">指标快照</div>
|
||||
<div class="kpi-value">{{ statList.length }}</div>
|
||||
<div class="kpi-sub">最新日期:{{ latestStatDate || '-' }}</div>
|
||||
</el-card>
|
||||
</section>
|
||||
|
||||
<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="loadPayPlan">刷新</el-button>
|
||||
<div class="actions-inline">
|
||||
<el-select v-model="payPlanQuery.status" size="mini" placeholder="状态" clearable style="width:120px" @change="loadPayPlan">
|
||||
<el-option label="启用" value="active" />
|
||||
<el-option label="草稿/停用" value="inactive" />
|
||||
</el-select>
|
||||
<el-button size="mini" type="primary" icon="el-icon-plus" @click="openPlanDialog()">新增</el-button>
|
||||
<el-button size="mini" icon="el-icon-refresh" @click="loadPayPlan">刷新</el-button>
|
||||
</div>
|
||||
</div>
|
||||
<el-table :data="payPlanList" v-loading="payPlanLoading" height="320" stripe>
|
||||
<el-table-column label="方案" prop="planName" min-width="140" />
|
||||
@@ -15,13 +45,26 @@
|
||||
</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="openPlanDialog(scope.row)">编辑</el-button>
|
||||
<el-button size="mini" type="text" @click="delPlan(scope.row)">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</el-card>
|
||||
|
||||
<el-card class="metal-panel" shadow="hover">
|
||||
<div slot="header" class="panel-header">
|
||||
<span>薪酬批次</span>
|
||||
<el-button size="mini" icon="el-icon-refresh" @click="loadPayRun">刷新</el-button>
|
||||
<div class="actions-inline">
|
||||
<el-select v-model="payRunQuery.status" size="mini" placeholder="状态" clearable style="width:120px" @change="loadPayRun">
|
||||
<el-option label="审批中" value="pending" />
|
||||
<el-option label="已完成" value="approved" />
|
||||
</el-select>
|
||||
<el-button size="mini" type="primary" icon="el-icon-plus" @click="openRunDialog()">新增</el-button>
|
||||
<el-button size="mini" icon="el-icon-refresh" @click="loadPayRun">刷新</el-button>
|
||||
</div>
|
||||
</div>
|
||||
<el-table :data="payRunList" v-loading="payRunLoading" height="320" stripe>
|
||||
<el-table-column label="批次" prop="runName" min-width="140" />
|
||||
@@ -32,6 +75,12 @@
|
||||
</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="openRunDialog(scope.row)">编辑</el-button>
|
||||
<el-button size="mini" type="text" @click="delRun(scope.row)">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</el-card>
|
||||
|
||||
@@ -47,14 +96,24 @@
|
||||
clearable
|
||||
@keyup.enter.native="loadPayslip"
|
||||
/>
|
||||
<el-input
|
||||
v-model="payslipQuery.empName"
|
||||
placeholder="员工姓名/工号"
|
||||
size="mini"
|
||||
style="width: 150px"
|
||||
clearable
|
||||
@keyup.enter.native="loadPayslip"
|
||||
/>
|
||||
<el-button size="mini" type="primary" @click="loadPayslip">查询</el-button>
|
||||
<el-button size="mini" type="primary" icon="el-icon-plus" @click="openPayslipDialog()">新增</el-button>
|
||||
<el-button size="mini" icon="el-icon-download" :loading="exportLoading" @click="exportPayslip">导出</el-button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="dual-tables">
|
||||
<div class="table-half">
|
||||
<div class="table-title">工资条</div>
|
||||
<el-table :data="payslipList" v-loading="payslipLoading" height="230" stripe>
|
||||
<el-table-column label="员工" prop="empId" min-width="100" />
|
||||
<el-table :data="payslipList" v-loading="payslipLoading" height="230" stripe @row-click="openPayslipDetail">
|
||||
<el-table-column label="员工" prop="empName" min-width="120" />
|
||||
<el-table-column label="应发" prop="amountGross" min-width="100">
|
||||
<template slot-scope="scope">¥{{ formatNumber(scope.row.amountGross) }}</template>
|
||||
</el-table-column>
|
||||
@@ -66,11 +125,24 @@
|
||||
<el-tag :type="statusType(scope.row.status)" size="mini">{{ scope.row.status || '-' }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="140" fixed="right">
|
||||
<template slot-scope="scope">
|
||||
<el-button size="mini" type="text" @click.stop="openPayslipDialog(scope.row)">编辑</el-button>
|
||||
<el-button size="mini" type="text" @click.stop="delPayslipRow(scope.row)">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</div>
|
||||
<div class="table-half">
|
||||
<div class="table-title">指标快照</div>
|
||||
<el-table :data="statList" v-loading="statLoading" height="230" stripe>
|
||||
<div class="actions-inline" style="margin-bottom:6px">
|
||||
<el-select v-model="statQuery.statType" size="mini" placeholder="类型" clearable style="width:140px" @change="loadStatSnapshot">
|
||||
<el-option label="工资成本" value="cost" />
|
||||
<el-option label="人均成本" value="per_capita_cost" />
|
||||
<el-option label="奖金" value="bonus" />
|
||||
</el-select>
|
||||
</div>
|
||||
<el-table :data="statList" v-loading="statLoading" height="200" stripe>
|
||||
<el-table-column label="类型" prop="statType" min-width="120" />
|
||||
<el-table-column label="维度" prop="dimension" min-width="120" show-overflow-tooltip />
|
||||
<el-table-column label="数值" prop="value" min-width="100" />
|
||||
@@ -80,11 +152,137 @@
|
||||
</div>
|
||||
</el-card>
|
||||
</section>
|
||||
|
||||
<el-dialog title="工资条详情" :visible.sync="payslipDetailVisible" width="520px" append-to-body>
|
||||
<el-descriptions :column="2" size="small" border>
|
||||
<el-descriptions-item label="员工">{{ payslipDetail.empName || payslipDetail.empId }}</el-descriptions-item>
|
||||
<el-descriptions-item label="批次">{{ payslipDetail.batchNo || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="应发">¥{{ formatNumber(payslipDetail.amountGross) }}</el-descriptions-item>
|
||||
<el-descriptions-item label="实发">¥{{ formatNumber(payslipDetail.amountNet) }}</el-descriptions-item>
|
||||
<el-descriptions-item label="状态">{{ payslipDetail.status || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="周期">{{ payslipDetail.period || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="发放日期">{{ payslipDetail.payDate || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="备注">{{ payslipDetail.remark || '-' }}</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
<div slot="footer" class="dialog-footer">
|
||||
<el-button @click="payslipDetailVisible = false">关闭</el-button>
|
||||
</div>
|
||||
</el-dialog>
|
||||
|
||||
<!-- 方案弹窗 -->
|
||||
<el-dialog title="薪酬方案" :visible.sync="planDialogVisible" width="480px" append-to-body>
|
||||
<el-form ref="planFormRef" :model="planForm" :rules="planRules" label-width="100px" size="small">
|
||||
<el-form-item label="方案名称" prop="planName">
|
||||
<el-input v-model="planForm.planName" />
|
||||
</el-form-item>
|
||||
<el-form-item label="币种">
|
||||
<el-input v-model="planForm.currency" placeholder="如:CNY" />
|
||||
</el-form-item>
|
||||
<el-form-item label="状态">
|
||||
<el-select v-model="planForm.status" placeholder="选择状态" style="width: 100%">
|
||||
<el-option label="启用" value="active" />
|
||||
<el-option label="停用/草稿" value="inactive" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="备注">
|
||||
<el-input v-model="planForm.remark" type="textarea" :rows="2" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<div slot="footer">
|
||||
<el-button @click="planDialogVisible = false">取消</el-button>
|
||||
<el-button type="primary" :loading="planSubmitting" @click="submitPlan">保存</el-button>
|
||||
</div>
|
||||
</el-dialog>
|
||||
|
||||
<!-- 批次弹窗 -->
|
||||
<el-dialog title="薪酬批次" :visible.sync="runDialogVisible" width="480px" append-to-body>
|
||||
<el-form ref="runFormRef" :model="runForm" :rules="runRules" label-width="100px" size="small">
|
||||
<el-form-item label="批次名称" prop="runName">
|
||||
<el-input v-model="runForm.runName" />
|
||||
</el-form-item>
|
||||
<el-form-item label="核算周期" prop="period">
|
||||
<el-input v-model="runForm.period" placeholder="如 2024-08" />
|
||||
</el-form-item>
|
||||
<el-form-item label="关联方案" prop="planId">
|
||||
<el-select v-model="runForm.planId" placeholder="选择方案" filterable style="width: 100%">
|
||||
<el-option v-for="plan in payPlanList" :key="plan.planId" :label="plan.planName" :value="plan.planId" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="状态">
|
||||
<el-select v-model="runForm.status" placeholder="选择状态" style="width: 100%">
|
||||
<el-option label="审批中" value="pending" />
|
||||
<el-option label="已完成" value="approved" />
|
||||
<el-option label="草稿" value="draft" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="备注">
|
||||
<el-input v-model="runForm.remark" type="textarea" :rows="2" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<div slot="footer">
|
||||
<el-button @click="runDialogVisible = false">取消</el-button>
|
||||
<el-button type="primary" :loading="runSubmitting" @click="submitRun">保存</el-button>
|
||||
</div>
|
||||
</el-dialog>
|
||||
|
||||
<!-- 工资条弹窗 -->
|
||||
<el-dialog title="工资条" :visible.sync="payslipDialogVisible" width="520px" append-to-body>
|
||||
<el-form ref="payslipFormRef" :model="payslipForm" :rules="payslipRules" label-width="110px" size="small">
|
||||
<el-form-item label="员工" prop="empId">
|
||||
<el-select v-model="payslipForm.empId" filterable placeholder="选择员工" style="width: 100%" :loading="employeeLoading">
|
||||
<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="batchNo">
|
||||
<el-input v-model="payslipForm.batchNo" />
|
||||
</el-form-item>
|
||||
<el-form-item label="应发金额" prop="amountGross">
|
||||
<el-input-number v-model="payslipForm.amountGross" :min="0" :step="100" style="width:100%" />
|
||||
</el-form-item>
|
||||
<el-form-item label="实发金额" prop="amountNet">
|
||||
<el-input-number v-model="payslipForm.amountNet" :min="0" :step="100" style="width:100%" />
|
||||
</el-form-item>
|
||||
<el-form-item label="周期" prop="period">
|
||||
<el-input v-model="payslipForm.period" placeholder="如 2024-08" />
|
||||
</el-form-item>
|
||||
<el-form-item label="状态">
|
||||
<el-select v-model="payslipForm.status" placeholder="选择状态" style="width: 100%">
|
||||
<el-option label="待发" value="pending" />
|
||||
<el-option label="已发" value="paid" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="备注">
|
||||
<el-input v-model="payslipForm.remark" type="textarea" :rows="2" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<div slot="footer">
|
||||
<el-button @click="payslipDialogVisible = false">取消</el-button>
|
||||
<el-button type="primary" :loading="payslipSubmitting" @click="submitPayslip">保存</el-button>
|
||||
</div>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { listPayPlan, listPayRun, listPayslip, listStatSnapshot } from '@/api/hrm'
|
||||
import {
|
||||
listPayPlan,
|
||||
getPayPlan,
|
||||
addPayPlan,
|
||||
updatePayPlan,
|
||||
delPayPlan,
|
||||
listPayRun,
|
||||
getPayRun,
|
||||
addPayRun,
|
||||
updatePayRun,
|
||||
delPayRun,
|
||||
listPayslip,
|
||||
getPayslip,
|
||||
addPayslip,
|
||||
updatePayslip,
|
||||
delPayslip,
|
||||
listEmployee,
|
||||
listStatSnapshot
|
||||
} from '@/api/hrm'
|
||||
|
||||
export default {
|
||||
name: 'HrmPayroll',
|
||||
@@ -92,13 +290,42 @@ export default {
|
||||
return {
|
||||
payPlanList: [],
|
||||
payPlanLoading: false,
|
||||
payPlanQuery: { status: undefined },
|
||||
payRunList: [],
|
||||
payRunLoading: false,
|
||||
payRunQuery: { status: undefined },
|
||||
payslipList: [],
|
||||
payslipLoading: false,
|
||||
payslipQuery: { batchNo: '' },
|
||||
payslipQuery: { batchNo: '', empName: '' },
|
||||
statList: [],
|
||||
statLoading: false
|
||||
statLoading: false,
|
||||
statQuery: { statType: undefined },
|
||||
payslipDetailVisible: false,
|
||||
payslipDetail: {},
|
||||
employeeOptions: [],
|
||||
employeeLoading: false,
|
||||
exportLoading: false,
|
||||
planDialogVisible: false,
|
||||
planSubmitting: false,
|
||||
planForm: {},
|
||||
planRules: { planName: [{ required: true, message: '请输入方案名称', trigger: 'blur' }] },
|
||||
runDialogVisible: false,
|
||||
runSubmitting: false,
|
||||
runForm: {},
|
||||
runRules: {
|
||||
runName: [{ required: true, message: '请输入批次名称', trigger: 'blur' }],
|
||||
period: [{ required: true, message: '请输入核算周期', trigger: 'blur' }],
|
||||
planId: [{ required: true, message: '请选择方案', trigger: 'change' }]
|
||||
},
|
||||
payslipDialogVisible: false,
|
||||
payslipSubmitting: false,
|
||||
payslipForm: {},
|
||||
payslipRules: {
|
||||
empId: [{ required: true, message: '请输入员工ID', trigger: 'blur' }],
|
||||
amountGross: [{ required: true, message: '请输入应发金额', trigger: 'blur' }],
|
||||
amountNet: [{ required: true, message: '请输入实发金额', trigger: 'blur' }],
|
||||
batchNo: [{ required: true, message: '请输入批次号', trigger: 'blur' }]
|
||||
}
|
||||
}
|
||||
},
|
||||
created() {
|
||||
@@ -106,6 +333,7 @@ export default {
|
||||
this.loadPayRun()
|
||||
this.loadPayslip()
|
||||
this.loadStatSnapshot()
|
||||
this.loadEmployees()
|
||||
},
|
||||
methods: {
|
||||
statusType(status) {
|
||||
@@ -119,7 +347,7 @@ export default {
|
||||
},
|
||||
loadPayPlan() {
|
||||
this.payPlanLoading = true
|
||||
listPayPlan({ pageNum: 1, pageSize: 50 })
|
||||
listPayPlan({ pageNum: 1, pageSize: 50, status: this.payPlanQuery.status })
|
||||
.then(res => {
|
||||
this.payPlanList = res.rows || []
|
||||
})
|
||||
@@ -129,7 +357,7 @@ export default {
|
||||
},
|
||||
loadPayRun() {
|
||||
this.payRunLoading = true
|
||||
listPayRun({ pageNum: 1, pageSize: 50 })
|
||||
listPayRun({ pageNum: 1, pageSize: 50, status: this.payRunQuery.status })
|
||||
.then(res => {
|
||||
this.payRunList = res.rows || []
|
||||
})
|
||||
@@ -139,7 +367,7 @@ export default {
|
||||
},
|
||||
loadPayslip() {
|
||||
this.payslipLoading = true
|
||||
listPayslip({ pageNum: 1, pageSize: 50, batchNo: this.payslipQuery.batchNo })
|
||||
listPayslip({ pageNum: 1, pageSize: 50, batchNo: this.payslipQuery.batchNo, empName: this.payslipQuery.empName })
|
||||
.then(res => {
|
||||
this.payslipList = res.rows || []
|
||||
})
|
||||
@@ -149,13 +377,169 @@ export default {
|
||||
},
|
||||
loadStatSnapshot() {
|
||||
this.statLoading = true
|
||||
listStatSnapshot({ pageNum: 1, pageSize: 50 })
|
||||
listStatSnapshot({ pageNum: 1, pageSize: 50, statType: this.statQuery.statType })
|
||||
.then(res => {
|
||||
this.statList = res.rows || []
|
||||
})
|
||||
.finally(() => {
|
||||
this.statLoading = false
|
||||
})
|
||||
},
|
||||
loadEmployees() {
|
||||
this.employeeLoading = true
|
||||
listEmployee({ pageNum: 1, pageSize: 500 })
|
||||
.then(res => {
|
||||
this.employeeOptions = res.rows || []
|
||||
})
|
||||
.finally(() => {
|
||||
this.employeeLoading = false
|
||||
})
|
||||
},
|
||||
openPayslipDetail(row) {
|
||||
this.payslipDetail = { ...row }
|
||||
this.payslipDetailVisible = true
|
||||
},
|
||||
// 方案 CRUD
|
||||
openPlanDialog(row) {
|
||||
this.planForm = row ? { ...row } : { planName: '', currency: 'CNY', status: 'active', remark: '' }
|
||||
this.planDialogVisible = true
|
||||
this.$nextTick(() => this.$refs.planFormRef && this.$refs.planFormRef.clearValidate())
|
||||
},
|
||||
submitPlan() {
|
||||
this.$refs.planFormRef.validate(valid => {
|
||||
if (!valid) return
|
||||
this.planSubmitting = true
|
||||
const api = this.planForm.planId ? updatePayPlan : addPayPlan
|
||||
api(this.planForm)
|
||||
.then(() => {
|
||||
this.$message.success('已保存')
|
||||
this.planDialogVisible = false
|
||||
this.loadPayPlan()
|
||||
})
|
||||
.finally(() => {
|
||||
this.planSubmitting = false
|
||||
})
|
||||
})
|
||||
},
|
||||
delPlan(row) {
|
||||
this.$confirm('确认删除该方案吗?', '提示', { type: 'warning' }).then(() => {
|
||||
delPayPlan(row.planId).then(() => {
|
||||
this.$message.success('已删除')
|
||||
this.loadPayPlan()
|
||||
})
|
||||
})
|
||||
},
|
||||
// 批次 CRUD
|
||||
openRunDialog(row) {
|
||||
this.runForm = row
|
||||
? { ...row }
|
||||
: { runName: '', period: '', planId: this.payPlanList[0]?.planId, status: 'pending', remark: '' }
|
||||
this.runDialogVisible = true
|
||||
this.$nextTick(() => this.$refs.runFormRef && this.$refs.runFormRef.clearValidate())
|
||||
},
|
||||
submitRun() {
|
||||
this.$refs.runFormRef.validate(valid => {
|
||||
if (!valid) return
|
||||
this.runSubmitting = true
|
||||
const api = this.runForm.runId ? updatePayRun : addPayRun
|
||||
api(this.runForm)
|
||||
.then(() => {
|
||||
this.$message.success('已保存')
|
||||
this.runDialogVisible = false
|
||||
this.loadPayRun()
|
||||
})
|
||||
.finally(() => {
|
||||
this.runSubmitting = false
|
||||
})
|
||||
})
|
||||
},
|
||||
delRun(row) {
|
||||
this.$confirm('确认删除该批次吗?', '提示', { type: 'warning' }).then(() => {
|
||||
delPayRun(row.runId).then(() => {
|
||||
this.$message.success('已删除')
|
||||
this.loadPayRun()
|
||||
})
|
||||
})
|
||||
},
|
||||
// 工资条 CRUD
|
||||
openPayslipDialog(row) {
|
||||
this.payslipForm = row
|
||||
? { ...row }
|
||||
: {
|
||||
empId: '',
|
||||
batchNo: '',
|
||||
amountGross: 0,
|
||||
amountNet: 0,
|
||||
period: '',
|
||||
status: 'pending',
|
||||
remark: ''
|
||||
}
|
||||
this.payslipDialogVisible = true
|
||||
this.$nextTick(() => this.$refs.payslipFormRef && this.$refs.payslipFormRef.clearValidate())
|
||||
},
|
||||
submitPayslip() {
|
||||
this.$refs.payslipFormRef.validate(valid => {
|
||||
if (!valid) return
|
||||
this.payslipSubmitting = true
|
||||
const api = this.payslipForm.slipId ? updatePayslip : addPayslip
|
||||
api(this.payslipForm)
|
||||
.then(() => {
|
||||
this.$message.success('已保存')
|
||||
this.payslipDialogVisible = false
|
||||
this.loadPayslip()
|
||||
})
|
||||
.finally(() => {
|
||||
this.payslipSubmitting = false
|
||||
})
|
||||
})
|
||||
},
|
||||
delPayslipRow(row) {
|
||||
this.$confirm('确认删除该工资条吗?', '提示', { type: 'warning' }).then(() => {
|
||||
delPayslip(row.slipId).then(() => {
|
||||
this.$message.success('已删除')
|
||||
this.loadPayslip()
|
||||
})
|
||||
})
|
||||
},
|
||||
exportPayslip() {
|
||||
if (!this.payslipList.length) {
|
||||
this.$message.info('暂无可导出的数据')
|
||||
return
|
||||
}
|
||||
this.exportLoading = true
|
||||
try {
|
||||
const headers = ['员工', '批次号', '应发', '实发', '周期', '状态', '发放日期', '备注']
|
||||
const rows = this.payslipList.map(i => [
|
||||
i.empName || i.empId || '',
|
||||
i.batchNo || '',
|
||||
i.amountGross || '',
|
||||
i.amountNet || '',
|
||||
i.period || '',
|
||||
i.status || '',
|
||||
i.payDate || '',
|
||||
(i.remark || '').replace(/\r?\n/g, ' ')
|
||||
])
|
||||
const tableRows = [headers, ...rows]
|
||||
.map(
|
||||
r =>
|
||||
'<tr>' +
|
||||
r
|
||||
.map(v => `<td style="mso-number-format:'\\@';">${String(v).replace(/</g, '<').replace(/>/g, '>')}</td>`)
|
||||
.join('') +
|
||||
'</tr>'
|
||||
)
|
||||
.join('')
|
||||
const html = `<html xmlns:o="urn:schemas-microsoft-com:office:office" xmlns:x="urn:schemas-microsoft-com:office:excel" xmlns="http://www.w3.org/TR/REC-html40"><head><meta charset="UTF-8"></head><body><table>${tableRows}</table></body></html>`
|
||||
const blob = new Blob([html], { type: 'application/vnd.ms-excel;charset=utf-8;' })
|
||||
const url = URL.createObjectURL(blob)
|
||||
const a = document.createElement('a')
|
||||
a.href = url
|
||||
a.download = `payslips_${Date.now()}.xls`
|
||||
a.click()
|
||||
URL.revokeObjectURL(url)
|
||||
} finally {
|
||||
this.exportLoading = false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -166,6 +550,12 @@ export default {
|
||||
padding: 16px 20px 32px;
|
||||
background: #f8f9fb;
|
||||
}
|
||||
.stats-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, 1fr);
|
||||
gap: 12px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
.panel-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
@@ -188,6 +578,24 @@ export default {
|
||||
gap: 8px;
|
||||
align-items: center;
|
||||
}
|
||||
.kpi-card {
|
||||
.kpi-title {
|
||||
font-size: 13px;
|
||||
color: #7a7d85;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
.kpi-value {
|
||||
font-size: 22px;
|
||||
font-weight: 700;
|
||||
color: #303133;
|
||||
line-height: 1.3;
|
||||
}
|
||||
.kpi-sub {
|
||||
font-size: 12px;
|
||||
color: #909399;
|
||||
margin-top: 2px;
|
||||
}
|
||||
}
|
||||
.dual-tables {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
@@ -203,6 +611,9 @@ export default {
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
@media (max-width: 1200px) {
|
||||
.stats-grid {
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
}
|
||||
.panel-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
@@ -1,19 +1,20 @@
|
||||
<template>
|
||||
<div class="hrm-page">
|
||||
<section class="panel-grid quad">
|
||||
<el-card v-for="item in requestBlocks" :key="item.key" class="metal-panel" shadow="hover">
|
||||
<div slot="header" class="panel-header">
|
||||
<span>{{ item.title }}</span>
|
||||
<div class="actions-inline">
|
||||
<el-select
|
||||
v-if="item.statusField"
|
||||
v-model="item.query.status"
|
||||
size="mini"
|
||||
placeholder="状态"
|
||||
clearable
|
||||
style="width: 120px"
|
||||
@change="item.loader"
|
||||
>
|
||||
<el-card v-for="item in requestBlocks" :key="item.key" class="metal-panel" shadow="hover">
|
||||
<div slot="header" class="panel-header">
|
||||
<span>{{ item.title }}</span>
|
||||
<div class="actions-inline">
|
||||
<el-button size="mini" type="primary" plain icon="el-icon-plus" @click="openCreate(item.key)">新增</el-button>
|
||||
<el-select
|
||||
v-if="item.statusField"
|
||||
v-model="item.query.status"
|
||||
size="mini"
|
||||
placeholder="状态"
|
||||
clearable
|
||||
style="width: 120px"
|
||||
@change="item.loader"
|
||||
>
|
||||
<el-option label="草稿" value="draft" />
|
||||
<el-option label="审批中" value="pending" />
|
||||
<el-option label="通过" value="approved" />
|
||||
@@ -129,6 +130,107 @@
|
||||
<el-button type="primary" :loading="stampSubmitting" @click="submitStamp">盖章</el-button>
|
||||
</div>
|
||||
</el-dialog>
|
||||
|
||||
<el-dialog
|
||||
title="新增申请"
|
||||
:visible.sync="createDialogVisible"
|
||||
width="520px"
|
||||
append-to-body
|
||||
>
|
||||
<div class="create-type-switch">
|
||||
<el-radio-group v-model="createType" size="small" @change="() => (createForm = defaultCreateForm(createType))">
|
||||
<el-radio-button label="leave">请假</el-radio-button>
|
||||
<el-radio-button label="overtime">加班</el-radio-button>
|
||||
<el-radio-button label="travel">出差</el-radio-button>
|
||||
<el-radio-button label="seal">用印</el-radio-button>
|
||||
</el-radio-group>
|
||||
<span class="hint-text" style="margin-left:8px">先选类型,再填写必填项</span>
|
||||
</div>
|
||||
<el-form ref="createFormRef" :model="createForm" :rules="currentCreateRules" label-width="110px" size="small">
|
||||
<el-form-item label="申请人">
|
||||
<el-select
|
||||
v-model="createForm.empId"
|
||||
filterable
|
||||
clearable
|
||||
placeholder="选择员工"
|
||||
style="width: 100%"
|
||||
>
|
||||
<el-option
|
||||
v-for="emp in employees"
|
||||
:key="emp.empId"
|
||||
:label="`${emp.empName || emp.empNo || emp.empId}`"
|
||||
:value="emp.empId"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<template v-if="createType === 'leave'">
|
||||
<el-form-item label="请假类型" prop="leaveType">
|
||||
<el-input v-model="createForm.leaveType" placeholder="年假/事假等" />
|
||||
</el-form-item>
|
||||
<el-form-item label="开始时间" prop="startTime">
|
||||
<el-date-picker v-model="createForm.startTime" type="datetime" placeholder="开始" style="width: 100%" />
|
||||
</el-form-item>
|
||||
<el-form-item label="结束时间" prop="endTime">
|
||||
<el-date-picker v-model="createForm.endTime" type="datetime" placeholder="结束" style="width: 100%" />
|
||||
</el-form-item>
|
||||
<el-form-item label="时长(小时)" prop="hours">
|
||||
<el-input-number v-model="createForm.hours" :min="0.5" :step="0.5" />
|
||||
</el-form-item>
|
||||
<el-form-item label="事由" prop="reason">
|
||||
<el-input v-model="createForm.reason" type="textarea" :rows="2" />
|
||||
</el-form-item>
|
||||
</template>
|
||||
<template v-else-if="createType === 'overtime'">
|
||||
<el-form-item label="加班类型" prop="otType">
|
||||
<el-input v-model="createForm.otType" placeholder="工作日/休息日等" />
|
||||
</el-form-item>
|
||||
<el-form-item label="开始时间" prop="startTime">
|
||||
<el-date-picker v-model="createForm.startTime" type="datetime" placeholder="开始" style="width: 100%" />
|
||||
</el-form-item>
|
||||
<el-form-item label="结束时间" prop="endTime">
|
||||
<el-date-picker v-model="createForm.endTime" type="datetime" placeholder="结束" style="width: 100%" />
|
||||
</el-form-item>
|
||||
<el-form-item label="时长(小时)" prop="hours">
|
||||
<el-input-number v-model="createForm.hours" :min="0.5" :step="0.5" />
|
||||
</el-form-item>
|
||||
<el-form-item label="事由" prop="reason">
|
||||
<el-input v-model="createForm.reason" type="textarea" :rows="2" />
|
||||
</el-form-item>
|
||||
</template>
|
||||
<template v-else-if="createType === 'travel'">
|
||||
<el-form-item label="开始时间" prop="startTime">
|
||||
<el-date-picker v-model="createForm.startTime" type="datetime" placeholder="开始" style="width: 100%" />
|
||||
</el-form-item>
|
||||
<el-form-item label="结束时间" prop="endTime">
|
||||
<el-date-picker v-model="createForm.endTime" type="datetime" placeholder="结束" style="width: 100%" />
|
||||
</el-form-item>
|
||||
<el-form-item label="目的地" prop="destination">
|
||||
<el-input v-model="createForm.destination" />
|
||||
</el-form-item>
|
||||
<el-form-item label="事由" prop="reason">
|
||||
<el-input v-model="createForm.reason" type="textarea" :rows="2" />
|
||||
</el-form-item>
|
||||
</template>
|
||||
<template v-else-if="createType === 'seal'">
|
||||
<el-form-item label="用印类型" prop="sealType">
|
||||
<el-input v-model="createForm.sealType" placeholder="合同章/法人章..." />
|
||||
</el-form-item>
|
||||
<el-form-item label="用途说明" prop="purpose">
|
||||
<el-input v-model="createForm.purpose" type="textarea" :rows="2" />
|
||||
</el-form-item>
|
||||
<el-form-item label="申请材料附件" prop="applyFileIds">
|
||||
<file-upload v-model="createForm.applyFileIds" :limit="5" :file-size="50" :file-type="['pdf']" />
|
||||
</el-form-item>
|
||||
<el-form-item label="需要回执">
|
||||
<el-switch v-model="createForm.receiptRequired" :active-value="1" :inactive-value="0" />
|
||||
</el-form-item>
|
||||
</template>
|
||||
</el-form>
|
||||
<div slot="footer" class="dialog-footer">
|
||||
<el-button @click="createDialogVisible = false">取消</el-button>
|
||||
<el-button type="primary" :loading="createSubmitting" @click="submitCreate">提交</el-button>
|
||||
</div>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -138,15 +240,23 @@ import {
|
||||
listOvertimeReq,
|
||||
listTravelReq,
|
||||
listSealReq,
|
||||
listEmployee,
|
||||
addLeaveReq,
|
||||
addOvertimeReq,
|
||||
addTravelReq,
|
||||
addSealReq,
|
||||
approveSealReq,
|
||||
rejectSealReq,
|
||||
cancelSealReq,
|
||||
stampSealJava,
|
||||
stampSealPython
|
||||
} from '@/api/hrm'
|
||||
import FileUpload from '@/components/FileUpload'
|
||||
|
||||
export default {
|
||||
name: 'HrmRequests',
|
||||
dicts: ['hrm_stamp_image'],
|
||||
components: { FileUpload },
|
||||
data() {
|
||||
return {
|
||||
requestBlocks: [],
|
||||
@@ -164,11 +274,52 @@ export default {
|
||||
currentSeal: null,
|
||||
previewNatural: { width: 0, height: 0 },
|
||||
marker: { visible: false, x: 0, y: 0, width: 0, height: 0 },
|
||||
stampImageNatural: { width: 0, height: 0 }
|
||||
stampImageNatural: { width: 0, height: 0 },
|
||||
employees: [],
|
||||
createDialogVisible: false,
|
||||
createType: '',
|
||||
createSubmitting: false,
|
||||
createForm: {},
|
||||
createRules: {
|
||||
empId: [{ required: true, message: '请选择申请人', trigger: 'change' }],
|
||||
leaveType: [{ required: true, message: '请选择请假类型', trigger: 'blur' }],
|
||||
otType: [{ required: true, message: '请输入加班类型', trigger: 'blur' }],
|
||||
sealType: [{ required: true, message: '请输入用印类型', trigger: 'blur' }],
|
||||
startTime: [{ required: true, message: '请选择开始时间', trigger: 'change' }],
|
||||
endTime: [{ required: true, message: '请选择结束时间', trigger: 'change' }],
|
||||
hours: [{ required: true, message: '请输入时长', trigger: 'blur' }],
|
||||
destination: [{ required: true, message: '请输入目的地', trigger: 'blur' }],
|
||||
purpose: [{ required: true, message: '请输入用途说明', trigger: 'blur' }],
|
||||
applyFileIds: [{ required: true, message: '请上传附件', trigger: 'change' }],
|
||||
reason: [{ required: true, message: '请输入事由', trigger: 'blur' }]
|
||||
}
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.initRequests()
|
||||
this.loadEmployees()
|
||||
},
|
||||
computed: {
|
||||
applicantDisplay() {
|
||||
const user = this.$store?.state?.user || {}
|
||||
const name = user.nickName || user.userName || ''
|
||||
const id = user.userId || user.userId === 0 ? user.userId : ''
|
||||
return name ? `${name}${id ? ` (${id})` : ''}` : id || '当前登录人'
|
||||
},
|
||||
currentCreateRules() {
|
||||
// 动态裁剪规则,避免多余必填影响校验
|
||||
const keys = {
|
||||
leave: ['empId', 'leaveType', 'startTime', 'endTime', 'hours', 'reason'],
|
||||
overtime: ['empId', 'otType', 'startTime', 'endTime', 'hours', 'reason'],
|
||||
travel: ['empId', 'startTime', 'endTime', 'destination', 'reason'],
|
||||
seal: ['empId', 'sealType', 'purpose', 'applyFileIds']
|
||||
}[this.createType] || []
|
||||
const picked = {}
|
||||
keys.forEach(k => {
|
||||
if (this.createRules[k]) picked[k] = this.createRules[k]
|
||||
})
|
||||
return picked
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
statusType(status) {
|
||||
@@ -322,6 +473,67 @@ export default {
|
||||
width: wRatio * displayWidth,
|
||||
height: hRatio * displayHeight
|
||||
}
|
||||
},
|
||||
openCreate(type) {
|
||||
this.createType = type
|
||||
this.createDialogVisible = true
|
||||
this.createForm = this.defaultCreateForm(type)
|
||||
this.$nextTick(() => this.$refs.createFormRef && this.$refs.createFormRef.clearValidate())
|
||||
},
|
||||
defaultCreateForm(type) {
|
||||
const common = { empId: '', remark: '' }
|
||||
if (type === 'leave') {
|
||||
return { ...common, leaveType: '', startTime: '', endTime: '', hours: 0, reason: '' }
|
||||
}
|
||||
if (type === 'overtime') {
|
||||
return { ...common, otType: '', startTime: '', endTime: '', hours: 0, reason: '' }
|
||||
}
|
||||
if (type === 'travel') {
|
||||
return { ...common, startTime: '', endTime: '', destination: '', reason: '' }
|
||||
}
|
||||
if (type === 'seal') {
|
||||
return { ...common, sealType: '', purpose: '', applyFileIds: '', receiptRequired: 0 }
|
||||
}
|
||||
return common
|
||||
},
|
||||
loadEmployees() {
|
||||
listEmployee({ pageNum: 1, pageSize: 500 }).then(res => {
|
||||
this.employees = res.rows || res.data || []
|
||||
})
|
||||
},
|
||||
submitCreate() {
|
||||
if (!this.createType) return
|
||||
const apiMap = {
|
||||
leave: addLeaveReq,
|
||||
overtime: addOvertimeReq,
|
||||
travel: addTravelReq,
|
||||
seal: addSealReq
|
||||
}
|
||||
const fn = apiMap[this.createType]
|
||||
if (!fn) return
|
||||
// 默认申请人取当前登录用户
|
||||
const payload = { ...this.createForm }
|
||||
const userId = this.$store?.state?.user?.userId
|
||||
if (userId) {
|
||||
payload.empId = userId
|
||||
} else {
|
||||
this.$message.error('未获取到当前登录用户,无法提交')
|
||||
return
|
||||
}
|
||||
this.$refs.createFormRef.validate(valid => {
|
||||
if (!valid) return
|
||||
this.createSubmitting = true
|
||||
fn(payload)
|
||||
.then(() => {
|
||||
this.$message.success('提交成功')
|
||||
this.createDialogVisible = false
|
||||
const block = this.requestBlocks.find(i => i.key === this.createType)
|
||||
block && block.loader()
|
||||
})
|
||||
.finally(() => {
|
||||
this.createSubmitting = false
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user