feat(attendance): 优化考勤记录导出功能支持部门选择

- 修改后端接口参数名称从 deptname 改为 enames
- 实现前端多员工选择下拉框组件
- 添加部门联动员工动态加载功能
- 重构导出逻辑支持按员工列表精确过滤
- 修复数组参数序列化导致的逗号编码问题
- 新增获取部门列表和按部门查询员工的API接口
- 实现导出表单验证和异步处理机制
This commit is contained in:
2026-07-06 10:54:57 +08:00
parent 660e8049cd
commit 55ae4ad332
9 changed files with 180 additions and 40 deletions

View File

@@ -63,6 +63,19 @@ export function exportAttendanceReport(params) {
url: '/wms/attendanceRecords/exportReport', url: '/wms/attendanceRecords/exportReport',
method: 'post', method: 'post',
params: params, params: params,
responseType: 'blob' responseType: 'blob',
// enames 是数组,需要特殊序列化避免逗号被二次编码
paramsSerializer: params => {
const searchParams = new URLSearchParams();
Object.keys(params).forEach(key => {
const val = params[key];
if (Array.isArray(val)) {
searchParams.append(key, val.join(','));
} else if (val !== undefined && val !== null && val !== '') {
searchParams.append(key, val);
}
});
return searchParams.toString();
}
}) })
} }

View File

@@ -42,3 +42,20 @@ export function delEmployeeInfo(infoId) {
method: 'delete' method: 'delete'
}) })
} }
// 获取所有不重复的部门名称
export function getDepts() {
return request({
url: '/wms/employeeInfo/depts',
method: 'get'
})
}
// 根据部门查询在职员工
export function getEmployeesByDept(dept) {
return request({
url: '/wms/employeeInfo/employeesByDept',
method: 'get',
params: { dept }
})
}

View File

@@ -211,7 +211,14 @@
<el-input v-model="exportFormData.ename" placeholder="请输入姓名(可选)" clearable /> <el-input v-model="exportFormData.ename" placeholder="请输入姓名(可选)" clearable />
</el-form-item> </el-form-item>
<el-form-item label="部门" prop="deptname"> <el-form-item label="部门" prop="deptname">
<el-input v-model="exportFormData.deptname" placeholder="请输入部门(可选)" clearable /> <el-select v-model="exportFormData.deptname" placeholder="请选择部门(可选)" clearable filterable @change="handleDeptChange" style="width: 100%">
<el-option v-for="dept in deptList" :key="dept" :label="dept" :value="dept" />
</el-select>
</el-form-item>
<el-form-item label="员工" prop="enames" v-if="exportFormData.deptname">
<el-select v-model="exportFormData.enames" placeholder="请选择员工(可选,不选则导出整个部门)" multiple clearable filterable style="width: 100%">
<el-option v-for="emp in employeeList" :key="emp.name" :label="emp.name + ' (' + emp.jobType + ')'" :value="emp.name" />
</el-select>
</el-form-item> </el-form-item>
</el-form> </el-form>
<div slot="footer" class="dialog-footer"> <div slot="footer" class="dialog-footer">
@@ -224,6 +231,7 @@
<script> <script>
import { listRecords, getRecords, delRecords, addRecords, updateRecords, syncRecords, exportAttendanceReport } from "@/api/wms/attendance"; import { listRecords, getRecords, delRecords, addRecords, updateRecords, syncRecords, exportAttendanceReport } from "@/api/wms/attendance";
import { getDepts, getEmployeesByDept } from "@/api/wms/employeeInfo";
export default { export default {
name: "Records", name: "Records",
@@ -247,12 +255,17 @@ export default {
exportDialogVisible: false, exportDialogVisible: false,
// 导出loading // 导出loading
exportLoading: false, exportLoading: false,
// 部门列表
deptList: [],
// 员工列表(根据部门动态加载)
employeeList: [],
// 导出表单 // 导出表单
exportFormData: { exportFormData: {
dateRange: [], dateRange: [],
pin: "", pin: "",
ename: "", ename: "",
deptname: "" deptname: "",
enames: []
}, },
// 导出表单校验 // 导出表单校验
exportRules: { exportRules: {
@@ -474,6 +487,12 @@ export default {
this.exportFormData.pin = ""; this.exportFormData.pin = "";
this.exportFormData.ename = ""; this.exportFormData.ename = "";
this.exportFormData.deptname = ""; this.exportFormData.deptname = "";
this.exportFormData.enames = [];
this.employeeList = [];
// 加载部门列表
getDepts().then(res => {
this.deptList = res.data || [];
});
this.exportDialogVisible = true; this.exportDialogVisible = true;
this.$nextTick(() => { this.$nextTick(() => {
this.$refs.exportForm && this.$refs.exportForm.clearValidate(); this.$refs.exportForm && this.$refs.exportForm.clearValidate();
@@ -484,19 +503,51 @@ export default {
} }
}); });
}, },
/** 部门变更时加载对应员工 */
handleDeptChange(dept) {
this.exportFormData.enames = [];
if (dept) {
getEmployeesByDept(dept).then(res => {
this.employeeList = res.data || [];
});
} else {
this.employeeList = [];
}
},
/** 执行导出考勤记录表 */ /** 执行导出考勤记录表 */
doExportReport() { async doExportReport() {
this.$refs.exportForm.validate(valid => { const valid = await this.$refs.exportForm.validate().catch(() => false);
if (!valid) return; if (!valid) return;
const [startDate, endDate] = this.exportFormData.dateRange; const [startDate, endDate] = this.exportFormData.dateRange;
this.exportLoading = true; this.exportLoading = true;
exportAttendanceReport({
try {
let enames = undefined;
if (this.exportFormData.deptname) {
// 选了部门:通过 wms_employee_info 查员工姓名来过滤
if (this.exportFormData.enames && this.exportFormData.enames.length > 0) {
enames = this.exportFormData.enames;
} else {
const res = await getEmployeesByDept(this.exportFormData.deptname);
const allEmps = res.data || [];
enames = allEmps.map(e => e.name);
if (enames.length === 0) {
this.$modal.msgWarning('该部门下没有在职员工');
this.exportLoading = false;
return;
}
}
}
const res = await exportAttendanceReport({
startTime: startDate, startTime: startDate,
endTime: endDate, endTime: endDate,
pin: this.exportFormData.pin || undefined, pin: !enames ? (this.exportFormData.pin || undefined) : undefined,
ename: this.exportFormData.ename || undefined, ename: !enames ? (this.exportFormData.ename || undefined) : undefined,
deptname: this.exportFormData.deptname || undefined enames: enames
}).then(res => { });
const blob = new Blob([res], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' }); const blob = new Blob([res], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' });
const url = window.URL.createObjectURL(blob); const url = window.URL.createObjectURL(blob);
const link = document.createElement('a'); const link = document.createElement('a');
@@ -509,10 +560,9 @@ export default {
this.$modal.msgSuccess("导出成功"); this.$modal.msgSuccess("导出成功");
this.exportLoading = false; this.exportLoading = false;
this.exportDialogVisible = false; this.exportDialogVisible = false;
}).catch(() => { } catch (e) {
this.exportLoading = false; this.exportLoading = false;
}); }
});
} }
} }
}; };

