yusm 3 weeks ago
parent
commit
4edc6170ba

+ 3 - 2
fhKeeper/formulahousekeeper/management-platform/src/main/java/com/management/platform/controller/ReportController.java

@@ -3666,9 +3666,10 @@ public class ReportController {
 
     //各部门总工时和人均工时表
     @RequestMapping("/getProjectReportGroupByDept")
-    public HttpRespMsg getProjectReportGroupByDept(String ymonth) {
+    public HttpRespMsg getProjectReportGroupByDept(String ymonth,
+            @RequestParam(required = false) String deptMode) {
         User user = userMapper.selectById(request.getHeader("TOKEN"));
-        return reportService.getProjectReportGroupByDept(user.getCompanyId(), ymonth);
+        return reportService.getProjectReportGroupByDept(user.getCompanyId(), ymonth, deptMode);
     }
 
     //各部门参与项目数量表

+ 2 - 0
fhKeeper/formulahousekeeper/management-platform/src/main/java/com/management/platform/service/ReportService.java

@@ -241,6 +241,8 @@ public interface ReportService extends IService<Report> {
 
     HttpRespMsg getProjectReportGroupByDept(Integer companyId, String ymonth);
 
+    HttpRespMsg getProjectReportGroupByDept(Integer companyId, String ymonth, String deptMode);
+
     HttpRespMsg getDeptProjectCount(Integer companyId, String ymonth);
 
     HttpRespMsg getDashboardAnalysisReport(Integer companyId, String ymonth);

+ 38 - 1
fhKeeper/formulahousekeeper/management-platform/src/main/java/com/management/platform/service/impl/ReportServiceImpl.java

@@ -16293,8 +16293,15 @@ public class ReportServiceImpl extends ServiceImpl<ReportMapper, Report> impleme
     }
     @Override
     public HttpRespMsg getProjectReportGroupByDept(Integer companyId, String ymonth) {
+        return getProjectReportGroupByDept(companyId, ymonth, null);
+    }
+
+    @Override
+    public HttpRespMsg getProjectReportGroupByDept(Integer companyId, String ymonth, String deptMode) {
         HttpRespMsg msg = new HttpRespMsg();
         String month = normalizeYearMonth(ymonth);
+        boolean useTopLevel = "top".equals(deptMode);
+        
         List<Map<String, Object>> deptReportList = reportMapper.selectMaps(new QueryWrapper<Report>()
                 .select("dept_id as deptId", "creator_id as userId", "sum(working_time) as workingTime", "sum(ifnull(overtime_hours, 0)) as overtimeHours")
                 .eq("company_id", companyId)
@@ -16303,7 +16310,37 @@ public class ReportServiceImpl extends ServiceImpl<ReportMapper, Report> impleme
                 .groupBy("dept_id", "creator_id")
                 .orderByDesc("sum(working_time)"));
         Map<Integer, Department> departmentMap = getDepartmentMap(companyId);
-        List<HashMap> resultList = buildDeptReportResult(rollupDeptReportRows(deptReportList, departmentMap), departmentMap, true);
+        
+        List<Map<String, Object>> aggregated;
+        if (useTopLevel) {
+            aggregated = rollupDeptReportRows(deptReportList, departmentMap);
+        } else {
+            Map<Integer, HashMap<String, Object>> grouped = new LinkedHashMap<>();
+            deptReportList.forEach(row -> {
+                Integer deptId = toInteger(row.get("deptId"));
+                HashMap<String, Object> target = grouped.computeIfAbsent(deptId, key -> {
+                    HashMap<String, Object> map = new HashMap<>();
+                    map.put("deptId", deptId);
+                    map.put("workingTime", 0D);
+                    map.put("overtimeHours", 0D);
+                    map.put("memberIds", new HashSet<String>());
+                    return map;
+                });
+                target.put("workingTime", toDouble(target.get("workingTime")) + toDouble(row.get("workingTime")));
+                target.put("overtimeHours", toDouble(target.get("overtimeHours")) + toDouble(row.get("overtimeHours")));
+                String userId = row.get("userId") == null ? null : String.valueOf(row.get("userId"));
+                if (userId != null && !userId.isEmpty()) {
+                    ((Set<String>) target.get("memberIds")).add(userId);
+                }
+            });
+            grouped.values().forEach(row -> {
+                Set<String> memberIds = (Set<String>) row.remove("memberIds");
+                row.put("memberCount", memberIds != null ? memberIds.size() : 0);
+            });
+            aggregated = new ArrayList<>(grouped.values());
+        }
+        
+        List<HashMap> resultList = buildDeptReportResult(aggregated, departmentMap, true);
         msg.data = resultList;
         return msg;
     }

+ 83 - 33
fhKeeper/formulahousekeeper/timesheet/src/views/dashboard/index.vue

@@ -140,8 +140,8 @@
               :disabled="loading2"
               @change="onTop3DeptTabChange"
             >
-              <el-radio-button label="top">一级部门</el-radio-button>
-              <el-radio-button label="all">子孙部门明细</el-radio-button>
+              <el-radio-button label="top">部门明细</el-radio-button>
+              <el-radio-button label="all">小组明细</el-radio-button>
             </el-radio-group>
             <el-radio-group
               v-model="top3PieMode"
@@ -170,11 +170,22 @@
       <div class="chart-card">
         <div class="chart-title">
           <span>各部门总工时和人均工时表</span>
+          <div class="chart-title-actions">
+            <el-radio-group
+              v-model="deptHoursTabMode"
+              size="mini"
+              :disabled="loading3"
+              @change="onDeptHoursTabChange"
+            >
+              <el-radio-button label="top">部门明细</el-radio-button>
+              <el-radio-button label="all">小组明细</el-radio-button>
+            </el-radio-group>
+          </div>
         </div>
         <div v-if="loading3" class="chart-loading">
           <i class="el-icon-loading"></i> 加载中...
         </div>
-        <div v-else-if="deptHoursData.length === 0" class="chart-empty">
+        <div v-else-if="!hasDeptHoursData()" class="chart-empty">
           暂无数据
         </div>
         <div v-else ref="chart3" class="chart-body"></div>
@@ -403,6 +414,8 @@ export default {
       top3PieMode: "working",
       top3DeptTabMode: "top",
       top3DeptDataAll: [],
+      deptHoursTabMode: "top",
+      deptHoursDataAll: [],
       userProjectTop10Data: [],
       loading9: false,
     };
@@ -785,37 +798,66 @@ export default {
       });
     },
 
