feat(erp/timesheetReport): 新增项目人员工时统计报表功能
- 在项目工时报表中添加行样式高亮功能,突出显示跨部门工时 - 新增项目人员工时统计报表页面,支持按部门查询人员工时分布 - 实现动态人员列展示和工时数据统计汇总功能 - 添加跨部门工时预警样式和导出功能 - 新增项目人员工时统计API接口和数据模型定义dev
parent
c0e6d124b5
commit
8fd0209cac
@ -0,0 +1,224 @@
|
|||||||
|
<template>
|
||||||
|
<div class="app-container">
|
||||||
|
<el-form :model="queryParams" ref="queryRef" :inline="true" v-show="showSearch" label-width="68px">
|
||||||
|
<el-form-item label="部门" prop="deptId">
|
||||||
|
<el-select v-model="queryParams.deptId" placeholder="请选择部门" filterable style="width: 240px" @change="handleQuery">
|
||||||
|
<el-option v-for="item in deptOptions" :key="item.deptId" :label="item.deptName" :value="item.deptId" />
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="起止日期">
|
||||||
|
<el-date-picker
|
||||||
|
v-model="dateRange[0]"
|
||||||
|
type="date"
|
||||||
|
value-format="YYYY-MM-DD"
|
||||||
|
placeholder="开始日期"
|
||||||
|
:disabled-date="disabledDateStart"
|
||||||
|
style="width: 150px"
|
||||||
|
/>
|
||||||
|
<span class="el-range-separator" style="margin: 0 5px">-</span>
|
||||||
|
<el-date-picker
|
||||||
|
v-model="dateRange[1]"
|
||||||
|
type="date"
|
||||||
|
value-format="YYYY-MM-DD"
|
||||||
|
placeholder="结束日期"
|
||||||
|
:disabled-date="disabledDateEnd"
|
||||||
|
style="width: 150px"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item>
|
||||||
|
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
|
||||||
|
<el-button icon="Refresh" @click="resetQuery">重置</el-button>
|
||||||
|
<el-button type="warning" plain icon="Download" @click="handleExport">导出</el-button>
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
|
||||||
|
<!-- 报表表格 -->
|
||||||
|
<el-table
|
||||||
|
v-loading="loading"
|
||||||
|
:data="tableData"
|
||||||
|
border
|
||||||
|
:row-class-name="tableRowClassName"
|
||||||
|
style="width: 100%"
|
||||||
|
show-summary
|
||||||
|
:summary-method="getSummaries"
|
||||||
|
>
|
||||||
|
<el-table-column type="selection" width="60" align="center" />
|
||||||
|
<el-table-column label="部门" prop="deptName" align="center" width="120" fixed />
|
||||||
|
<el-table-column label="项目名称" prop="projectName" align="left" min-width="180" fixed />
|
||||||
|
<el-table-column label="项目编号" prop="projectCode" align="center" width="150" fixed />
|
||||||
|
|
||||||
|
<!-- 汇总列 -->
|
||||||
|
<el-table-column label="汇总" prop="rowTotal" align="center" width="80">
|
||||||
|
<template #default="scope">
|
||||||
|
{{ Number(scope.row.rowTotal) === 0 ? '' : Number(scope.row.rowTotal).toFixed(1) }}
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
|
||||||
|
<!-- 动态人员列 -->
|
||||||
|
<el-table-column v-for="col in columns" :key="col.prop" :label="col.label" :prop="col.prop" align="center" width="80">
|
||||||
|
<template #default="scope">
|
||||||
|
{{ formatHours(scope.row[col.prop]) }}
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
|
||||||
|
<!-- 备注列 (跨部门) -->
|
||||||
|
<el-table-column label="备注" prop="remark" align="center" width="100" fixed="right">
|
||||||
|
<template #default="scope">
|
||||||
|
<span v-if="scope.row.isCrossDept" style="color: black; font-weight: bold">跨部门</span>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
</el-table>
|
||||||
|
|
||||||
|
<!-- 底部统计 -->
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup name="ProjectPersonnelReport" lang="ts">
|
||||||
|
import { listDept } from '@/api/system/dept';
|
||||||
|
import { getProjectPersonnelReport, ProjectPersonnelReportVO } from '@/api/oa/erp/timesheetReport';
|
||||||
|
|
||||||
|
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
|
||||||
|
|
||||||
|
const loading = ref(false);
|
||||||
|
const showSearch = ref(true);
|
||||||
|
const deptOptions = ref<any[]>([]);
|
||||||
|
const dateRange = ref<[string, string]>(['', '']);
|
||||||
|
|
||||||
|
const columns = ref<Array<{ label: string; prop: string }>>([]);
|
||||||
|
const tableData = ref<Array<any>>([]);
|
||||||
|
|
||||||
|
const queryParams = ref({
|
||||||
|
deptId: undefined
|
||||||
|
});
|
||||||
|
|
||||||
|
// 监听部门变化自动查询
|
||||||
|
// watch(() => queryParams.value.deptId, (newVal) => {
|
||||||
|
// if (newVal) handleQuery();
|
||||||
|
// });
|
||||||
|
|
||||||
|
/** 获取部门 */
|
||||||
|
const getDeptList = async () => {
|
||||||
|
const res = await listDept();
|
||||||
|
deptOptions.value = res.data || res.rows || [];
|
||||||
|
};
|
||||||
|
|
||||||
|
/** 查询报表 */
|
||||||
|
const getList = async () => {
|
||||||
|
if (!queryParams.value.deptId) {
|
||||||
|
proxy?.$modal.msgWarning('请选择部门');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
loading.value = true;
|
||||||
|
const params = {
|
||||||
|
deptId: queryParams.value.deptId,
|
||||||
|
startTime: dateRange.value[0],
|
||||||
|
endTime: dateRange.value[1]
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
const res = await getProjectPersonnelReport(params);
|
||||||
|
if (res.data) {
|
||||||
|
columns.value = res.data.columns || [];
|
||||||
|
tableData.value = res.data.rows || [];
|
||||||
|
} else {
|
||||||
|
tableData.value = [];
|
||||||
|
columns.value = [];
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
loading.value = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleQuery = () => {
|
||||||
|
getList();
|
||||||
|
};
|
||||||
|
|
||||||
|
const resetQuery = () => {
|
||||||
|
dateRange.value = ['', ''];
|
||||||
|
(proxy as any)?.resetForm('queryRef');
|
||||||
|
tableData.value = [];
|
||||||
|
columns.value = [];
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleExport = () => {
|
||||||
|
proxy?.download(
|
||||||
|
'oa/erp/timesheetReport/exportProjectPersonnel',
|
||||||
|
{
|
||||||
|
deptId: queryParams.value.deptId,
|
||||||
|
startTime: dateRange.value[0],
|
||||||
|
endTime: dateRange.value[1]
|
||||||
|
},
|
||||||
|
`项目人员工时统计报表_${new Date().getTime()}.xlsx`
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
/** 行样式 */
|
||||||
|
const tableRowClassName = ({ row }: { row: any }) => {
|
||||||
|
if (row.isCrossDept) {
|
||||||
|
return 'warning-row';
|
||||||
|
}
|
||||||
|
return '';
|
||||||
|
};
|
||||||
|
|
||||||
|
/** 格式化工时 */
|
||||||
|
const formatHours = (val: any) => {
|
||||||
|
if (val === undefined || val === null || val === '' || Number(val) === 0) return '';
|
||||||
|
return Number(val).toFixed(1);
|
||||||
|
};
|
||||||
|
|
||||||
|
/** 合计行逻辑 */
|
||||||
|
const getSummaries = (param: any) => {
|
||||||
|
const { columns, data } = param;
|
||||||
|
const sums: string[] = [];
|
||||||
|
|
||||||
|
columns.forEach((column: any, index: number) => {
|
||||||
|
if (index === 0) {
|
||||||
|
sums[index] = '总计';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// 项目名称、编号、备注不统计
|
||||||
|
if (['projectName', 'projectCode', 'remark', 'deptName'].includes(column.property)) {
|
||||||
|
sums[index] = '';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 计算列总和
|
||||||
|
const values = data.map((item: any) => Number(item[column.property]));
|
||||||
|
if (!values.every((value: any) => Number.isNaN(value))) {
|
||||||
|
const sum = values.reduce((prev: any, curr: any) => {
|
||||||
|
const value = Number(curr);
|
||||||
|
if (!Number.isNaN(value)) {
|
||||||
|
return prev + curr;
|
||||||
|
} else {
|
||||||
|
return prev;
|
||||||
|
}
|
||||||
|
}, 0);
|
||||||
|
sums[index] = sum === 0 ? '' : sum.toFixed(1);
|
||||||
|
} else {
|
||||||
|
sums[index] = '';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return sums;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 禁用非周一/周日逻辑
|
||||||
|
const disabledDateStart = (date: Date) => {
|
||||||
|
return date.getDay() !== 1;
|
||||||
|
};
|
||||||
|
const disabledDateEnd = (date: Date) => {
|
||||||
|
return date.getDay() !== 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
getDeptList();
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
:deep(.warning-row) {
|
||||||
|
background-color: #fcf6ec;
|
||||||
|
/* Element Plus Warning Light Color or Custom Yellow */
|
||||||
|
background-color: #ffffcc !important;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
Loading…
Reference in New Issue