View File

@@ -48,9 +48,9 @@ public class AttendanceRecordsController extends BaseController {
@RequestParam String endTime, @RequestParam String endTime,
@RequestParam(required = false) String pin, @RequestParam(required = false) String pin,
@RequestParam(required = false) String ename, @RequestParam(required = false) String ename,
@RequestParam(required = false) String deptname, @RequestParam(required = false) String enames,
HttpServletResponse response) { HttpServletResponse response) {
iAttendanceRecordsService.exportReport(startTime, endTime, pin, ename, deptname, response); iAttendanceRecordsService.exportReport(startTime, endTime, pin, ename, enames, response);
} }
@GetMapping("/{id}") @GetMapping("/{id}")

View File

@@ -96,4 +96,20 @@ public class WmsEmployeeInfoController extends BaseController {
@PathVariable Long[] infoIds) { @PathVariable Long[] infoIds) {
return toAjax(iWmsEmployeeInfoService.deleteWithValidByIds(Arrays.asList(infoIds), true)); return toAjax(iWmsEmployeeInfoService.deleteWithValidByIds(Arrays.asList(infoIds), true));
} }
/**
* 获取所有不重复的部门名称
*/
@GetMapping("/depts")
public R<List<String>> getDepts() {
return R.ok(iWmsEmployeeInfoService.getDistinctDepts());
}
/**
* 根据部门查询在职员工
*/
@GetMapping("/employeesByDept")
public R<List<WmsEmployeeInfoVo>> getEmployeesByDept(@RequestParam String dept) {
return R.ok(iWmsEmployeeInfoService.getEmployeesByDept(dept));
}
} }

View File

@@ -33,5 +33,5 @@ public interface IAttendanceRecordsService {
/** /**
* 导出考勤记录表(复杂格式) * 导出考勤记录表(复杂格式)
*/ */
void exportReport(String startTime, String endTime, String pin, String ename, String deptname, HttpServletResponse response); void exportReport(String startTime, String endTime, String pin, String ename, String enames, HttpServletResponse response);
} }

