Преглед на файлове

抽问抽答完成趋势回滚原接口,新增completion-comparison多期对比接口

/daily-exam/completion-trend 恢复为趋势折线接口(按角色分级展示)
新增 /daily-exam/completion-comparison 实现多期聚合对比(适配美兰4级架构):
  YEAR返回[去年,今年],QUARTER返回[去年同季度,上季度,本季度],
  MONTH返回[去年同月,上月,本月],携带同比(yoyRate)和环比(chainRatio)。
  scopeType: STATION按大队分组+全站汇总,BRIGADE按主管分组,MANAGER/USER单条series。
simonlll преди 3 седмици
родител
ревизия
af4d21c356

+ 404 - 0
airport-admin/src/main/java/com/sundot/airport/web/controller/exam/DailyExamComparisonController.java

@@ -0,0 +1,404 @@
1
+package com.sundot.airport.web.controller.exam;
2
+
3
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
4
+import com.sundot.airport.common.core.domain.entity.SysDept;
5
+import com.sundot.airport.common.core.domain.entity.SysRole;
6
+import com.sundot.airport.common.core.domain.model.LoginUser;
7
+import com.sundot.airport.common.enums.RoleTypeEnum;
8
+import com.sundot.airport.common.utils.SecurityUtils;
9
+import com.sundot.airport.exam.common.HttpResult;
10
+import com.sundot.airport.exam.domain.DailyTask;
11
+import com.sundot.airport.exam.mapper.DailyTaskMapper;
12
+import com.sundot.airport.system.mapper.SysDeptMapper;
13
+import org.slf4j.Logger;
14
+import org.slf4j.LoggerFactory;
15
+import org.springframework.beans.factory.annotation.Autowired;
16
+import org.springframework.web.bind.annotation.GetMapping;
17
+import org.springframework.web.bind.annotation.RequestMapping;
18
+import org.springframework.web.bind.annotation.RequestParam;
19
+import org.springframework.web.bind.annotation.RestController;
20
+
21
+import java.math.BigDecimal;
22
+import java.math.RoundingMode;
23
+import java.sql.Date;
24
+import java.time.LocalDate;
25
+import java.util.*;
26
+import java.util.stream.Collectors;
27
+
28
+/**
29
+ * 抽问抽答完成趋势对比接口(美兰4级架构)
30
+ * STATION > 大队(BRIGADE) > 主管(MANAGER) > 班组
31
+ * 始终返回多期聚合对比数据:
32
+ *   YEAR:[去年, 今年] 含同比(yoyRate)
33
+ *   QUARTER:[去年同季度, 上季度, 本季度] 含同比+环比(chainRatio)
34
+ *   MONTH:[去年同月, 上月, 本月] 含同比+环比
35
+ */
36
+@RestController
37
+@RequestMapping("/v1/cs/app/daily-exam")
38
+public class DailyExamComparisonController {
39
+
40
+    private static final Logger log = LoggerFactory.getLogger(DailyExamComparisonController.class);
41
+
42
+    @Autowired
43
+    private DailyTaskMapper dailyTaskMapper;
44
+
45
+    @Autowired
46
+    private SysDeptMapper sysDeptMapper;
47
+
48
+    /**
49
+     * 抽问抽答完成趋势对比
50
+     *
51
+     * @param dateRangeQueryType 时间类型:YEAR/QUARTER/MONTH,默认 MONTH
52
+     * @param year               年份,默认当前年
53
+     * @param quarter            季度 1-4,dateRangeQueryType=QUARTER 时有效
54
+     * @param month              月份 1-12,dateRangeQueryType=MONTH 时有效
55
+     * @param scopeType          统计范围:STATION/BRIGADE/MANAGER/USER,不传则按登录角色自动判断
56
+     * @param scopeId            统计范围ID(deptId 或 userId)
57
+     */
58
+    @GetMapping("/completion-comparison")
59
+    public HttpResult<Map<String, Object>> getCompletionComparison(
60
+            @RequestParam(required = false, defaultValue = "MONTH") String dateRangeQueryType,
61
+            @RequestParam(required = false) Integer year,
62
+            @RequestParam(required = false) Integer quarter,
63
+            @RequestParam(required = false) Integer month,
64
+            @RequestParam(required = false) String scopeType,
65
+            @RequestParam(required = false) Long scopeId) {
66
+
67
+        try {
68
+            LoginUser loginUser = SecurityUtils.getLoginUser();
69
+            List<String> roleKeys = loginUser.getUser().getRoles().stream()
70
+                    .map(SysRole::getRoleKey).collect(Collectors.toList());
71
+            Long userDeptId = loginUser.getDeptId();
72
+            Long userId = loginUser.getUserId();
73
+
74
+            boolean isStation = userId == 1L
75
+                    || roleKeys.contains(RoleTypeEnum.admin.getCode())
76
+                    || roleKeys.contains(RoleTypeEnum.test.getCode())
77
+                    || roleKeys.contains(RoleTypeEnum.zhijianke.getCode());
78
+            boolean isBrigade = !isStation && (roleKeys.contains(RoleTypeEnum.jingli.getCode())
79
+                    || roleKeys.contains(RoleTypeEnum.xingzheng.getCode()));
80
+            boolean isManager = !isStation && !isBrigade && roleKeys.contains(RoleTypeEnum.kezhang.getCode());
81
+
82
+            // 班组长及以下不显示(未传 scopeType 且无管理权限)
83
+            if (scopeType == null && !isStation && !isBrigade && !isManager) {
84
+                Map<String, Object> noShow = new HashMap<>();
85
+                noShow.put("show", false);
86
+                return HttpResult.success(noShow);
87
+            }
88
+
89
+            int curYear = (year != null) ? year : LocalDate.now().getYear();
90
+            int curQuarter = (quarter != null) ? quarter : ((LocalDate.now().getMonthValue() - 1) / 3 + 1);
91
+            int curMonth = (month != null) ? month : LocalDate.now().getMonthValue();
92
+
93
+            List<LocalDate[]> periods = buildPeriods(dateRangeQueryType, curYear, curQuarter, curMonth);
94
+            List<String> xAxis = buildXAxisLabels(dateRangeQueryType, curYear, curQuarter, curMonth);
95
+
96
+            Map<String, Object> result = new LinkedHashMap<>();
97
+            result.put("show", true);
98
+            result.put("xAxis", xAxis);
99
+            result.put("series", buildComparisonSeries(
100
+                    scopeType, scopeId, periods, dateRangeQueryType,
101
+                    isStation, isBrigade, isManager, userDeptId, loginUser));
102
+
103
+            return HttpResult.success(result);
104
+
105
+        } catch (Exception e) {
106
+            log.error("获取抽问抽答完成趋势对比失败", e);
107
+            return HttpResult.error("获取完成趋势对比失败:" + e.getMessage());
108
+        }
109
+    }
110
+
111
+    /**
112
+     * 构建各期日期范围列表
113
+     * YEAR:[去年, 今年]
114
+     * QUARTER:[去年同季度, 上季度, 本季度]
115
+     * MONTH:[去年同月, 上月, 本月]
116
+     */
117
+    private List<LocalDate[]> buildPeriods(String type, int year, int quarter, int month) {
118
+        List<LocalDate[]> periods = new ArrayList<>();
119
+        switch (type == null ? "MONTH" : type) {
120
+            case "YEAR":
121
+                periods.add(new LocalDate[]{LocalDate.of(year - 1, 1, 1), LocalDate.of(year - 1, 12, 31)});
122
+                periods.add(new LocalDate[]{LocalDate.of(year, 1, 1), LocalDate.of(year, 12, 31)});
123
+                break;
124
+            case "QUARTER":
125
+                periods.add(resolveDateRange("QUARTER", year - 1, quarter, null));
126
+                int prevQ = quarter - 1;
127
+                int prevQYear = year;
128
+                if (prevQ < 1) { prevQ = 4; prevQYear = year - 1; }
129
+                periods.add(resolveDateRange("QUARTER", prevQYear, prevQ, null));
130
+                periods.add(resolveDateRange("QUARTER", year, quarter, null));
131
+                break;
132
+            case "MONTH":
133
+            default:
134
+                periods.add(resolveDateRange("MONTH", year - 1, null, month));
135
+                int prevM = month - 1;
136
+                int prevMYear = year;
137
+                if (prevM < 1) { prevM = 12; prevMYear = year - 1; }
138
+                periods.add(resolveDateRange("MONTH", prevMYear, null, prevM));
139
+                periods.add(resolveDateRange("MONTH", year, null, month));
140
+                break;
141
+        }
142
+        return periods;
143
+    }
144
+
145
+    /**
146
+     * 构建 X 轴中文标签列表
147
+     */
148
+    private List<String> buildXAxisLabels(String type, int year, int quarter, int month) {
149
+        List<String> labels = new ArrayList<>();
150
+        switch (type == null ? "MONTH" : type) {
151
+            case "YEAR":
152
+                labels.add((year - 1) + "年");
153
+                labels.add(year + "年");
154
+                break;
155
+            case "QUARTER":
156
+                labels.add((year - 1) + "年第" + quarter + "季度");
157
+                int prevQ = quarter - 1;
158
+                int prevQYear = year;
159
+                if (prevQ < 1) { prevQ = 4; prevQYear = year - 1; }
160
+                labels.add(prevQYear + "年第" + prevQ + "季度");
161
+                labels.add(year + "年第" + quarter + "季度");
162
+                break;
163
+            case "MONTH":
164
+            default:
165
+                labels.add((year - 1) + "年" + month + "月");
166
+                int prevM = month - 1;
167
+                int prevMYear = year;
168
+                if (prevM < 1) { prevM = 12; prevMYear = year - 1; }
169
+                labels.add(prevMYear + "年" + prevM + "月");
170
+                labels.add(year + "年" + month + "月");
171
+                break;
172
+        }
173
+        return labels;
174
+    }
175
+
176
+    /**
177
+     * 构建多期对比 series 列表(适配美兰4级架构)
178
+     * STATION:各大队各一条 series + 全站汇总
179
+     * BRIGADE:该大队各主管各一条 series
180
+     * MANAGER/USER:单条 series
181
+     */
182
+    private List<Map<String, Object>> buildComparisonSeries(
183
+            String scopeType, Long scopeId, List<LocalDate[]> periods, String dateRangeQueryType,
184
+            boolean isStation, boolean isBrigade, boolean isManager, Long userDeptId, LoginUser loginUser) {
185
+
186
+        boolean isYearType = "YEAR".equals(dateRangeQueryType);
187
+
188
+        if ("MANAGER".equals(scopeType) && scopeId != null) {
189
+            String name = "主管" + scopeId;
190
+            List<Integer> data = new ArrayList<>();
191
+            for (LocalDate[] p : periods) {
192
+                List<DailyTask> tasks = queryTasksByDepartmentId(scopeId, p[0], p[1]);
193
+                if (name.startsWith("主管")) {
194
+                    name = tasks.stream().map(DailyTask::getDtDepartmentName)
195
+                            .filter(Objects::nonNull).findFirst().orElse(name);
196
+                }
197
+                data.add(countCompleted(tasks));
198
+            }
199
+            return Collections.singletonList(buildSeriesItem(name, scopeId, null, data, isYearType));
200
+
201
+        } else if ("USER".equals(scopeType) && scopeId != null) {
202
+            String name = "用户" + scopeId;
203
+            List<Integer> data = new ArrayList<>();
204
+            for (LocalDate[] p : periods) {
205
+                List<DailyTask> tasks = queryTasksByUserId(scopeId, p[0], p[1]);
206
+                if (name.startsWith("用户")) {
207
+                    name = tasks.stream().map(DailyTask::getDtUserName)
208
+                            .filter(Objects::nonNull).findFirst().orElse(name);
209
+                }
210
+                data.add(countCompleted(tasks));
211
+            }
212
+            return Collections.singletonList(buildSeriesItem(name, null, scopeId, data, isYearType));
213
+
214
+        } else if ("BRIGADE".equals(scopeType) && scopeId != null) {
215
+            return buildBrigadeSeries(scopeId, periods, isYearType);
216
+
217
+        } else {
218
+            Long stationDeptId = ("STATION".equals(scopeType) && scopeId != null) ? scopeId : userDeptId;
219
+            if (isStation || "STATION".equals(scopeType)) {
220
+                return buildStationSeries(stationDeptId, periods, isYearType);
221
+            } else if (isBrigade) {
222
+                return buildBrigadeSeries(userDeptId, periods, isYearType);
223
+            } else {
224
+                // 主管:单条 series
225
+                String deptName = loginUser.getUser().getDept() != null
226
+                        ? loginUser.getUser().getDept().getDeptName() : "本主管";
227
+                List<Integer> data = new ArrayList<>();
228
+                for (LocalDate[] p : periods) {
229
+                    data.add(countCompleted(queryTasksByDepartmentId(userDeptId, p[0], p[1])));
230
+                }
231
+                return Collections.singletonList(buildSeriesItem(deptName, userDeptId, null, data, isYearType));
232
+            }
233
+        }
234
+    }
235
+
236
+    /**
237
+     * 站长视角:按大队分组,各期聚合 + 全站汇总
238
+     */
239
+    private List<Map<String, Object>> buildStationSeries(Long stationDeptId, List<LocalDate[]> periods, boolean isYearType) {
240
+        List<SysDept> subDepts = sysDeptMapper.selectChildrenDeptById(stationDeptId);
241
+        if (subDepts == null) subDepts = Collections.emptyList();
242
+
243
+        // 大队(站的直接下级)
244
+        List<SysDept> brigadeDepts = subDepts.stream()
245
+                .filter(d -> stationDeptId.equals(d.getParentId()) && "0".equals(d.getDelFlag()))
246
+                .collect(Collectors.toList());
247
+
248
+        // 主管deptId -> 所属大队deptId
249
+        Map<Long, Long> managerToBrigadeId = new HashMap<>();
250
+        for (SysDept d : subDepts) {
251
+            if ("0".equals(d.getDelFlag()) && d.getParentId() != null && !stationDeptId.equals(d.getParentId())) {
252
+                managerToBrigadeId.put(d.getDeptId(), d.getParentId());
253
+            }
254
+        }
255
+
256
+        Set<Long> allDeptIds = new HashSet<>();
257
+        allDeptIds.add(stationDeptId);
258
+        subDepts.forEach(d -> allDeptIds.add(d.getDeptId()));
259
+
260
+        List<List<DailyTask>> periodTasks = new ArrayList<>();
261
+        for (LocalDate[] p : periods) {
262
+            periodTasks.add(queryTasksByDeptIds(allDeptIds, p[0], p[1]));
263
+        }
264
+
265
+        List<Map<String, Object>> series = new ArrayList<>();
266
+        List<Integer> totalData = new ArrayList<>();
267
+        for (int i = 0; i < periods.size(); i++) totalData.add(0);
268
+
269
+        for (SysDept brigade : brigadeDepts) {
270
+            Long brigadeId = brigade.getDeptId();
271
+            List<Integer> data = new ArrayList<>();
272
+            for (int i = 0; i < periodTasks.size(); i++) {
273
+                int count = countCompleted(periodTasks.get(i).stream()
274
+                        .filter(t -> {
275
+                            Long managerId = t.getDtDepartmentId();
276
+                            if (managerId == null) return false;
277
+                            return brigadeId.equals(managerToBrigadeId.get(managerId));
278
+                        })
279
+                        .collect(Collectors.toList()));
280
+                data.add(count);
281
+                totalData.set(i, totalData.get(i) + count);
282
+            }
283
+            series.add(buildSeriesItem(brigade.getDeptName(), brigadeId, null, data, isYearType));
284
+        }
285
+        series.add(buildSeriesItem("全站", null, null, totalData, isYearType));
286
+        return series;
287
+    }
288
+
289
+    /**
290
+     * 大队视角:按主管分组,各期聚合
291
+     */
292
+    private List<Map<String, Object>> buildBrigadeSeries(Long brigadeId, List<LocalDate[]> periods, boolean isYearType) {
293
+        List<SysDept> subDepts = sysDeptMapper.selectChildrenDeptById(brigadeId);
294
+        if (subDepts == null) subDepts = Collections.emptyList();
295
+
296
+        List<SysDept> managerDepts = subDepts.stream()
297
+                .filter(d -> brigadeId.equals(d.getParentId()) && "0".equals(d.getDelFlag()))
298
+                .collect(Collectors.toList());
299
+
300
+        List<Map<String, Object>> series = new ArrayList<>();
301
+        for (SysDept manager : managerDepts) {
302
+            List<Integer> data = new ArrayList<>();
303
+            for (LocalDate[] p : periods) {
304
+                data.add(countCompleted(queryTasksByDepartmentId(manager.getDeptId(), p[0], p[1])));
305
+            }
306
+            series.add(buildSeriesItem(manager.getDeptName(), manager.getDeptId(), null, data, isYearType));
307
+        }
308
+        return series;
309
+    }
310
+
311
+    /**
312
+     * 构建单条 series map,含同比/环比
313
+     * data[0]=去年同期,data[1]=上期(仅QUARTER/MONTH),data[last]=本期
314
+     */
315
+    private Map<String, Object> buildSeriesItem(String name, Long deptId, Long userId,
316
+                                                 List<Integer> data, boolean isYearType) {
317
+        Map<String, Object> s = new LinkedHashMap<>();
318
+        s.put("name", name);
319
+        if (deptId != null) s.put("deptId", deptId);
320
+        if (userId != null) s.put("userId", userId);
321
+        s.put("data", data);
322
+        int current = data.get(data.size() - 1);
323
+        // 同比:本期 vs 去年同期(data[0])
324
+        s.put("yoyRate", calcRate(current, data.get(0)));
325
+        // 环比:本期 vs 上一期(data[last-1]),仅 QUARTER/MONTH 有效
326
+        if (!isYearType && data.size() >= 3) {
327
+            s.put("chainRatio", calcRate(current, data.get(data.size() - 2)));
328
+        } else {
329
+            s.put("chainRatio", null);
330
+        }
331
+        return s;
332
+    }
333
+
334
+    /**
335
+     * 计算变化率百分比,base=0 时返回 null
336
+     */
337
+    private BigDecimal calcRate(int current, int base) {
338
+        if (base == 0) return null;
339
+        return BigDecimal.valueOf(current - base)
340
+                .multiply(BigDecimal.valueOf(100))
341
+                .divide(BigDecimal.valueOf(base), 2, RoundingMode.HALF_UP);
342
+    }
343
+
344
+    /**
345
+     * 统计 COMPLETED 任务数
346
+     */
347
+    private int countCompleted(List<DailyTask> tasks) {
348
+        return (int) tasks.stream().filter(t -> "COMPLETED".equals(t.getDtStatus())).count();
349
+    }
350
+
351
+    /**
352
+     * 根据 type/year/quarter/month 计算起止日期
353
+     */
354
+    private LocalDate[] resolveDateRange(String type, Integer year, Integer quarter, Integer month) {
355
+        LocalDate today = LocalDate.now();
356
+        int y = (year != null) ? year : today.getYear();
357
+        LocalDate start, end;
358
+        switch (type == null ? "MONTH" : type) {
359
+            case "YEAR":
360
+                start = LocalDate.of(y, 1, 1);
361
+                end = LocalDate.of(y, 12, 31);
362
+                break;
363
+            case "QUARTER":
364
+                int q = (quarter != null) ? quarter : 1;
365
+                start = LocalDate.of(y, (q - 1) * 3 + 1, 1);
366
+                end = start.plusMonths(3).minusDays(1);
367
+                break;
368
+            case "MONTH":
369
+            default:
370
+                int m = (month != null) ? month : today.getMonthValue();
371
+                start = LocalDate.of(y, m, 1);
372
+                end = start.withDayOfMonth(start.lengthOfMonth());
373
+                break;
374
+        }
375
+        return new LocalDate[]{start, end};
376
+    }
377
+
378
+    private List<DailyTask> queryTasksByDeptIds(Set<Long> deptIds, LocalDate start, LocalDate end) {
379
+        LambdaQueryWrapper<DailyTask> wrapper = new LambdaQueryWrapper<>();
380
+        wrapper.in(DailyTask::getDtDeptId, deptIds);
381
+        wrapper.ge(DailyTask::getDtBusinessDate, Date.valueOf(start));
382
+        wrapper.le(DailyTask::getDtBusinessDate, Date.valueOf(end));
383
+        wrapper.eq(DailyTask::getDelStatus, 0);
384
+        return dailyTaskMapper.selectList(wrapper);
385
+    }
386
+
387
+    private List<DailyTask> queryTasksByDepartmentId(Long departmentId, LocalDate start, LocalDate end) {
388
+        LambdaQueryWrapper<DailyTask> wrapper = new LambdaQueryWrapper<>();
389
+        wrapper.eq(DailyTask::getDtDepartmentId, departmentId);
390
+        wrapper.ge(DailyTask::getDtBusinessDate, Date.valueOf(start));
391
+        wrapper.le(DailyTask::getDtBusinessDate, Date.valueOf(end));
392
+        wrapper.eq(DailyTask::getDelStatus, 0);
393
+        return dailyTaskMapper.selectList(wrapper);
394
+    }
395
+
396
+    private List<DailyTask> queryTasksByUserId(Long userId, LocalDate start, LocalDate end) {
397
+        LambdaQueryWrapper<DailyTask> wrapper = new LambdaQueryWrapper<>();
398
+        wrapper.eq(DailyTask::getDtUserId, userId);
399
+        wrapper.ge(DailyTask::getDtBusinessDate, Date.valueOf(start));
400
+        wrapper.le(DailyTask::getDtBusinessDate, Date.valueOf(end));
401
+        wrapper.eq(DailyTask::getDelStatus, 0);
402
+        return dailyTaskMapper.selectList(wrapper);
403
+    }
404
+}