+    onDeptHoursTabChange() {
+      this.$nextTick(() => this.renderChart3());
+    },
+
+    hasDeptHoursData() {
+      const sourceData = this.deptHoursTabMode === "top" ? this.deptHoursData : this.deptHoursDataAll;
+      return (sourceData || []).some((dept) => Number(dept.workingTime || 0) > 0);
+    },
+
     loadDeptHours() {
       const requestMonth = this.selectedMonth;
       this.loading3 = true;
       this.deptHoursData = [];
-      this.http.post(
-        "/report/getProjectReportGroupByDept",
-        { ymonth: requestMonth },
-        (res) => {
-          if (!this.isCurrentMonthRequest(requestMonth)) return;
-          this.loading3 = false;
-          if (res.code === "ok") {
-            const data = res.data || [];
-            const processData = (translated) => {
-              this.deptHoursData = translated;
-              this.$nextTick(() => this.renderChart3());
-            };
-            if (this.needWxOpenData && data.length > 0) {
-              this.translateDeptNames(data, "departmentName", processData);
+      this.deptHoursDataAll = [];
+
+      const loadMode = (deptMode, storeKey, callback) => {
+        this.http.post(
+          "/report/getProjectReportGroupByDept",
+          { ymonth: requestMonth, deptMode },
+          (res) => {
+            if (!this.isCurrentMonthRequest(requestMonth)) return;
+            if (res.code === "ok") {
+              const data = res.data || [];
+              const processData = (translated) => {
+                this[storeKey] = translated;
+                if (callback) callback();
+              };
+              if (this.needWxOpenData && data.length > 0) {
+                this.translateDeptNames(data, "departmentName", processData);
+              } else {
+                processData(data);
+              }
             } else {
-              processData(data);
+              this.$message({ message: res.msg, type: "error" });
             }
-          } else {
-            this.$message({ message: res.msg, type: "error" });
-          }
-        },
-        (err) => {
-          if (!this.isCurrentMonthRequest(requestMonth)) return;
+          },
+          (err) => {
+            if (!this.isCurrentMonthRequest(requestMonth)) return;
+            this.$message({ message: err, type: "error" });
+          },
+        );
+      };
+
+      let topDone = false;
+      let allDone = false;
+      const tryFinish = () => {
+        if (topDone && allDone) {
           this.loading3 = false;
-          this.$message({ message: err, type: "error" });
-        },
-      );
+          this.$nextTick(() => this.renderChart3());
+        }
+      };
+
+      loadMode("top", "deptHoursData", () => {
+        topDone = true;
+        tryFinish();
+      });
+      loadMode(null, "deptHoursDataAll", () => {
+        allDone = true;
+        tryFinish();
+      });
     },
 
     loadDeptProjectCount() {
@@ -1453,18 +1495,26 @@ export default {
       const el = this.$refs.chart3;
       const parentEl = el.parentElement;
       const width = parentEl ? parentEl.clientWidth - 32 : el.clientWidth;
-      this.chart3 = echarts.init(el, null, { width, height: 300 });
 
-      const depts = this.deptHoursData.map((item) => item.departmentName);
-      const totalHours = this.deptHoursData.map((item) =>
+      const sourceData = this.deptHoursTabMode === "top" ? this.deptHoursData : this.deptHoursDataAll;
+      const chartData = (sourceData || []).filter((item) => Number(item.workingTime || 0) > 0);
+
+      const rowHeight = 28;
+      const minHeight = 300;
+      const height = Math.max(minHeight, chartData.length * rowHeight + 100);
+      el.style.height = height + "px";
+      this.chart3 = echarts.init(el, null, { width, height });
+
+      const depts = chartData.map((item) => item.departmentName);
+      const totalHours = chartData.map((item) =>
         Number((item.workingTime || 0).toFixed(2)),
       );
-      const avgHours = this.deptHoursData.map((item) =>
+      const avgHours = chartData.map((item) =>
         Number((item.avgWorkingTime || 0).toFixed(2)),
       );
 
       const deptOpenIdMap3 = {};
-      this.deptHoursData.forEach((item) => {
+      chartData.forEach((item) => {
         deptOpenIdMap3[item.departmentName] = item._deptOpenId || null;
       });
 
@@ -1477,7 +1527,7 @@ export default {
         deptOpenIdMap: deptOpenIdMap3,
       });
       this.chart3.setOption(option);
-      this.chart3.resize({ width, height: 300 });
+      this.chart3.resize({ width, height });
     },
 
     renderChart4() {