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

@@ -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
})
})
}
}
}