View File

@@ -46,4 +46,14 @@ public interface IWmsEmployeeInfoService {
* 校验并批量删除员工信息信息 * 校验并批量删除员工信息信息
*/ */
Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid); Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid);
/**
* 获取所有不重复的部门名称
*/
List<String> getDistinctDepts();
/**
* 根据部门查询在职员工信息
*/
List<WmsEmployeeInfoVo> getEmployeesByDept(String dept);
} }

View File

@@ -104,7 +104,7 @@ public class AttendanceRecordsServiceImpl implements IAttendanceRecordsService {
} }
@Override @Override
public void exportReport(String startTime, String endTime, String pin, String ename, String deptname, HttpServletResponse response) { public void exportReport(String startTime, String endTime, String pin, String ename, String enames, HttpServletResponse response) {
try { try {
// 1. 解析时间范围 // 1. 解析时间范围
DateTimeFormatter fmt = DateTimeFormatter.ofPattern("yyyy-MM-dd"); DateTimeFormatter fmt = DateTimeFormatter.ofPattern("yyyy-MM-dd");
@@ -119,14 +119,17 @@ public class AttendanceRecordsServiceImpl implements IAttendanceRecordsService {
LambdaQueryWrapper<AttendanceRecords> lqw = Wrappers.lambdaQuery(); LambdaQueryWrapper<AttendanceRecords> lqw = Wrappers.lambdaQuery();
lqw.ge(AttendanceRecords::getChecktime, startDatetime); lqw.ge(AttendanceRecords::getChecktime, startDatetime);
lqw.le(AttendanceRecords::getChecktime, endDatetime); lqw.le(AttendanceRecords::getChecktime, endDatetime);
// 如果传入了员工姓名列表,优先按姓名列表过滤
if (StringUtils.isNotBlank(enames)) {
List<String> enameList = Arrays.asList(enames.split(","));
lqw.in(AttendanceRecords::getEname, enameList);
} else {
if (StringUtils.isNotBlank(pin)) { if (StringUtils.isNotBlank(pin)) {
lqw.eq(AttendanceRecords::getPin, pin); lqw.eq(AttendanceRecords::getPin, pin);
} }
if (StringUtils.isNotBlank(ename)) { if (StringUtils.isNotBlank(ename)) {
lqw.like(AttendanceRecords::getEname, ename); lqw.like(AttendanceRecords::getEname, ename);
} }
if (StringUtils.isNotBlank(deptname)) {
lqw.eq(AttendanceRecords::getDeptname, deptname);
} }
lqw.orderByAsc(AttendanceRecords::getPin).orderByAsc(AttendanceRecords::getChecktime); lqw.orderByAsc(AttendanceRecords::getPin).orderByAsc(AttendanceRecords::getChecktime);
List<AttendanceRecords> records = baseMapper.selectList(lqw); List<AttendanceRecords> records = baseMapper.selectList(lqw);

View File

@@ -17,7 +17,9 @@ import com.klp.service.IWmsEmployeeInfoService;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Objects;
import java.util.Collection; import java.util.Collection;
import java.util.stream.Collectors;
/** /**
* 员工信息Service业务层处理 * 员工信息Service业务层处理
@@ -145,4 +147,33 @@ public class WmsEmployeeInfoServiceImpl implements IWmsEmployeeInfoService {
} }
return baseMapper.deleteBatchIds(ids) > 0; return baseMapper.deleteBatchIds(ids) > 0;
} }
/**
* 获取所有不重复的部门名称(仅在职员工)
*/
@Override
public List<String> getDistinctDepts() {
LambdaQueryWrapper<WmsEmployeeInfo> lqw = Wrappers.lambdaQuery();
lqw.eq(WmsEmployeeInfo::getIsLeave, 0);
List<WmsEmployeeInfoVo> list = baseMapper.selectVoList(lqw);
return list.stream()
.map(WmsEmployeeInfoVo::getDept)
.filter(Objects::nonNull)
.filter(org.apache.commons.lang3.StringUtils::isNotBlank)
.distinct()
.sorted()
.collect(Collectors.toList());
}
/**
* 根据部门查询在职员工信息
*/
@Override
public List<WmsEmployeeInfoVo> getEmployeesByDept(String dept) {
LambdaQueryWrapper<WmsEmployeeInfo> lqw = Wrappers.lambdaQuery();
lqw.eq(WmsEmployeeInfo::getDept, dept)
.eq(WmsEmployeeInfo::getIsLeave, 0)
.orderByAsc(WmsEmployeeInfo::getName);
return baseMapper.selectVoList(lqw);
}
} }