yusm 13 ساعت پیش
والد
کامیت
df02a14631

+ 26 - 9
fhKeeper/formulahousekeeper/management-platform/src/main/java/com/management/platform/service/impl/ReportServiceImpl.java

@@ -16108,7 +16108,8 @@ public class ReportServiceImpl extends ServiceImpl<ReportMapper, Report> impleme
         return msg;
     }
 
-    private static final int MAX_DASHBOARD_RANGE_DAYS = 366;
+    private static final int MAX_DASHBOARD_RANGE_MONTHS = 12;
+    private static final DateTimeFormatter YEAR_MONTH_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM");
 
     private static class DashboardDateFilter {
         private final String month;
@@ -16138,30 +16139,46 @@ public class ReportServiceImpl extends ServiceImpl<ReportMapper, Report> impleme
         boolean hasEnd = !StringUtils.isEmpty(endDate);
         if (hasStart && hasEnd) {
             try {
-                LocalDate start = LocalDate.parse(startDate);
-                LocalDate end = LocalDate.parse(endDate);
+                LocalDate start = parseDashboardRangeStart(startDate);
+                LocalDate end = parseDashboardRangeEnd(endDate);
                 if (end.isBefore(start)) {
-                    msg.setError("结束日期不能早于开始日期");
+                    msg.setError("结束月份不能早于开始月份");
                     return null;
                 }
-                long days = ChronoUnit.DAYS.between(start, end) + 1;
-                if (days > MAX_DASHBOARD_RANGE_DAYS) {
-                    msg.setError("查询时间区间最多不能超过一年");
+                long months = ChronoUnit.MONTHS.between(YearMonth.from(start), YearMonth.from(end)) + 1;
+                if (months > MAX_DASHBOARD_RANGE_MONTHS) {
+                    msg.setError("查询月份区间最多不能超过12个月");
                     return null;
                 }
                 return new DashboardDateFilter(null, start, end, true);
             } catch (DateTimeParseException e) {
-                msg.setError("日期格式错误,请使用yyyy-MM-dd");
+                msg.setError("月份格式错误,请使用yyyy-MM");
                 return null;
             }
         }
         if (hasStart || hasEnd) {
-            msg.setError("请同时选择开始日期和结束日期");
+            msg.setError("请同时选择开始月份和结束月份");
             return null;
         }
         return new DashboardDateFilter(normalizeYearMonth(ymonth), null, null, false);
     }
 
+    private LocalDate parseDashboardRangeStart(String startDate) {
+        String value = startDate.trim();
+        if (value.length() == 7) {
+            return YearMonth.parse(value, YEAR_MONTH_FORMATTER).atDay(1);
+        }
+        return LocalDate.parse(value);
+    }
+
+    private LocalDate parseDashboardRangeEnd(String endDate) {
+        String value = endDate.trim();
+        if (value.length() == 7) {
+            return YearMonth.parse(value, YEAR_MONTH_FORMATTER).atEndOfMonth();
+        }
+        return LocalDate.parse(value);
+    }
+
     @Override
     public HttpRespMsg getTop10ProjectReport(Integer companyId, String ymonth, String startDate, String endDate) {
         HttpRespMsg msg = new HttpRespMsg();

+ 52 - 128
fhKeeper/formulahousekeeper/timesheet/src/views/dashboard/index.vue

@@ -7,60 +7,24 @@
         <div class="dashboard-subtitle">项目、部门与加班数据概览</div>
       </div>
       <div class="dashboard-filter">
-        <el-radio-group
-          v-model="filterMode"
-          size="small"
-          @change="onFilterModeChange"
-        >
-          <el-radio-button label="month">按月</el-radio-button>
-          <el-radio-button label="range">按区间</el-radio-button>
-        </el-radio-group>
-        <div
-          class="dashboard-date-picker"
-          :class="filterMode === 'month' ? 'is-month' : 'is-range'"
-        >
-          <div
-            v-show="filterMode === 'month'"
-            class="dashboard-date-picker-slot"
-          >
-            <el-date-picker
-              ref="monthDatePicker"
-              class="dashboard-date-picker-input"
-              v-model="selectedMonth"
-              type="month"
-              placeholder="选择年月"
-              value-format="yyyy-MM"
-              :clearable="false"
-              align="right"
-              :append-to-body="true"
-              popper-class="dashboard-date-picker-popper"
-              @focus="onDatePickerFocus"
-              @change="onMonthChange"
-              size="small"
-            ></el-date-picker>
-          </div>
-          <div
-            v-show="filterMode === 'range'"
-            class="dashboard-date-picker-slot"
-          >
-            <el-date-picker
-              ref="rangeDatePicker"
-              class="dashboard-date-picker-input"
-              v-model="dateRange"
-              type="daterange"
-              align="right"
-              range-separator="至"
-              start-placeholder="开始日期"
-              end-placeholder="结束日期"
-              value-format="yyyy-MM-dd"
-              :clearable="false"
-              :append-to-body="true"
-              popper-class="dashboard-date-picker-popper"
-              @focus="onDatePickerFocus"
-              @change="onDateRangeChange"
-              size="small"
-            ></el-date-picker>
-          </div>
+        <div class="dashboard-date-picker is-range">
+          <el-date-picker
+            ref="rangeDatePicker"
+            class="dashboard-date-picker-input"
+            v-model="dateRange"
+            type="monthrange"
+            align="right"
+            range-separator="至"
+            start-placeholder="开始月份"
+            end-placeholder="结束月份"
+            value-format="yyyy-MM"
+            :clearable="false"
+            :append-to-body="true"
+            popper-class="dashboard-date-picker-popper"
+            @focus="onDatePickerFocus"
+            @change="onDateRangeChange"
+            size="small"
+          ></el-date-picker>
         </div>
       </div>
     </div>
@@ -428,15 +392,9 @@ export default {
     const now = new Date();
     const year = now.getFullYear();
     const month = String(now.getMonth() + 1).padStart(2, "0");
-    const lastDay = new Date(year, now.getMonth() + 1, 0).getDate();
     return {
       user: JSON.parse(sessionStorage.getItem("user")),
-      filterMode: "month",
-      selectedMonth: `${year}-${month}`,
-      dateRange: [
-        `${year}-${month}-01`,
-        `${year}-${month}-${String(lastDay).padStart(2, "0")}`,
-      ],
+      dateRange: [`${year}-${month}`, `${year}-${month}`],
       loading1: false,
       loading2: false,
       loading3: false,
@@ -540,7 +498,7 @@ export default {
           key: "workedProjectCount",
           label: "有工时投入项目数量",
           value: `${data.workedProjectCount || 0} 项`,
-          remark: "当月存在已审核工时",
+          remark: "所选区间存在已审核工时",
         },
         {
           key: "notWorkedExecutingProjectCount",
@@ -550,15 +508,15 @@ export default {
         },
         {
           key: "totalWorkingTime",
-          label: "当月总工时",
+          label: "区间总工时",
           value: `${data.totalWorkingTime || 0} h`,
           remark: "点击查看有工时投入项目明细",
         },
         {
           key: "totalOvertimeHours",
-          label: "当月加班总工时",
+          label: "区间加班总工时",
           value: `${data.totalOvertimeHours || 0} h`,
-          remark: "当月加班工时合计",
+          remark: "所选区间加班工时合计",
         },
       ];
     },
@@ -646,59 +604,44 @@ export default {
   methods: {
     // ========== 基础工具方法 ==========
     getQueryParams() {
-      if (this.filterMode === "range") {
-        return {
-          startDate: this.dateRange[0],
-          endDate: this.dateRange[1],
-        };
-      }
-      return { ymonth: this.selectedMonth };
+      return {
+        startDate: this.dateRange[0],
+        endDate: this.dateRange[1],
+      };
     },
     getQueryToken() {
-      if (this.filterMode === "range") {
-        return `range_${this.dateRange[0]}_${this.dateRange[1]}`;
-      }
-      return `month_${this.selectedMonth}`;
+      return `range_${this.dateRange[0]}_${this.dateRange[1]}`;
     },
     validateDateRange() {
-      if (this.filterMode !== "range") return true;
       if (!this.dateRange || this.dateRange.length !== 2) {
-        this.$message({ message: "请选择开始和结束日期", type: "warning" });
+        this.$message({ message: "请选择开始和结束月份", type: "warning" });
         return false;
       }
-      const start = new Date(this.dateRange[0]);
-      const end = new Date(this.dateRange[1]);
-      if (end < start) {
-        this.$message({ message: "结束日期不能早于开始日期", type: "warning" });
+      const [startMonth, endMonth] = this.dateRange;
+      if (endMonth < startMonth) {
+        this.$message({ message: "结束月份不能早于开始月份", type: "warning" });
         return false;
       }
-      const days =
-        Math.floor((end.getTime() - start.getTime()) / (24 * 3600 * 1000)) + 1;
-      if (days > 366) {
+      const monthCount = this.getMonthRangeCount(startMonth, endMonth);
+      if (monthCount > 12) {
         this.$message({
-          message: "查询时间区间最多不能超过一年",
+          message: "查询月份区间最多不能超过12个月",
           type: "warning",
         });
         return false;
       }
       return true;
     },
-    onFilterModeChange() {
-      this.closeDashboardDatePickers();
-      this.resetWorkedProjectDialog();
-      if (this.validateDateRange()) {
-        this.loadAllCharts();
-      }
+    getMonthRangeCount(startMonth, endMonth) {
+      const [startYear, startMon] = startMonth.split("-").map(Number);
+      const [endYear, endMon] = endMonth.split("-").map(Number);
+      return (endYear - startYear) * 12 + (endMon - startMon) + 1;
     },
     onDateRangeChange() {
       if (!this.validateDateRange()) return;
       this.resetWorkedProjectDialog();
       this.loadAllCharts();
     },
-    onMonthChange() {
-      this.resetWorkedProjectDialog();
-      this.loadAllCharts();
-    },
     onDatePickerFocus() {
       [0, 30, 80, 150].forEach((delay) => {
         setTimeout(() => this.fixDatePickerPopperPosition(), delay);
@@ -736,8 +679,7 @@ export default {
 
       const gap = 6;
       const margin = 8;
-      const popperWidth =
-        popper.offsetWidth || (picker.type === "daterange" ? 646 : 322);
+      const popperWidth = popper.offsetWidth || 300;
       const popperHeight = popper.offsetHeight || 320;
 
       let left = rect.right - popperWidth;
@@ -761,24 +703,20 @@ export default {
       popper.style.zIndex = "3000";
     },
     getActiveDatePickerRef() {
-      return this.filterMode === "month"
-        ? this.$refs.monthDatePicker
-        : this.$refs.rangeDatePicker;
+      return this.$refs.rangeDatePicker;
     },
     closeDashboardDatePickers() {
-      ["monthDatePicker", "rangeDatePicker"].forEach((refName) => {
-        const picker = this.$refs[refName];
-        if (!picker) return;
-        picker.pickerVisible = false;
-        if (picker.picker) {
-          picker.picker.visible = false;
-        }
-        if (picker.popperJS && picker.popperJS.destroy) {
-          picker.popperJS.destroy();
-          picker.popperJS = null;
-        }
-        picker.referenceElm = null;
-      });
+      const picker = this.$refs.rangeDatePicker;
+      if (!picker) return;
+      picker.pickerVisible = false;
+      if (picker.picker) {
+        picker.picker.visible = false;
+      }
+      if (picker.popperJS && picker.popperJS.destroy) {
+        picker.popperJS.destroy();
+        picker.popperJS = null;
+      }
+      picker.referenceElm = null;
     },
     updateOpenDatePickers() {
       this.fixDatePickerPopperPosition();
@@ -2454,18 +2392,10 @@ export default {
   box-sizing: border-box;
 }
 
-.dashboard-date-picker.is-month {
-  width: 220px;
-}
-
 .dashboard-date-picker.is-range {
   width: 350px;
 }
 
-.dashboard-date-picker-slot {
-  width: 100%;
-}
-
 .dashboard-date-picker ::v-deep .dashboard-date-picker-input.el-date-editor {
   width: 100% !important;
   max-width: 100%;
@@ -2473,11 +2403,6 @@ export default {
   vertical-align: middle;
 }
 
-.dashboard-date-picker.is-month ::v-deep .el-input__inner {
-  padding-left: 30px;
-  padding-right: 10px;
-}
-
 .dashboard-date-picker.is-range ::v-deep .el-range-editor.el-input__inner {
   padding: 3px 10px;
 }
@@ -2839,7 +2764,6 @@ export default {
     flex-wrap: wrap;
   }
 
-  .dashboard-date-picker.is-month,
   .dashboard-date-picker.is-range {
     width: 100%;
     max-width: 350px;