+ 140 - 289
airport-admin/src/main/java/com/sundot/airport/web/controller/exam/DailyExamController.java

@@ -5,6 +5,8 @@ import com.sundot.airport.common.core.domain.entity.SysDept;
5 5
 import com.sundot.airport.common.core.domain.entity.SysRole;
6 6
 import com.sundot.airport.common.core.domain.model.LoginUser;
7 7
 import com.sundot.airport.common.enums.RoleTypeEnum;
8
+import com.sundot.airport.common.core.domain.SysAnalysisReportParamDto;
9
+import com.sundot.airport.common.utils.DateRangeQueryUtils;
8 10
 import com.sundot.airport.common.utils.SecurityUtils;
9 11
 import com.sundot.airport.exam.adapter.DailyTaskToExamAdapter;
10 12
 import com.sundot.airport.exam.common.ClientTypeConstant;
@@ -32,11 +34,10 @@ import org.springframework.validation.annotation.Validated;
32 34
 import org.springframework.web.bind.annotation.*;
33 35
 
34 36
 import javax.annotation.Resource;
35
-import java.math.BigDecimal;
36
-import java.math.RoundingMode;
37 37
 import java.sql.Date;
38 38
 import java.text.SimpleDateFormat;
39 39
 import java.time.LocalDate;
