Files
klp-oa/klp-ui/src/views/wms/hrm/apply/leave.vue
砂糖 d927aa8647 feat(员工管理): 新增员工信息管理模块及员工选择器组件
新增员工信息管理模块,包括员工信息的增删改查功能
添加员工选择器组件,用于在请假、外出、报餐等场景中选择员工
替换原有字典选择方式为员工选择器,提升用户体验
2026-03-03 10:36:51 +08:00

476 lines
19 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<div class="app-container">
<el-row :gutter="20">
<el-col :span="6">
<el-card>
<template slot="header">
<span>提交请假申请</span>
</template>
<!-- 左侧是新增表单 -->
<el-form ref="form" :model="form" :rules="rules" label-width="80px" v-loading="loading">
<el-form-item label="审批部门" prop="deptId">
<el-select v-model="form.deptId" placeholder="请选择审批部门" filterable @change="getDeptLeader">
<el-option v-for="item in deptOptions" :key="item.deptId"
:label="item.deptName + '(' + (item.leaderNickName || '无负责人') + ')'" :value="item.deptId"></el-option>
</el-select>
</el-form-item>
<el-form-item label="请假类型" prop="leaveType">
<el-select v-model="form.leaveType" placeholder="请选择请假类型">
<el-option v-for="dict in dict.type.hrm_leave_type" :key="dict.value" :label="dict.label"
:value="dict.value"></el-option>
</el-select>
</el-form-item>
<el-form-item label="请假人姓名" prop="applicantName">
<employee-selector v-model="form.applicantName" :key-field="'name'" placeholder="请选择请假人姓名" />
</el-form-item>
<!-- 批量请假时段区域 -->
<div v-for="(item, index) in form.list" :key="index" class="leave-item">
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 10px;">
<span style="font-weight: 600;">请假时段 {{ index + 1 }}</span>
<!-- 只有多行时显示删除按钮 -->
<el-button
v-if="form.list.length > 1"
type="text"
icon="el-icon-delete"
size="mini"
@click="removeLeaveItem(index)"
style="color: #f56c6c;"
>删除</el-button>
</div>
<!-- 注意prop需要指定数组索引支持嵌套校验 -->
<el-form-item label="开始时间" :prop="`list[${index}].startTime`" :rules="rules.listItem.startTime">
<el-date-picker clearable v-model="item.startTime" type="datetime" value-format="yyyy-MM-dd HH:mm:ss"
placeholder="请选择请假开始时间" @change="calculateLeaveDays(index)">
</el-date-picker>
</el-form-item>
<el-form-item label="结束时间" :prop="`list[${index}].endTime`" :rules="rules.listItem.endTime">
<el-date-picker clearable v-model="item.endTime" type="datetime" value-format="yyyy-MM-dd HH:mm:ss"
placeholder="请选择请假结束时间" @change="calculateLeaveDays(index)">
</el-date-picker>
</el-form-item>
<el-form-item label="请假班次" :prop="`list[${index}].leaveShift`" :rules="rules.listItem.leaveShift">
<el-select v-model="item.leaveShift" placeholder="请选择请假班次">
<el-option v-for="dict in dict.type.hrm_leave_shift" :key="dict.value" :label="dict.label"
:value="dict.value"></el-option>
</el-select>
</el-form-item>
<el-form-item label="请假天数" :prop="`list[${index}].leaveDays`" :rules="rules.listItem.leaveDays">
<el-input v-model="item.leaveDays" placeholder="选择时间后自动计算,也可手动修改" />
</el-form-item>
<el-form-item label="请假原因" :prop="`list[${index}].leaveReason`" :rules="rules.listItem.leaveReason">
<el-input v-model="item.leaveReason" type="textarea" placeholder="请输入内容" />
</el-form-item>
<el-divider v-if="index < form.list.length - 1"></el-divider>
</div>
<!-- 新增请假时段按钮 -->
<el-form-item>
<el-button type="dashed" icon="el-icon-plus" @click="addLeaveItem" style="width: 100%;">
新增请假时段
</el-button>
</el-form-item>
<el-form-item label="附件" prop="attachmentUrls">
<FileUpload v-model="form.attachmentUrls" :max-count="1" :show-file-list="true" />
</el-form-item>
<el-form-item label="备注" prop="remark">
<el-input v-model="form.remark" placeholder="请输入备注" />
</el-form-item>
</el-form>
<div style="text-align: center;">
<el-button type="primary" @click="handleSubmit" v-loading="buttonLoading">{{ form.leaveId ? '更新申请' : '批量提交申请' }}</el-button>
<el-button @click="handleReset">重置表单</el-button>
</div>
</el-card>
</el-col>
<el-col :span="18">
<el-card>
<template slot="header">
<span>请假申请列表</span>
<el-button style="float: right;" icon="el-icon-refresh" @click="getList">刷新</el-button>
</template>
<el-table v-loading="loading" :data="leaveRequestList">
<el-table-column prop="approvalStatus" label="审批状态" align="center">
<template slot-scope="scope">
<el-tag :type="getStatusTagType(scope.row.approvalStatus)">
{{ getStatusText(scope.row.approvalStatus) }}
</el-tag>
</template>
</el-table-column>
<el-table-column label="请假类型" align="center" prop="leaveType">
<template slot-scope="scope">
<dict-tag :options="dict.type.hrm_leave_type" :value="scope.row.leaveType" />
</template>
</el-table-column>
<el-table-column label="请假人姓名" align="center" prop="applicantName" />
<el-table-column label="审批情况" align="center" prop="approverName">
<template slot-scope="scope">
<!-- 每行一个不要出现换行将英文映射成中文 -->
<el-tag v-for="task in scope.row.tasks" :key="task.taskId" :type="getTaskStatusTagType(task.taskStatus)" style="margin-right: 8px;">
<!-- taskStatus包括pending, approved, rejected, 根据状态设置不同的标签类型 -->
{{ task.approverName }} {{ getTaskStatusText(task.taskStatus) }}
</el-tag>
</template>
</el-table-column>
<el-table-column label="开始时间" align="center" prop="startTime" width="120">
<template slot-scope="scope">
<span>{{ parseTime(scope.row.startTime, '{y}-{m}-{d} {h}') }}</span>
</template>
</el-table-column>
<el-table-column label="结束时间" align="center" prop="endTime" width="120">
<template slot-scope="scope">
<span>{{ parseTime(scope.row.endTime, '{y}-{m}-{d} {h}') }}</span>
</template>
</el-table-column>
<el-table-column label="请假班次" align="center" prop="leaveShift">
<template slot-scope="scope">
<dict-tag :options="dict.type.hrm_leave_shift" :value="scope.row.leaveShift" />
</template>
</el-table-column>
<el-table-column label="具体原因" align="center" prop="leaveReason" show-overflow-tooltip />
<el-table-column label="请假天数" align="center" prop="leaveDays" />
<el-table-column label="操作" align="center" width="160">
<template slot-scope="scope" v-if="scope.row.approvalStatus === '待审批'">
<el-button icon="el-icon-edit" size="mini" @click="handleEdit(scope.row)">修改</el-button>
<!-- <el-button icon="el-icon-delete" size="mini" @click="handleWithdraw(scope.row)">撤回</el-button> -->
</template>
</el-table-column>
</el-table>
<pagination v-show="total > 0" :total="total" :page.sync="queryParams.pageNum"
:limit.sync="queryParams.pageSize" @pagination="getList" />
</el-card>
</el-col>
</el-row>
</div>
</template>
<script>
import { getLeaveRequest, addLeaveRequest, updateLeaveRequest } from "@/api/wms/leaveRequest";
import { listApproval, updateApproval } from "@/api/wms/approval"
import { listDept } from "@/api/wms/dept"
import FileUpload from '@/components/FileUpload'
import EmployeeSelector from '@/components/EmployeeSelector'
export default {
name: 'LeaveApply',
dicts: ['hrm_leave_shift', 'hrm_leave_type', 'hrm_department'],
components: {
FileUpload,
EmployeeSelector
},
data() {
return {
// 表单参数
form: {
list: [{ // 初始化默认一行请假时段
startTime: undefined,
endTime: undefined,
leaveShift: undefined,
leaveDays: undefined,
leaveReason: undefined
}]
},
loading: false,
buttonLoading: false,
leaveRequestList: [],
total: 0,
queryParams: {
pageNum: 1,
pageSize: 10,
leaveStatus: undefined,
applyType: 'leave',
createBy: this.$store.getters.name,
},
// 表单校验规则【适配数组嵌套校验】
rules: {
leaveType: [{ required: true, message: '请假类型不能为空', trigger: 'change' }],
applicantName: [{ required: true, message: '请假人姓名不能为空', trigger: 'change' }],
deptId: [{ required: true, message: '审批部门不能为空', trigger: 'change' }],
attachmentUrls: [{ required: false }],
remark: [{ required: false }],
// 数组项的校验规则
listItem: {
startTime: [{ required: true, message: '开始时间不能为空', trigger: 'change' }],
endTime: [{ required: true, message: '结束时间不能为空', trigger: 'change' }],
leaveShift: [{ required: true, message: '请假班次不能为空', trigger: 'change' }],
leaveDays: [{ required: true, message: '请假天数不能为空', trigger: ['blur', 'change'] }],
leaveReason: [{ required: true, message: '请假原因不能为空', trigger: ['blur', 'change'] }]
}
},
deptOptions: []
}
},
created() {
this.getList();
this.getDeptList();
},
methods: {
getDeptList() {
listDept().then(response => {
this.deptOptions = response.data
})
},
// 获取部门负责人
getDeptLeader() {
const selectedDept = this.deptOptions.find(item => item.deptId === this.form.deptId)
const approverName = selectedDept.leaderNickName;
if (!approverName) {
this.$message.warning('该部门无负责人,申请将无人审批');
return;
}
this.form.approverName = approverName
},
/** 查询员工请假申请列表 */
getList() {
this.loading = true;
listApproval(this.queryParams).then(response => {
this.leaveRequestList = response.rows.map(item => {
return {
approvalStatus: item.approval.approvalStatus,
applyId: item.approval.applyId,
approvalId: item.approval.approvalId,
approvalType: item.approval.approvalType,
approverName: item.approval.approverName,
tasks: item.tasks,
...item.detail,
}
});
this.total = response.total;
this.loading = false;
});
},
// 新增请假时段行
addLeaveItem() {
this.form.list.push({
startTime: undefined,
endTime: undefined,
leaveShift: undefined,
leaveDays: undefined,
leaveReason: undefined
});
},
// 删除指定索引的请假时段行
removeLeaveItem(index) {
if (this.form.list.length <= 1) {
this.$message.warning('至少保留一行请假时段');
return;
}
this.form.list.splice(index, 1);
},
handleReset() {
this.reset()
},
// 表单重置
reset() {
this.form = {
leaveId: undefined,
leaveType: undefined,
applicantName: undefined,
deptId: undefined,
approverName: undefined,
attachmentUrls: undefined,
remark: undefined,
list: [{ // 重置后保留默认一行
startTime: undefined,
endTime: undefined,
leaveShift: undefined,
leaveDays: undefined,
leaveReason: undefined
}]
};
this.resetForm("form");
},
/** 搜索按钮操作 */
handleQuery() {
this.queryParams.pageNum = 1;
this.getList();
},
/** 重置按钮操作 */
resetQuery() {
this.resetForm("queryForm");
this.handleQuery();
},
/** 修改按钮操作 */
handleEdit(row) {
this.loading = true;
this.reset();
const leaveId = row.applyId
getLeaveRequest(leaveId).then(response => {
this.loading = false;
const data = response.data;
// 编辑时默认填充为单行数据(因为后端是单条记录)
this.form = {
...data,
list: [{
startTime: data.startTime,
endTime: data.endTime,
leaveShift: data.leaveShift,
leaveDays: data.leaveDays,
leaveReason: data.leaveReason
}]
};
});
},
/** 批量提交按钮(核心修改) */
async handleSubmit() {
try {
// 1. 先校验表单
const valid = await new Promise((resolve) => {
this.$refs["form"].validate((valid) => {
resolve(valid);
});
});
if (!valid) return;
this.buttonLoading = true;
const { list, ...commonFields } = this.form; // 拆分公共字段和时段列表
let successCount = 0;
let failCount = 0;
const failReasons = [];
// 2. 循环处理每个时段,逐个发送请求
for (let i = 0; i < list.length; i++) {
const item = list[i];
const approvalType = parseInt(item.leaveDays) > 3 ? 'multi' : 'single';
const singleRequestData = {
...commonFields,
startTime: item.startTime,
endTime: item.endTime,
leaveShift: item.leaveShift,
leaveDays: item.leaveDays,
leaveReason: item.leaveReason,
// 生成单条记录的标题
leaveTitle: `${commonFields.applicantName}-${commonFields.leaveType}-时段${i+1}-${item.startTime}-${item.leaveReason || ''}`,
approvalType: approvalType,
};
try {
if (commonFields.leaveId != null) {
// 编辑模式:仅支持修改单条(因为后端是单条记录)
if (list.length > 1) {
this.$message.warning('编辑模式仅支持单条修改,已自动取第一行数据');
await updateLeaveRequest({ ...singleRequestData, leaveId: commonFields.leaveId });
successCount++;
break; // 编辑时只处理第一条
} else {
await updateLeaveRequest({ ...singleRequestData, leaveId: commonFields.leaveId });
successCount++;
}
} else {
// 新增模式:批量提交多条
await addLeaveRequest(singleRequestData);
successCount++;
}
} catch (error) {
failCount++;
failReasons.push(`时段${i+1}提交失败:${error.message || '未知错误'}`);
// 失败后继续提交下一条,不中断批量操作
continue;
}
}
// 3. 提交完成后反馈结果
if (failCount === 0) {
this.$modal.msgSuccess(`批量提交成功!共提交${successCount}条请假申请`);
} else {
this.$modal.msgWarning(`批量提交完成!成功${successCount}条,失败${failCount}\n失败原因${failReasons.join('')}`);
}
// 4. 刷新列表并重置表单
this.getList();
this.reset();
} catch (error) {
this.$modal.msgError('批量提交异常:' + error.message);
} finally {
this.buttonLoading = false;
}
},
handleWithdraw(row) {
this.$modal.confirm('是否确认撤回请假申请编号为"' + row.applyId + '"的数据项?').then(() => {
this.loading = true;
return updateApproval({
approvalId: row.approvalId,
approvalStatus: '已撤销'
});
}).then(() => {
this.loading = false;
this.getList();
this.$modal.msgSuccess("撤回成功");
}).catch(() => {
}).finally(() => {
this.loading = false;
});
},
// 获取审批状态对应的标签类型
getStatusTagType(status) {
const typeMap = {
'待审批': 'warning',
'已同意': 'success',
'已驳回': 'danger',
'已撤销': 'info',
}
return typeMap[status] || 'default'
},
// 获取审批状态的中文文本
getStatusText(status) {
const textMap = {
'待审批': '待审批',
'已同意': '已同意',
'已驳回': '已驳回',
'已撤销': '已撤销',
}
return textMap[status] || '未知状态'
},
// 获取任务状态的中文文本
getTaskStatusText(status) {
const textMap = {
'pending': '待审批',
'approved': '已同意',
'rejected': '已驳回'
}
return textMap[status] || status
},
// 获取任务状态的标签类型
getTaskStatusTagType(status) {
const typeMap = {
'pending': 'warning',
'approved': 'success',
'rejected': 'danger'
}
return typeMap[status] || 'info'
},
// 计算指定行的请假天数
calculateLeaveDays(index) {
const item = this.form.list[index];
// 两个时间都选择后才计算
if (item.startTime && item.endTime) {
// 转成时间戳
const start = new Date(item.startTime).getTime();
const end = new Date(item.endTime).getTime();
// 判断结束时间不能小于开始时间
if (end < start) {
this.$modal.msgWarning('结束时间不能早于开始时间,请重新选择!');
item.leaveDays = undefined;
return;
}
// 计算时间差(毫秒) → 转天 → 保留2位小数
const diffTime = end - start;
const diffDays = (diffTime / (1000 * 60 * 60 * 24)).toFixed(2);
// 赋值到对应行的天数输入框
item.leaveDays = diffDays;
}
}
}
}
</script>
<style scoped>
.leave-item {
margin-bottom: 15px;
padding: 10px;
border: 1px solid #ebeef5;
border-radius: 4px;
}
</style>