40
+import java.time.format.DateTimeFormatter;
40 41
 import java.util.*;
41 42
 import java.util.stream.Collectors;
42 43
 
@@ -291,27 +292,16 @@ public class DailyExamController {
291 292
 
292 293
     /**
293 294
      * 抽问抽答完成趋势
294
-     * 美兰4级架构:STATION > 大队(BRIGADE) > 主管(MANAGER) > 班组
295
-     * 始终返回多期对比数据:
296
-     *   YEAR:[去年, 今年] 含同比
297
-     *   QUARTER:[去年同季度, 上季度, 本季度] 含同比+环比
298
-     *   MONTH:[去年同月, 上月, 本月] 含同比+环比
295
+     * 美兰4级架构:STATION > 大队(BRIGADE) > 主管(MANAGER) > 班组(TEAMS)
296
+     * 站长/管理员/质检科:各大队完成趋势折线
297
+     * 大队长(经理/行政):本大队各主管完成趋势折线
298
+     * 主管:本主管室完成趋势(单条折线)
299
+     * 班组长/安检员:show=false
299 300
      *
300
-     * @param dateRangeQueryType 时间类型:YEAR/QUARTER/MONTH,默认 MONTH
301
-     * @param year               年份,默认当前年
302
-     * @param quarter            季度 1-4,dateRangeQueryType=QUARTER 时有效
303
-     * @param month              月份 1-12,dateRangeQueryType=MONTH 时有效
304
-     * @param scopeType          统计范围:STATION/BRIGADE/MANAGER/USER,不传则按登录角色自动判断
305
-     * @param scopeId            统计范围ID(deptId 或 userId)
301
+     * @param param dateRangeQueryType=YEAR|QUARTER|MONTH,year,quarter,month
306 302
      */
307 303
     @GetMapping("/completion-trend")
308
-    public HttpResult<Map<String, Object>> getCompletionTrend(
309
-            @RequestParam(required = false, defaultValue = "MONTH") String dateRangeQueryType,
310
-            @RequestParam(required = false) Integer year,
311
-            @RequestParam(required = false) Integer quarter,
312
-            @RequestParam(required = false) Integer month,
313
-            @RequestParam(required = false) String scopeType,
314
-            @RequestParam(required = false) Long scopeId) {
304
+    public HttpResult<Map<String, Object>> getCompletionTrend(SysAnalysisReportParamDto param) {
315 305
 
316 306
         try {
317 307
             LoginUser loginUser = SecurityUtils.getLoginUser();
@@ -326,29 +316,100 @@ public class DailyExamController {
326 316
                     || roleKeys.contains(RoleTypeEnum.zhijianke.getCode());
327 317
             boolean isBrigade = !isStation && (roleKeys.contains(RoleTypeEnum.jingli.getCode())
328 318
                     || roleKeys.contains(RoleTypeEnum.xingzheng.getCode()));
329
-            boolean isManager = !isStation && !isBrigade && roleKeys.contains(RoleTypeEnum.kezhang.getCode());
319
+            boolean isKezhang = !isStation && !isBrigade && roleKeys.contains(RoleTypeEnum.kezhang.getCode());
330 320
 
331
-            // 班组长及以下不显示(未传 scopeType 且无管理权限)
332
-            if (scopeType == null && !isStation && !isBrigade && !isManager) {
321
+            // 班组长及以下不显示此tab
322
+            if (!isStation && !isBrigade && !isKezhang) {
333 323
                 Map<String, Object> noShow = new HashMap<>();
334 324
                 noShow.put("show", false);
335 325
                 return HttpResult.success(noShow);
336 326
             }
337 327
 
338
-            int curYear = (year != null) ? year : LocalDate.now().getYear();
339
-            int curQuarter = (quarter != null) ? quarter : ((LocalDate.now().getMonthValue() - 1) / 3 + 1);
340
-            int curMonth = (month != null) ? month : LocalDate.now().getMonthValue();
328
+            LocalDate[] range = resolveDateRange(param);
329
+            LocalDate start = range[0];
330
+            LocalDate end = range[1];
331
+            boolean byMonth = "YEAR".equals(param != null ? param.getDateRangeQueryType() : null);
332
+            List<String> xAxis = buildXAxis(start, end, byMonth);
333
+            List<Map<String, Object>> series = new ArrayList<>();
334
+
335
+            if (isStation) {
336
+                // 站长:查该站所有子部门,按大队分组
337
+                List<SysDept> subDepts = sysDeptMapper.selectChildrenDeptById(userDeptId);
338
+                if (subDepts == null) subDepts = Collections.emptyList();
339
+
340
+                // 大队(站的直接下级)
341
+                List<SysDept> brigadeDepts = subDepts.stream()
342
+                        .filter(d -> userDeptId.equals(d.getParentId()) && "0".equals(d.getDelFlag()))
343
+                        .collect(Collectors.toList());
344
+
345
+                // 构建 主管deptId -> 所属大队deptId 的映射
346
+                Map<Long, Long> managerToBrigadeId = new HashMap<>();
347
+                for (SysDept d : subDepts) {
348
+                    if ("0".equals(d.getDelFlag()) && d.getParentId() != null && !userDeptId.equals(d.getParentId())) {
349
+                        // 非直接下级,其parentId为某个大队,记录映射(主管级别)
350
+                        managerToBrigadeId.put(d.getDeptId(), d.getParentId());
351
+                    }
352
+                }
353
+
354
+                // 查全站所有任务
355
+                Set<Long> allDeptIds = new HashSet<>();
356
+                allDeptIds.add(userDeptId);
357
+                subDepts.forEach(d -> allDeptIds.add(d.getDeptId()));
358
+                List<DailyTask> allTasks = queryTasksByDeptIds(allDeptIds, start, end);
359
+
360
+                // 按大队分组生成折线
361
+                for (SysDept brigade : brigadeDepts) {
362
+                    Long brigadeId = brigade.getDeptId();
363
+                    List<DailyTask> brigadeTasks = allTasks.stream()
364
+                            .filter(t -> {
365
+                                Long managerId = t.getDtDepartmentId();
366
+                                if (managerId == null) return false;
367
+                                Long parentBrigadeId = managerToBrigadeId.get(managerId);
368
+                                return brigadeId.equals(parentBrigadeId);
369
+                            })
370
+                            .collect(Collectors.toList());
371
+                    Map<String, Object> item = new LinkedHashMap<>();
372
+                    item.put("name", brigade.getDeptName());
373
+                    item.put("deptId", brigadeId);
374
+                    item.put("data", buildSeriesData(xAxis, brigadeTasks, byMonth));
375
+                    series.add(item);
376
+                }
377
+
378
+            } else if (isBrigade) {
379
+                // 大队长:查本大队下各主管的任务
380
+                List<SysDept> subDepts = sysDeptMapper.selectChildrenDeptById(userDeptId);
381
+                if (subDepts == null) subDepts = Collections.emptyList();
382
+
383
+                // 主管(大队的直接下级)
384
+                List<SysDept> managerDepts = subDepts.stream()
385
+                        .filter(d -> userDeptId.equals(d.getParentId()) && "0".equals(d.getDelFlag()))
386
+                        .collect(Collectors.toList());
387
+
388
+                for (SysDept manager : managerDepts) {
389
+                    List<DailyTask> tasks = queryTasksByDepartmentId(manager.getDeptId(), start, end);
390
+                    Map<String, Object> item = new LinkedHashMap<>();
391
+                    item.put("name", manager.getDeptName());
392
+                    item.put("deptId", manager.getDeptId());
393
+                    item.put("data", buildSeriesData(xAxis, tasks, byMonth));
394
+                    series.add(item);
395
+                }
341 396
 
342
-            List<LocalDate[]> periods = buildPeriods(dateRangeQueryType, curYear, curQuarter, curMonth);
343
-            List<String> xAxis = buildXAxisLabels(dateRangeQueryType, curYear, curQuarter, curMonth);
397
+            } else {
398
+                // 主管:本主管室所有任务,一条折线
399
+                List<DailyTask> tasks = queryTasksByDepartmentId(userDeptId, start, end);
400
+                String deptName = loginUser.getUser().getDept() != null
401
+                        ? loginUser.getUser().getDept().getDeptName() : "本主管";
402
+                Map<String, Object> item = new LinkedHashMap<>();
403
+                item.put("name", deptName);
404
+                item.put("deptId", userDeptId);
405
+                item.put("data", buildSeriesData(xAxis, tasks, byMonth));
406
+                series.add(item);
407
+            }
344 408
 
345 409
             Map<String, Object> result = new LinkedHashMap<>();
346 410
             result.put("show", true);
347 411
             result.put("xAxis", xAxis);
348
-            result.put("series", buildComparisonSeries(
349
-                    scopeType, scopeId, periods, dateRangeQueryType,
350
-                    isStation, isBrigade, isManager, userDeptId, loginUser));
351
-
412
+            result.put("series", series);
352 413
             return HttpResult.success(result);
353 414
 
354 415
         } catch (Exception e) {
@@ -358,273 +419,75 @@ public class DailyExamController {
358 419
     }
359 420
 
360 421
     /**
361
-     * 构建各期日期范围列表
362
-     * YEAR:[去年, 今年]
363
-     * QUARTER:[去年同季度, 上季度, 本季度]
364
-     * MONTH:[去年同月, 上月, 本月]
365
-     */
366
-    private List<LocalDate[]> buildPeriods(String type, int year, int quarter, int month) {
367
-        List<LocalDate[]> periods = new ArrayList<>();
368
-        switch (type == null ? "MONTH" : type) {
369
-            case "YEAR":
370
-                periods.add(new LocalDate[]{LocalDate.of(year - 1, 1, 1), LocalDate.of(year - 1, 12, 31)});
371
-                periods.add(new LocalDate[]{LocalDate.of(year, 1, 1), LocalDate.of(year, 12, 31)});
372
-                break;
373
-            case "QUARTER":
374
-                periods.add(resolveDateRange("QUARTER", year - 1, quarter, null));
375
-                int prevQ = quarter - 1;
376
-                int prevQYear = year;
377
-                if (prevQ < 1) { prevQ = 4; prevQYear = year - 1; }
378
-                periods.add(resolveDateRange("QUARTER", prevQYear, prevQ, null));
379
-                periods.add(resolveDateRange("QUARTER", year, quarter, null));
380
-                break;
381
-            case "MONTH":
382
-            default:
383
-                periods.add(resolveDateRange("MONTH", year - 1, null, month));
384
-                int prevM = month - 1;
385
-                int prevMYear = year;
386
-                if (prevM < 1) { prevM = 12; prevMYear = year - 1; }
387
-                periods.add(resolveDateRange("MONTH", prevMYear, null, prevM));
388
-                periods.add(resolveDateRange("MONTH", year, null, month));
389
-                break;
390
-        }
391
-        return periods;
392
-    }
393
-
394
-    /**
395
-     * 构建 X 轴中文标签列表
422
+     * 根据 SysAnalysisReportParamDto 计算起止日期
423
+     * YEAR → 全年;QUARTER → 指定季度;MONTH → 指定月份;默认 → 当月
396 424
      */
397
-    private List<String> buildXAxisLabels(String type, int year, int quarter, int month) {
398
-        List<String> labels = new ArrayList<>();
399
-        switch (type == null ? "MONTH" : type) {
425
+    private LocalDate[] resolveDateRange(SysAnalysisReportParamDto param) {
426
+        LocalDate today = LocalDate.now();
427
+        int year = (param != null && param.getYear() != null) ? param.getYear() : today.getYear();
428
+        String type = (param != null && param.getDateRangeQueryType() != null) ? param.getDateRangeQueryType() : "MONTH";
429
+        com.sundot.airport.common.core.domain.SysAnalysisReportDateRangeDto range;
430
+        switch (type) {
400 431
             case "YEAR":
401
-                labels.add((year - 1) + "年");
402
-                labels.add(year + "年");
432
+                range = DateRangeQueryUtils.getYearRange(year, null);
403 433
                 break;
404 434
             case "QUARTER":
405
-                labels.add((year - 1) + "年第" + quarter + "季度");
406
-                int prevQ = quarter - 1;
407
-                int prevQYear = year;
408
-                if (prevQ < 1) { prevQ = 4; prevQYear = year - 1; }
409
-                labels.add(prevQYear + "年第" + prevQ + "季度");
410
-                labels.add(year + "年第" + quarter + "季度");
435
+                int quarter = (param.getQuarter() != null) ? param.getQuarter() : 1;
436
+                range = DateRangeQueryUtils.getQuarterRange(year, quarter, null);
411 437
                 break;
412
-            case "MONTH":
413
-            default:
414
-                labels.add((year - 1) + "年" + month + "月");
415
-                int prevM = month - 1;
416
-                int prevMYear = year;
417
-                if (prevM < 1) { prevM = 12; prevMYear = year - 1; }
418
-                labels.add(prevMYear + "年" + prevM + "月");
419
-                labels.add(year + "年" + month + "月");
438
+            default: // MONTH
439
+                int month = (param != null && param.getMonth() != null) ? param.getMonth() : today.getMonthValue();
440
+                range = DateRangeQueryUtils.getMonthRange(year, month, null);
420 441
                 break;
421 442
         }
422
-        return labels;
443
+        return new LocalDate[]{
444
+                ((java.sql.Date) range.getStartDate()).toLocalDate(),
445
+                ((java.sql.Date) range.getEndDate()).toLocalDate()
446
+        };
423 447
     }
424 448
 
425 449
     /**
426
-     * 构建多期对比 series 列表(适配美兰4级架构)
427
-     * STATION:各大队各一条 series + 全站汇总
428
-     * BRIGADE:该大队各主管各一条 series
429
-     * MANAGER/USER:单条 series
450
+     * 生成X轴标签列表
451
+     * byMonth=true:每月一个标签(格式 yyyy-MM);false:每天一个标签(格式 yyyy-MM-dd)
430 452
      */
431
-    private List<Map<String, Object>> buildComparisonSeries(
432
-            String scopeType, Long scopeId, List<LocalDate[]> periods, String dateRangeQueryType,
433
-            boolean isStation, boolean isBrigade, boolean isManager, Long userDeptId, LoginUser loginUser) {
434
-
435
-        boolean isYearType = "YEAR".equals(dateRangeQueryType);
436
-
437
-        if ("MANAGER".equals(scopeType) && scopeId != null) {
438
-            String name = "主管" + scopeId;
439
-            List<Integer> data = new ArrayList<>();
440
-            for (LocalDate[] p : periods) {
441
-                List<DailyTask> tasks = queryTasksByDepartmentId(scopeId, p[0], p[1]);
442
-                if (name.startsWith("主管")) {
443
-                    name = tasks.stream().map(DailyTask::getDtDepartmentName)
444
-                            .filter(Objects::nonNull).findFirst().orElse(name);
445
-                }
446
-                data.add(countCompleted(tasks));
447
-            }
448
-            return Collections.singletonList(buildSeriesItem(name, scopeId, null, data, isYearType));
449
-
450
-        } else if ("USER".equals(scopeType) && scopeId != null) {
451
-            String name = "用户" + scopeId;
452
-            List<Integer> data = new ArrayList<>();
453
-            for (LocalDate[] p : periods) {
454
-                List<DailyTask> tasks = queryTasksByUserId(scopeId, p[0], p[1]);
455
-                if (name.startsWith("用户")) {
456
-                    name = tasks.stream().map(DailyTask::getDtUserName)
457
-                            .filter(Objects::nonNull).findFirst().orElse(name);
458
-                }
459
-                data.add(countCompleted(tasks));
453
+    private List<String> buildXAxis(LocalDate start, LocalDate end, boolean byMonth) {
454
+        List<String> xAxis = new ArrayList<>();
455
+        if (byMonth) {
456
+            LocalDate cursor = start.withDayOfMonth(1);
457
+            LocalDate endMonth = end.withDayOfMonth(1);
458
+            while (!cursor.isAfter(endMonth)) {
459
+                xAxis.add(cursor.format(DateTimeFormatter.ofPattern("yyyy-MM")));
460
+                cursor = cursor.plusMonths(1);
460 461
             }
461
-            return Collections.singletonList(buildSeriesItem(name, null, scopeId, data, isYearType));
462
-
463
-        } else if ("BRIGADE".equals(scopeType) && scopeId != null) {
464
-            return buildBrigadeSeries(scopeId, periods, isYearType);
465
-
466 462
         } else {
467
-            Long stationDeptId = ("STATION".equals(scopeType) && scopeId != null) ? scopeId : userDeptId;
468
-            if (isStation || "STATION".equals(scopeType)) {
469
-                return buildStationSeries(stationDeptId, periods, isYearType);
470
-            } else if (isBrigade) {
471
-                return buildBrigadeSeries(userDeptId, periods, isYearType);
472
-            } else {
473
-                // isManager:单条 series
474
-                String deptName = loginUser.getUser().getDept() != null
475
-                        ? loginUser.getUser().getDept().getDeptName() : "本主管";
476
-                List<Integer> data = new ArrayList<>();
477
-                for (LocalDate[] p : periods) {
478
-                    data.add(countCompleted(queryTasksByDepartmentId(userDeptId, p[0], p[1])));
479
-                }
480
-                return Collections.singletonList(buildSeriesItem(deptName, userDeptId, null, data, isYearType));
463
+            LocalDate cursor = start;
464
+            while (!cursor.isAfter(end)) {
465
+                xAxis.add(cursor.toString());
466
+                cursor = cursor.plusDays(1);
481 467
             }
482 468
         }
469
+        return xAxis;
483 470
     }
484 471
 
485 472
     /**
486
-     * 站长视角:按大队分组,各期聚合 + 全站汇总
473
+     * 构建某条折线的数据数组,与 xAxis 对齐
487 474
      */
488
-    private List<Map<String, Object>> buildStationSeries(Long stationDeptId, List<LocalDate[]> periods, boolean isYearType) {
489
-        List<SysDept> subDepts = sysDeptMapper.selectChildrenDeptById(stationDeptId);
490
-        if (subDepts == null) subDepts = Collections.emptyList();
491
-
492
-        List<SysDept> brigadeDepts = subDepts.stream()
493
-                .filter(d -> stationDeptId.equals(d.getParentId()) && "0".equals(d.getDelFlag()))
494
-                .collect(Collectors.toList());
495
-
496
-        // 主管deptId -> 所属大队deptId
497
-        Map<Long, Long> managerToBrigadeId = new HashMap<>();
498
-        for (SysDept d : subDepts) {
499
-            if ("0".equals(d.getDelFlag()) && d.getParentId() != null && !stationDeptId.equals(d.getParentId())) {
500
-                managerToBrigadeId.put(d.getDeptId(), d.getParentId());
501
-            }
502
-        }
503
-
504
-        Set<Long> allDeptIds = new HashSet<>();
505
-        allDeptIds.add(stationDeptId);
506
-        subDepts.forEach(d -> allDeptIds.add(d.getDeptId()));
507
-
508
-        List<List<DailyTask>> periodTasks = new ArrayList<>();
509
-        for (LocalDate[] p : periods) {
510
-            periodTasks.add(queryTasksByDeptIds(allDeptIds, p[0], p[1]));
475
+    private List<Integer> buildSeriesData(List<String> xAxis, List<DailyTask> tasks, boolean byMonth) {
476
+        SimpleDateFormat sdf = new SimpleDateFormat(byMonth ? "yyyy-MM" : "yyyy-MM-dd");
477
+        Map<String, Long> countMap = tasks.stream()
478
+                .filter(t -> "COMPLETED".equals(t.getDtStatus()) && t.getDtBusinessDate() != null)
479
+                .collect(Collectors.groupingBy(
480
+                        t -> sdf.format(t.getDtBusinessDate()),
481
+                        Collectors.counting()));
482
+        List<Integer> data = new ArrayList<>();
483
+        for (String label : xAxis) {
484
+            data.add(countMap.getOrDefault(label, 0L).intValue());
511 485
         }
512
-
513
-        List<Map<String, Object>> series = new ArrayList<>();
514
-        List<Integer> totalData = new ArrayList<>();
515
-        for (int i = 0; i < periods.size(); i++) totalData.add(0);
516
-
517
-        for (SysDept brigade : brigadeDepts) {
518
-            Long brigadeId = brigade.getDeptId();
519
-            List<Integer> data = new ArrayList<>();
520
-            for (int i = 0; i < periodTasks.size(); i++) {
521
-                int count = countCompleted(periodTasks.get(i).stream()
522
-                        .filter(t -> {
523
-                            Long managerId = t.getDtDepartmentId();
524
-                            if (managerId == null) return false;
525
-                            return brigadeId.equals(managerToBrigadeId.get(managerId));
526
-                        })
527
-                        .collect(Collectors.toList()));
528
-                data.add(count);
529
-                totalData.set(i, totalData.get(i) + count);
530
-            }
531
-            series.add(buildSeriesItem(brigade.getDeptName(), brigadeId, null, data, isYearType));
532
-        }
533
-        series.add(buildSeriesItem("全站", null, null, totalData, isYearType));
534
-        return series;
486
+        return data;
535 487
     }
536 488
 
537 489
     /**
538
-     * 大队视角:按主管分组,各期聚合
539
-     */
540
-    private List<Map<String, Object>> buildBrigadeSeries(Long brigadeId, List<LocalDate[]> periods, boolean isYearType) {
541
-        List<SysDept> subDepts = sysDeptMapper.selectChildrenDeptById(brigadeId);
542
-        if (subDepts == null) subDepts = Collections.emptyList();
543
-
544
-        List<SysDept> managerDepts = subDepts.stream()
545
-                .filter(d -> brigadeId.equals(d.getParentId()) && "0".equals(d.getDelFlag()))
546
-                .collect(Collectors.toList());
547
-
548
-        List<Map<String, Object>> series = new ArrayList<>();
549
-        for (SysDept manager : managerDepts) {
550
-            List<Integer> data = new ArrayList<>();
551
-            for (LocalDate[] p : periods) {
552
-                data.add(countCompleted(queryTasksByDepartmentId(manager.getDeptId(), p[0], p[1])));
553
-            }
554
-            series.add(buildSeriesItem(manager.getDeptName(), manager.getDeptId(), null, data, isYearType));
555
-        }
556
-        return series;
557
-    }
558
-
559
-    /**
560
-     * 构建单条 series map,含同比/环比
561
-     * data[0]=去年同期,data[1]=上期(仅QUARTER/MONTH),data[last]=本期
562
-     */
563
-    private Map<String, Object> buildSeriesItem(String name, Long deptId, Long userId,
564
-                                                 List<Integer> data, boolean isYearType) {
565
-        Map<String, Object> s = new LinkedHashMap<>();
566
-        s.put("name", name);
567
-        if (deptId != null) s.put("deptId", deptId);
568
-        if (userId != null) s.put("userId", userId);
569
-        s.put("data", data);
570
-        int current = data.get(data.size() - 1);
571
-        // 同比:本期 vs 去年同期(data[0])
572
-        s.put("yoyRate", calcRate(current, data.get(0)));
573
-        // 环比:本期 vs 上一期(data[last-1]),仅 QUARTER/MONTH 有效
574
-        if (!isYearType && data.size() >= 3) {
575
-            s.put("chainRatio", calcRate(current, data.get(data.size() - 2)));
576
-        } else {
577
-            s.put("chainRatio", null);
578
-        }
579
-        return s;
580
-    }
581
-
582
-    /**
583
-     * 计算变化率百分比,base=0 时返回 null
584
-     */
585
-    private BigDecimal calcRate(int current, int base) {
586
-        if (base == 0) return null;
587
-        return BigDecimal.valueOf(current - base)
588
-                .multiply(BigDecimal.valueOf(100))
589
-                .divide(BigDecimal.valueOf(base), 2, RoundingMode.HALF_UP);
590
-    }
591
-
592
-    /**
593
-     * 统计 COMPLETED 任务数
594
-     */
595
-    private int countCompleted(List<DailyTask> tasks) {
596
-        return (int) tasks.stream().filter(t -> "COMPLETED".equals(t.getDtStatus())).count();
597
-    }
598
-
599
-    /**
600
-     * 根据 dateRangeQueryType/year/quarter/month 计算起止日期
601
-     */
602
-    private LocalDate[] resolveDateRange(String type, Integer year, Integer quarter, Integer month) {
603
-        LocalDate today = LocalDate.now();
604
-        int y = (year != null) ? year : today.getYear();
605
-        LocalDate start, end;
606
-        switch (type == null ? "MONTH" : type) {
607
-            case "YEAR":
608
-                start = LocalDate.of(y, 1, 1);
609
-                end = LocalDate.of(y, 12, 31);
610
-                break;
611
-            case "QUARTER":
612
-                int q = (quarter != null) ? quarter : 1;
613
-                start = LocalDate.of(y, (q - 1) * 3 + 1, 1);
614
-                end = start.plusMonths(3).minusDays(1);
615
-                break;
616
-            case "MONTH":
617
-            default:
618
-                int m = (month != null) ? month : today.getMonthValue();
619
-                start = LocalDate.of(y, m, 1);
620
-                end = start.withDayOfMonth(start.lengthOfMonth());
621
-                break;
622
-        }
623
-        return new LocalDate[]{start, end};
624
-    }
625
-
626
-    /**
627
-     * 按部门ID集合查询任务
490
+     * 按部门ID集合查询任务(用于站长视角:通过dtDeptId匹配所有子部门)
628 491
      */
629 492
     private List<DailyTask> queryTasksByDeptIds(Set<Long> deptIds, LocalDate start, LocalDate end) {
630 493
         LambdaQueryWrapper<DailyTask> wrapper = new LambdaQueryWrapper<>();
@@ -636,7 +499,7 @@ public class DailyExamController {
636 499
     }
637 500
 
638 501
     /**
639
-     * 按主管ID查询任务(dtDepartmentId)
502
+     * 按主管ID查询任务(通过dtDepartmentId匹配
640 503
      */
641 504
     private List<DailyTask> queryTasksByDepartmentId(Long departmentId, LocalDate start, LocalDate end) {
642 505
         LambdaQueryWrapper<DailyTask> wrapper = new LambdaQueryWrapper<>();
@@ -646,16 +509,4 @@ public class DailyExamController {
646 509
         wrapper.eq(DailyTask::getDelStatus, 0);
647 510
         return dailyTaskMapper.selectList(wrapper);
648 511
     }
649
-
650
-    /**
651
-     * 按用户ID查询任务(dtUserId)
652
-     */
653
-    private List<DailyTask> queryTasksByUserId(Long userId, LocalDate start, LocalDate end) {
654
-        LambdaQueryWrapper<DailyTask> wrapper = new LambdaQueryWrapper<>();
655
-        wrapper.eq(DailyTask::getDtUserId, userId);
656
-        wrapper.ge(DailyTask::getDtBusinessDate, Date.valueOf(start));
657
-        wrapper.le(DailyTask::getDtBusinessDate, Date.valueOf(end));
658
-        wrapper.eq(DailyTask::getDelStatus, 0);
659
-        return dailyTaskMapper.selectList(wrapper);
660
-    }
661 512
 }