From 40a8157440e3b906da8f52e07d939d78c3f4c313 Mon Sep 17 00:00:00 2001
From: wlzboy <66905212@qq.com>
Date: 星期日, 12 四月 2026 16:14:06 +0800
Subject: [PATCH] feat: 任务增加统计、同步增加通知
---
ruoyi-system/src/main/java/com/ruoyi/system/domain/vo/DeptOrderStatVO.java | 36 +
ruoyi-ui/src/views/system/user/index.vue | 85 ++
ruoyi-ui/src/api/system/user.js | 9
ruoyi-ui/src/views/system/gps/trackMap.vue | 570 +++++++++++++++++++
ruoyi-system/src/main/java/com/ruoyi/system/service/INotifyTaskService.java | 9
ruoyi-ui/src/api/task.js | 21
ruoyi-ui/src/views/task/stat/index.vue | 219 +++++++
ruoyi-system/src/main/java/com/ruoyi/system/service/impl/LegacyTransferSyncServiceImpl.java | 226 ++++++-
sql/vehicle_gps_track_map_menu.sql | 53 +
ruoyi-system/src/main/java/com/ruoyi/system/service/impl/NotifyDispatchServiceImpl.java | 63 +-
ruoyi-system/src/main/java/com/ruoyi/system/service/impl/NotifyTaskServiceImpl.java | 8
test/1.html | 24
ruoyi-system/src/main/java/com/ruoyi/system/service/ISysTaskService.java | 10
ruoyi-ui/src/api/system/dept.js | 8
ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysUserController.java | 44 +
ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/VehicleGpsController.java | 31 +
ruoyi-ui/src/router/index.js | 23
ruoyi-system/src/main/resources/mapper/system/VehicleGpsMapper.xml | 4
ruoyi-admin/src/main/java/com/ruoyi/web/controller/task/SysTaskStatController.java | 77 ++
ruoyi-system/src/main/java/com/ruoyi/system/listener/TaskMessageListener.java | 63 +
ruoyi-ui/vue.config.js | 2
sql/dept_order_stat_menu.sql | 20
ruoyi-admin/src/main/resources/application.yml | 2
ruoyi-ui/src/api/system/gps.js | 14
ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysTaskServiceImpl.java | 7
ruoyi-system/src/main/resources/mapper/system/SysTaskMapper.xml | 21
ruoyi-admin/src/main/resources/application-prod.yml | 6
ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysTaskMapper.java | 18
ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysDeptController.java | 12
ruoyi-system/src/main/java/com/ruoyi/system/mapper/NotifyTaskMapper.java | 9
ruoyi-system/src/main/resources/mapper/system/NotifyTaskMapper.xml | 5
ruoyi-admin/src/main/resources/application-dev.yml | 6
32 files changed, 1,594 insertions(+), 111 deletions(-)
diff --git a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysDeptController.java b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysDeptController.java
index 2b08014..6e97177 100644
--- a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysDeptController.java
+++ b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysDeptController.java
@@ -74,6 +74,18 @@
}
/**
+ * 鑾峰彇鎵�鏈夊垎鍏徃鍒楄〃锛坧arentId=100鐨勯儴闂級
+ */
+ @GetMapping("/branch/all")
+ public AjaxResult listAllBranches()
+ {
+ SysDept query = new SysDept();
+ query.setParentId(100L);
+ List<SysDept> depts = deptService.selectDeptList(query);
+ return success(depts);
+ }
+
+ /**
* 鏌ヨ閮ㄩ棬鍒楄〃锛堟帓闄よ妭鐐癸級
*/
@GetMapping("/list/exclude/{deptId}")
diff --git a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysUserController.java b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysUserController.java
index 84af926..0ab3cdf 100644
--- a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysUserController.java
+++ b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysUserController.java
@@ -350,4 +350,48 @@
return error("鏈壘鍒板搴旂殑鐢ㄦ埛淇℃伅");
}
}
+
+ /**
+ * 鏇存柊鐢ㄦ埛鍙鐞嗗垎鍏徃锛堥�氳繃oaOrderClass瀛楁淇濆瓨缂栫爜鍒楄〃锛�
+ * 鎺ユ敹閫変腑鐨勫垎鍏徃deptId鍒楄〃锛屾煡璇㈠搴旂紪鐮佸悗鍚堝苟鍐欏叆oaOrderClass
+ */
+ @PreAuthorize("@ss.hasPermi('system:user:edit')")
+ @Log(title = "鐢ㄦ埛绠$悊-鍒嗗叕鍙搁厤缃�", businessType = BusinessType.UPDATE)
+ @PutMapping("/branch/{userId}")
+ public AjaxResult updateUserBranch(@PathVariable Long userId, @RequestBody java.util.List<Long> deptIds)
+ {
+ userService.checkUserDataScope(userId);
+ SysUser user = userService.selectUserById(userId);
+ if (user == null) {
+ return error("鐢ㄦ埛涓嶅瓨鍦�");
+ }
+ // 鏍规嵁deptIds鏌ヨ鍒嗗叕鍙镐俊鎭紝鏀堕泦缂栫爜
+ java.util.Set<String> codeSet = new java.util.LinkedHashSet<>();
+ if (deptIds != null && !deptIds.isEmpty()) {
+ SysDept queryDept = new SysDept();
+ queryDept.setParentId(100L);
+ List<SysDept> allBranches = deptService.selectDeptList(queryDept);
+ java.util.Map<Long, SysDept> deptMap = new java.util.HashMap<>();
+ for (SysDept d : allBranches) {
+ deptMap.put(d.getDeptId(), d);
+ }
+ for (Long deptId : deptIds) {
+ SysDept dept = deptMap.get(deptId);
+ if (dept != null) {
+ if (StringUtils.isNotEmpty(dept.getServiceOrderClass())) {
+ codeSet.add(dept.getServiceOrderClass().trim());
+ }
+ if (StringUtils.isNotEmpty(dept.getDispatchOrderClass())) {
+ codeSet.add(dept.getDispatchOrderClass().trim());
+ }
+ }
+ }
+ }
+ String newOaOrderClass = String.join(",", codeSet);
+ SysUser updateUser = new SysUser();
+ updateUser.setUserId(userId);
+ updateUser.setOaOrderClass(newOaOrderClass);
+ updateUser.setUpdateBy(getUsername());
+ return toAjax(userService.updateUserProfile(updateUser));
+ }
}
diff --git a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/VehicleGpsController.java b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/VehicleGpsController.java
index a5a7621..65652d2 100644
--- a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/VehicleGpsController.java
+++ b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/VehicleGpsController.java
@@ -241,7 +241,7 @@
}
/**
- * 鏌ヨ杞﹁締鍘嗗彶杞ㄨ抗
+ * 鏌ヨ杞﹁締鍘嗗彶杞ㄨ抗锛堣皟鐢℅PS骞冲彴鎺ュ彛锛�
*/
@PreAuthorize("@ss.hasPermi('system:gps:query')")
@GetMapping("/tracks")
@@ -249,6 +249,35 @@
return getAnonymousTracks(vehicleNo, beginTime, endTime);
}
+ /**
+ * 浠庢湰鍦版暟鎹簱鏌ヨ杞﹁締琛岄┒杞ㄨ抗锛堝ぉ鍦板浘杞ㄨ抗椤典娇鐢級
+ * 鏀寔杞︾墝鍙锋ā绯婃煡璇� + 鏃堕棿鑼冨洿绮剧‘鏌ヨ
+ */
+ @PreAuthorize("@ss.hasPermi('system:gps:list')")
+ @GetMapping("/tracksByPlate")
+ public TableDataInfo getTracksByPlate(String vehicleNo, String beginTime, String endTime) {
+ try {
+ if (vehicleNo == null || vehicleNo.trim().isEmpty()) {
+ return getDataTable(new ArrayList<>());
+ }
+ VehicleGps query = new VehicleGps();
+ query.setVehicleNo(vehicleNo.trim());
+ if (beginTime != null && !beginTime.isEmpty()) {
+ query.setBeginTime(beginTime.replace("T", " "));
+ }
+ if (endTime != null && !endTime.isEmpty()) {
+ query.setEndTime(endTime.replace("T", " "));
+ }
+ query.setOrderByColumn("collect_time");
+ query.setIsAsc("asc");
+ List<VehicleGps> list = vehicleGpsService.selectVehicleGpsList(query);
+ return getDataTable(list);
+ } catch (Exception e) {
+ logger.error("浠庢暟鎹簱鏌ヨ杞﹁締杞ㄨ抗澶辫触", e);
+ return getDataTable(new ArrayList<>());
+ }
+ }
+
/**
* 鍖垮悕鏌ヨ杞﹁締鍘嗗彶杞ㄨ抗
diff --git a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/task/SysTaskStatController.java b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/task/SysTaskStatController.java
new file mode 100644
index 0000000..75c46b7
--- /dev/null
+++ b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/task/SysTaskStatController.java
@@ -0,0 +1,77 @@
+package com.ruoyi.web.controller.task;
+
+import com.ruoyi.common.core.controller.BaseController;
+import com.ruoyi.common.core.domain.AjaxResult;
+import com.ruoyi.common.utils.poi.ExcelUtil;
+import com.ruoyi.system.domain.vo.DeptOrderStatVO;
+import com.ruoyi.system.service.ISysTaskService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+
+import javax.servlet.http.HttpServletResponse;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * 鍒嗗叕鍙稿綍鍗曠粺璁� Controller
+ */
+@RestController
+@RequestMapping("/task/stat")
+public class SysTaskStatController extends BaseController {
+
+ @Autowired
+ private ISysTaskService sysTaskService;
+
+ /**
+ * 鎸夊垎鍏徃銆佹椂闂磋寖鍥存煡璇㈡瘡澶╁綍鍗曠粺璁�
+ *
+ * @param deptIds 鍒嗗叕鍙窱D鍒楄〃锛岄�楀彿鍒嗛殧锛屼负绌哄垯鏌ュ叏閮�
+ * @param startDate 寮�濮嬫棩鏈� yyyy-MM-dd
+ * @param endDate 缁撴潫鏃ユ湡 yyyy-MM-dd
+ */
+ @PreAuthorize("@ss.hasPermi('task:stat:query')")
+ @GetMapping("/deptOrder")
+ public AjaxResult deptOrderStat(
+ @RequestParam(required = false) String deptIds,
+ @RequestParam String startDate,
+ @RequestParam String endDate) {
+ List<Long> deptIdList = parseDeptIds(deptIds);
+ List<DeptOrderStatVO> list = sysTaskService.selectDeptOrderStat(deptIdList, startDate, endDate);
+ return AjaxResult.success(list);
+ }
+
+ /**
+ * 瀵煎嚭 Excel
+ */
+ @PreAuthorize("@ss.hasPermi('task:stat:export')")
+ @GetMapping("/deptOrder/export")
+ public void exportDeptOrderStat(
+ HttpServletResponse response,
+ @RequestParam(required = false) String deptIds,
+ @RequestParam String startDate,
+ @RequestParam String endDate) {
+ List<Long> deptIdList = parseDeptIds(deptIds);
+ List<DeptOrderStatVO> list = sysTaskService.selectDeptOrderStat(deptIdList, startDate, endDate);
+ ExcelUtil<DeptOrderStatVO> util = new ExcelUtil<>(DeptOrderStatVO.class);
+ util.exportExcel(response, list, "鍒嗗叕鍙稿綍鍗曠粺璁�");
+ }
+
+ /** 瑙f瀽閫楀彿鍒嗛殧鐨� deptIds 瀛楃涓蹭负 Long 鍒楄〃 */
+ private List<Long> parseDeptIds(String deptIds) {
+ List<Long> result = new ArrayList<>();
+ if (deptIds == null || deptIds.trim().isEmpty()) {
+ return result;
+ }
+ for (String id : deptIds.split(",")) {
+ try {
+ result.add(Long.parseLong(id.trim()));
+ } catch (NumberFormatException ignored) {
+ }
+ }
+ return result;
+ }
+}
diff --git a/ruoyi-admin/src/main/resources/application-dev.yml b/ruoyi-admin/src/main/resources/application-dev.yml
index 13f31b7..1495f3f 100644
--- a/ruoyi-admin/src/main/resources/application-dev.yml
+++ b/ruoyi-admin/src/main/resources/application-dev.yml
@@ -12,12 +12,16 @@
# 浠庡簱鏁版嵁婧�
# SQL Server鏁版嵁婧�
sqlserver:
- url: jdbc:sqlserver://120.25.98.119:1432;databaseName=came
+ url: jdbc:sqlserver://120.25.98.119:1432;databaseName=came;loginTimeout=60;queryTimeout=300
username: camesa
password: camesa
driverClassName: com.microsoft.sqlserver.jdbc.SQLServerDriver
enabled: true
validationQuery: SELECT 1
+ # 杩炴帴瓒呮椂鏃堕棿锛堟绉掞級锛氳幏鍙栬繛鎺ユ渶闀跨瓑寰呮椂闂达紝60绉�
+ connectTimeout: 60000
+ # Socket璇诲彇瓒呮椂锛堟绉掞級锛歋QL鎵ц鏈�闀跨瓑寰呮椂闂达紝5鍒嗛挓
+ socketTimeout: 300000
slave:
# 浠庢暟鎹簮寮�鍏�/榛樿鍏抽棴
enabled: false
diff --git a/ruoyi-admin/src/main/resources/application-prod.yml b/ruoyi-admin/src/main/resources/application-prod.yml
index cf4900c..f9472e0 100644
--- a/ruoyi-admin/src/main/resources/application-prod.yml
+++ b/ruoyi-admin/src/main/resources/application-prod.yml
@@ -12,12 +12,16 @@
# 浠庡簱鏁版嵁婧�
# SQL Server鏁版嵁婧�
sqlserver:
- url: jdbc:sqlserver://39.108.160.52;databaseName=came
+ url: jdbc:sqlserver://39.108.160.52;databaseName=came;loginTimeout=60;queryTimeout=300
username: camesa
password: camesa
driverClassName: com.microsoft.sqlserver.jdbc.SQLServerDriver
enabled: true
validationQuery: SELECT 1
+ # 杩炴帴瓒呮椂鏃堕棿锛堟绉掞級锛氳幏鍙栬繛鎺ユ渶闀跨瓑寰呮椂闂达紝鏀逛负60绉�
+ connectTimeout: 60000
+ # Socket璇诲彇瓒呮椂锛堟绉掞級锛歋QL鎵ц鏈�闀跨瓑寰呮椂闂达紝鏀逛负5鍒嗛挓
+ socketTimeout: 300000
slave:
# 浠庢暟鎹簮寮�鍏�/榛樿鍏抽棴
enabled: false
diff --git a/ruoyi-admin/src/main/resources/application.yml b/ruoyi-admin/src/main/resources/application.yml
index fed985b..ffcd5be 100644
--- a/ruoyi-admin/src/main/resources/application.yml
+++ b/ruoyi-admin/src/main/resources/application.yml
@@ -58,7 +58,7 @@
basename: i18n/messages
profiles:
# 鐜 dev|test|prod
- active: dev
+ active: prod
# 鏂囦欢涓婁紶
servlet:
multipart:
diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/domain/vo/DeptOrderStatVO.java b/ruoyi-system/src/main/java/com/ruoyi/system/domain/vo/DeptOrderStatVO.java
new file mode 100644
index 0000000..86a9f6b
--- /dev/null
+++ b/ruoyi-system/src/main/java/com/ruoyi/system/domain/vo/DeptOrderStatVO.java
@@ -0,0 +1,36 @@
+package com.ruoyi.system.domain.vo;
+
+import com.ruoyi.common.annotation.Excel;
+
+/**
+ * 鍒嗗叕鍙告瘡鏃ュ綍鍗曠粺璁� VO
+ */
+public class DeptOrderStatVO {
+
+ /** 鍒嗗叕鍙窱D */
+ private Long deptId;
+
+ /** 鍒嗗叕鍙稿悕绉� */
+ @Excel(name = "鍒嗗叕鍙�")
+ private String deptName;
+
+ /** 缁熻鏃ユ湡锛屾牸寮� yyyy-MM-dd */
+ @Excel(name = "鏃ユ湡")
+ private String statDate;
+
+ /** 褰曞崟鏁伴噺 */
+ @Excel(name = "褰曞崟鏁伴噺")
+ private Integer orderCount;
+
+ public Long getDeptId() { return deptId; }
+ public void setDeptId(Long deptId) { this.deptId = deptId; }
+
+ public String getDeptName() { return deptName; }
+ public void setDeptName(String deptName) { this.deptName = deptName; }
+
+ public String getStatDate() { return statDate; }
+ public void setStatDate(String statDate) { this.statDate = statDate; }
+
+ public Integer getOrderCount() { return orderCount; }
+ public void setOrderCount(Integer orderCount) { this.orderCount = orderCount; }
+}
diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/listener/TaskMessageListener.java b/ruoyi-system/src/main/java/com/ruoyi/system/listener/TaskMessageListener.java
index 88bfce9..5b20aae 100644
--- a/ruoyi-system/src/main/java/com/ruoyi/system/listener/TaskMessageListener.java
+++ b/ruoyi-system/src/main/java/com/ruoyi/system/listener/TaskMessageListener.java
@@ -1,6 +1,7 @@
package com.ruoyi.system.listener;
import com.ruoyi.common.core.domain.entity.SysDept;
+import com.ruoyi.common.config.WechatConfig;
import com.ruoyi.common.utils.DeptUtil;
import com.ruoyi.common.utils.LongUtil;
import com.ruoyi.system.domain.*;
@@ -56,6 +57,12 @@
@Autowired
private INotifyDispatchService notifyDispatchService;
+
+ @Autowired
+ private IQyWechatService qyWechatService;
+
+ @Autowired
+ private WechatConfig wechatConfig;
/** 寰呭噯澶囩姸鎬� - 鍙互鍙戦�佺煭淇¢�氱煡 */
private static final String TASK_STATUS_PENDING = "PENDING";
@@ -266,6 +273,7 @@
/**
* 鐩戝惉浠诲姟鍒嗛厤浜嬩欢
* 鍒涘缓閫氱煡浠诲姟锛岀敱閫氱煡鍒嗗彂鏈嶅姟鍐冲畾鍙戦�佹笭閬�
+ * 鍚屾椂鐩存帴鍙戦�佷紒涓氬井淇¢�氱煡
*
* @param event 浠诲姟鍒嗛厤浜嬩欢
*/
@@ -295,15 +303,18 @@
Long creatorId = task.getCreatorId();
String taskStatus = task.getTaskStatus();
task.setEmergencyInfo(emergency);
- // 浠呭湪寰呭噯澶囩姸鎬佷笅鍙戦�侀�氱煡
- if (!TASK_STATUS_PENDING.equals(taskStatus) && !TASK_STATUS_PREPARING.equals(taskStatus)) {
- log.info("浠诲姟鐘舵��({})闈炲緟鍑嗗鐘舵�侊紝璺宠繃閫氱煡锛宼askId={}", taskStatus, event.getTaskId());
- return;
- }
-
+
// 鏋勫缓閫氱煡鍐呭
String notifyContent = buildNotifyContent(task, emergency);
- this.sendDispatchNotify(event.getAssigneeIds(), creatorId, event.getTaskId(),task.getShowTaskCode(), notifyContent);
+
+ // 鐩存帴鍙戦�佷紒涓氬井淇¢�氱煡缁欐墽琛屼汉鍛�
+ sendQyWechatNotifyToAssignees(event.getAssigneeIds(), creatorId, event.getTaskId(), notifyContent);
+
+ // 鍚屾椂璧板師鏈夐�氱煡鍒嗗彂娴佺▼锛堢珯鍐呮秷鎭瓑锛�
+ // 浠呭湪寰呭噯澶囩姸鎬佷笅鍙戦�佸叾浠栭�氱煡
+ if (TASK_STATUS_PENDING.equals(taskStatus) || TASK_STATUS_PREPARING.equals(taskStatus)) {
+ this.sendDispatchNotify(event.getAssigneeIds(), creatorId, event.getTaskId(), task.getShowTaskCode(), notifyContent);
+ }
} catch (Exception e) {
@@ -312,6 +323,44 @@
}
/**
+ * 鐩存帴鍙戦�佷紒涓氬井淇¢�氱煡缁欐墽琛屼汉鍛�
+ */
+ private void sendQyWechatNotifyToAssignees(List<Long> assigneeIds, Long creatorId, Long taskId, String content) {
+ String appId = wechatConfig.getAppId();
+ String pathPage = "/pagesTask/detail?id=" + taskId;
+ int successCount = 0;
+
+ for (Long assigneeId : assigneeIds) {
+ // 鎺掗櫎鍒涘缓浜�
+ if (creatorId != null && creatorId.equals(assigneeId)) {
+ log.debug("璺宠繃鍒涘缓浜猴紝涓嶅彂閫佷紒涓氬井淇¢�氱煡锛寀serId={}", assigneeId);
+ continue;
+ }
+
+ try {
+ boolean success = qyWechatService.sendNotifyMessage(
+ assigneeId,
+ "杞繍鍗曚换鍔℃淳鍗曢�氱煡",
+ content,
+ appId,
+ pathPage
+ );
+
+ if (success) {
+ successCount++;
+ log.info("浼佷笟寰俊娲惧崟閫氱煡鍙戦�佹垚鍔燂紝taskId={}, userId={}", taskId, assigneeId);
+ } else {
+ log.warn("浼佷笟寰俊娲惧崟閫氱煡鍙戦�佸け璐ワ紝taskId={}, userId={}", taskId, assigneeId);
+ }
+ } catch (Exception e) {
+ log.error("浼佷笟寰俊娲惧崟閫氱煡鍙戦�佸紓甯革紝taskId={}, userId={}", taskId, assigneeId, e);
+ }
+ }
+
+ log.info("浼佷笟寰俊娲惧崟閫氱煡鍙戦�佸畬鎴愶紝taskId={}, 鎴愬姛鏁伴噺={}/{}", taskId, successCount, assigneeIds.size());
+ }
+
+ /**
* 鍚戞墽琛屼汉鍙戦�佷换鍔″垎閰嶉�氱煡
* @param assigneeIds
* @param creatorId
diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/mapper/NotifyTaskMapper.java b/ruoyi-system/src/main/java/com/ruoyi/system/mapper/NotifyTaskMapper.java
index 96ecce9..6a067f9 100644
--- a/ruoyi-system/src/main/java/com/ruoyi/system/mapper/NotifyTaskMapper.java
+++ b/ruoyi-system/src/main/java/com/ruoyi/system/mapper/NotifyTaskMapper.java
@@ -47,6 +47,15 @@
int countByTaskUserType(@Param("taskId") Long taskId, @Param("userId") Long userId, @Param("notifyType") String notifyType);
/**
+ * 鎸塼askId鍜岄�氱煡绫诲瀷鏌ヨ鎵�鏈夐�氱煡浠诲姟
+ *
+ * @param taskId 涓氬姟浠诲姟ID
+ * @param notifyType 閫氱煡绫诲瀷
+ * @return 閫氱煡浠诲姟鍒楄〃
+ */
+ List<NotifyTask> selectByTaskIdAndType(@Param("taskId") Long taskId, @Param("notifyType") String notifyType);
+
+ /**
* 鏂板閫氱煡浠诲姟
*
* @param notifyTask 閫氱煡浠诲姟
diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysTaskMapper.java b/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysTaskMapper.java
index 77d0170..c3e01e0 100644
--- a/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysTaskMapper.java
+++ b/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysTaskMapper.java
@@ -2,11 +2,13 @@
import java.util.Date;
import java.util.List;
+import java.util.Map;
import com.ruoyi.common.annotation.DataSource;
import com.ruoyi.common.enums.DataSourceType;
import com.ruoyi.system.domain.SysTask;
import com.ruoyi.system.domain.vo.TaskQueryVO;
+import com.ruoyi.system.domain.vo.DeptOrderStatVO;
import com.ruoyi.system.domain.vo.TaskStatisticsVO;
import org.apache.ibatis.annotations.Param;
@@ -153,10 +155,10 @@
/**
* 鏌ヨ杞﹁締鍦ㄦ寚瀹氭椂闂磋寖鍥村唴鐨勪换鍔″垪琛�
*
- * @param params鍖呭惈vehicleId銆乻tartTime銆乪ndTime鐨勫弬鏁癕ap
+ * @param params 鍖呭惈vehicleId銆乻tartTime銆乪ndTime鐨勫弬鏁癕ap
* @return 浠诲姟鍒楄〃
*/
- public List<SysTask> selectVehicleTasksInTimeRange(java.util.Map<String, Object> params);
+ public List<SysTask> selectVehicleTasksInTimeRange(Map<String, Object> params);
/**
* 浼樺寲鐨勫鐮佹煡璇㈡柟娉曪紝鍏宠仈sys_task_emergency琛ㄥ苟璁$畻dispatchCode鍜宻erviceCode
@@ -165,4 +167,16 @@
* @return 浠诲姟绠$悊闆嗗悎
*/
public List<SysTask> selectSysTaskListByMultiCodeOptimized(TaskQueryVO queryVO);
+ /**
+ * 鎸夊垎鍏徃鎸夊ぉ缁熻褰曞崟鏁伴噺
+ *
+ * @param deptIds 鍒嗗叕鍙窱D鍒楄〃锛堜负null鏃舵煡鍏ㄩ儴锛�
+ * @param startDate 寮�濮嬫棩鏈燂紝yyyy-MM-dd
+ * @param endDate 缁撴潫鏃ユ湡锛寉yyy-MM-dd
+ * @return 缁熻缁撴灉鍒楄〃
+ */
+ List<DeptOrderStatVO> selectDeptOrderStat(
+ @Param("deptIds") List<Long> deptIds,
+ @Param("startDate") String startDate,
+ @Param("endDate") String endDate);
}
diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/service/INotifyTaskService.java b/ruoyi-system/src/main/java/com/ruoyi/system/service/INotifyTaskService.java
index eeaa253..01e0d40 100644
--- a/ruoyi-system/src/main/java/com/ruoyi/system/service/INotifyTaskService.java
+++ b/ruoyi-system/src/main/java/com/ruoyi/system/service/INotifyTaskService.java
@@ -46,6 +46,15 @@
boolean existsNotifyTask(Long taskId, Long userId, String notifyType);
/**
+ * 鎸塼askId鍜岄�氱煡绫诲瀷鏌ヨ鎵�鏈夐�氱煡浠诲姟
+ *
+ * @param taskId 涓氬姟浠诲姟ID
+ * @param notifyType 閫氱煡绫诲瀷
+ * @return 閫氱煡浠诲姟鍒楄〃
+ */
+ List<NotifyTask> selectByTaskIdAndType(Long taskId, String notifyType);
+
+ /**
* 鍒涘缓閫氱煡浠诲姟锛堝甫闃查噸锛�
* 濡傛灉宸插瓨鍦ㄥ垯杩斿洖null锛屽惁鍒欏垱寤哄苟杩斿洖
*
diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/service/ISysTaskService.java b/ruoyi-system/src/main/java/com/ruoyi/system/service/ISysTaskService.java
index 3def5e1..21f81b5 100644
--- a/ruoyi-system/src/main/java/com/ruoyi/system/service/ISysTaskService.java
+++ b/ruoyi-system/src/main/java/com/ruoyi/system/service/ISysTaskService.java
@@ -364,4 +364,14 @@
*/
public boolean checkTaskDuplicate(String phone, String createDate);
+ /**
+ * 鎸夊垎鍏徃鎸夊ぉ缁熻褰曞崟鏁伴噺
+ *
+ * @param deptIds 鍒嗗叕鍙窱D鍒楄〃锛堜负null鎴栫┖鏃舵煡鍏ㄩ儴锛�
+ * @param startDate 寮�濮嬫棩鏈燂紝yyyy-MM-dd
+ * @param endDate 缁撴潫鏃ユ湡锛寉yyy-MM-dd
+ * @return 缁熻缁撴灉鍒楄〃
+ */
+ List<DeptOrderStatVO> selectDeptOrderStat(List<Long> deptIds, String startDate, String endDate);
+
}
diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/LegacyTransferSyncServiceImpl.java b/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/LegacyTransferSyncServiceImpl.java
index 997cc28..545b026 100644
--- a/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/LegacyTransferSyncServiceImpl.java
+++ b/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/LegacyTransferSyncServiceImpl.java
@@ -3,6 +3,7 @@
import com.ruoyi.common.core.domain.entity.SysDept;
import com.ruoyi.common.core.domain.entity.SysUser;
import com.ruoyi.common.utils.*;
+import com.ruoyi.system.domain.SysTask;
import com.ruoyi.system.domain.SysTaskEmergency;
import com.ruoyi.system.domain.VehicleInfo;
import com.ruoyi.system.domain.enums.TaskStatus;
@@ -17,6 +18,9 @@
import com.ruoyi.system.mapper.VehicleInfoMapper;
import com.ruoyi.system.service.ISysUserService;
import com.ruoyi.system.service.IWechatTaskNotifyService;
+import com.ruoyi.system.service.INotifyTaskService;
+import com.ruoyi.system.service.INotifyDispatchService;
+import com.ruoyi.system.domain.NotifyTask;
import com.ruoyi.system.utils.TaskStatusConverter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -28,8 +32,10 @@
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Date;
+import java.util.HashSet;
import java.util.List;
import java.util.Map;
+import java.util.Set;
/**
* 鏃х郴缁熻浆杩愬崟鍚屾Service涓氬姟灞傚鐞�
@@ -66,35 +72,71 @@
@Autowired
private IWechatTaskNotifyService wechatTaskNotifyService;
+ @Autowired
+ private INotifyTaskService notifyTaskService;
+
+ @Autowired
+ private INotifyDispatchService notifyDispatchService;
+
/**
* 鍚屾鎸囧畾鏃ユ湡鑼冨洿鐨勬棫绯荤粺杞繍鍗曞埌鏂扮郴缁�
- *
- * @param daysAgo 澶氬皯澶╁墠鐨勬暟鎹紙濡�7琛ㄧず7澶╁墠鐨勬暟鎹級
+ * 浼樺寲锛氬皢澶氬ぉ鑼冨洿鎷嗗垎涓洪�愬ぉ寰幆锛屾瘡娆′粎鏌ヨ1澶╂暟鎹紝閬垮厤澶ф暟鎹噺瀵艰嚧SQL Server瓒呮椂
+ *
+ * @param daysAgo 澶氬皯澶╁墠鐨勬暟鎹紙濡�7琛ㄧず鍚屾鏈�杩�7澶╃殑鏁版嵁锛�
* @return 鎴愬姛鍚屾鐨勮浆杩愬崟鏁伴噺
*/
@Override
public int syncLegacyTransferOrders(int daysAgo) {
-// log.info("寮�濮嬪悓姝}澶╁墠鐨勬棫绯荤粺杞繍鍗曟暟鎹�", daysAgo);
-
try {
// 鍙傛暟楠岃瘉
if (daysAgo <= 0) {
log.error("澶╂暟鍙傛暟蹇呴』澶т簬0");
return 0;
}
-
- // 璁$畻鏃ユ湡鑼冨洿
- Date startDate = DateUtils.addDays(new Date(), -daysAgo);
- String startDateStr = DateUtils.parseDateToStr("yyyy-MM-dd", startDate);
- String endDateStr = DateUtils.parseDateToStr("yyyy-MM-dd", new Date());
-
- // Keyset娓告爣鍒嗛〉浠� SQL Server 鎷夊彇杞繍鍗曟暟鎹紝姣忛〉 10 鏉★紝璧颁富閿储寮曞交搴曡閬胯秴鏃�
- final int PAGE_SIZE = 5;
- long lastId = 0L; // 娓告爣锛氳褰曚笂涓�椤垫渶鍚庝竴鏉$殑 ServiceOrdID锛岄娆′紶 0
- int successCount = 0;
+ log.info("[杞繍鍗曞悓姝 寮�濮嬪悓姝ワ紝鑼冨洿: 鏈�杩憑}澶�", daysAgo);
+ int totalSuccessCount = 0;
+ int totalDays = daysAgo + 1;
+
+ // 鎸夊ぉ鎷嗗垎锛屾瘡娆″彧鍚屾1澶╃殑鏁版嵁锛岄伩鍏嶅ぇ鑼冨洿鏌ヨ瓒呮椂
+ for (int i = daysAgo; i >= 0; i--) {
+ Date dayStart = DateUtils.addDays(new Date(), -i);
+ String dayStartStr = DateUtils.parseDateToStr("yyyy-MM-dd", dayStart) + " 00:00:00";
+ String dayEndStr = DateUtils.parseDateToStr("yyyy-MM-dd", dayStart) + " 23:59:59";
+
+ int dayIndex = totalDays - i;
+ log.info("[杞繍鍗曞悓姝 澶勭悊澶� {}/{}: {}", dayIndex, totalDays, dayStartStr);
+ int daySuccessCount = syncSingleDayOrders(dayStartStr, dayEndStr);
+ totalSuccessCount += daySuccessCount;
+ log.info("[杞繍鍗曞悓姝 {} 瀹屾垚锛屾柊澧炲悓姝�: {}鏉★紝绱: {}鏉�", dayStartStr, daySuccessCount, totalSuccessCount);
+ }
+
+ log.info("[杞繍鍗曞悓姝 鍏ㄩ儴瀹屾垚锛屽叡鏂板鍚屾 {}鏉�", totalSuccessCount);
+ return totalSuccessCount;
+
+ } catch (Exception e) {
+ log.error("鍚屾{}澶╁墠鐨勬棫绯荤粺杞繍鍗曟暟鎹紓甯�", daysAgo, e);
+ return 0;
+ }
+ }
+
+ /**
+ * 鍚屾鍗曞ぉ鐨勮浆杩愬崟鏁版嵁锛圞eyset娓告爣鍒嗛〉锛�
+ *
+ * @param startDateStr 寮�濮嬫棩鏈熷瓧绗︿覆锛坹yyy-MM-dd锛�
+ * @param endDateStr 缁撴潫鏃ユ湡瀛楃涓诧紙yyyy-MM-dd锛�
+ * @return 鎴愬姛鍚屾鐨勮浆杩愬崟鏁伴噺
+ */
+ private int syncSingleDayOrders(String startDateStr, String endDateStr) {
+ final int PAGE_SIZE = 5;
+ long lastId = 0L;
+ int successCount = 0;
+ int pageNum = 0;
+ int totalProcessed = 0;
+
+ try {
while (true) {
List<Map<String, Object>> transferOrders = legacyTransferSyncMapper.selectTransferOrders(startDateStr, endDateStr, lastId, PAGE_SIZE);
@@ -102,36 +144,39 @@
break;
}
+ pageNum++;
int totalCount = transferOrders.size();
int processedCount = 0;
+ log.info("[杞繍鍗曞悓姝 {} 绗瑊}椤碉紝鑾峰彇{}.鏉℃暟鎹紝lastId={}", startDateStr, pageNum, totalCount, lastId);
+
for (Map<String, Object> order : transferOrders) {
processedCount++;
+ totalProcessed++;
try {
Long serviceOrdID = MapValueUtils.getLongValue(order, "ServiceOrdID");
Long dispatchOrdID = MapValueUtils.getLongValue(order, "DispatchOrdID");
- // 妫�鏌ュ弬鏁版湁鏁堟��
if (serviceOrdID == null || serviceOrdID <= 0) {
log.warn("绗瑊}鏉℃暟鎹湇鍔″崟ID涓虹┖锛岃烦杩囧鐞�", processedCount);
continue;
}
-// log.debug("姝e湪澶勭悊绗瑊}/{}鏉¤浆杩愬崟: ServiceOrdID={}, DispatchOrdID={}",
-// processedCount, totalCount, serviceOrdID, dispatchOrdID);
-
// 妫�鏌ユ槸鍚﹀凡鍚屾
if (isTransferOrderSynced(serviceOrdID, dispatchOrdID)) {
-// log.debug("杞繍鍗曞凡鍚屾锛岃烦杩�: ServiceOrdID={}, DispatchOrdID={}", serviceOrdID, dispatchOrdID);
- //杩涜鏇存柊鎿嶄綔
+ log.debug("[杞繍鍗曞悓姝 宸插瓨鍦紝鎵ц鏇存柊: ServiceOrdID={}", serviceOrdID);
updateTransferOrder(serviceOrdID, dispatchOrdID, order);
continue;
}
// 鍚屾鍗曚釜杞繍鍗�
+ log.info("[杞繍鍗曞悓姝 鏂板鍚屾: ServiceOrdID={}, DispatchOrdID={}", serviceOrdID, dispatchOrdID);
boolean success = syncSingleTransferOrder(serviceOrdID, dispatchOrdID, order);
if (success) {
successCount++;
+ log.info("[杞繍鍗曞悓姝 鍚屾鎴愬姛: ServiceOrdID={}, 褰撳ぉ鏂板绱: {}", serviceOrdID, successCount);
+ } else {
+ log.warn("[杞繍鍗曞悓姝 鍚屾澶辫触: ServiceOrdID={}, DispatchOrdID={}", serviceOrdID, dispatchOrdID);
}
// 鎺у埗鍚屾棰戠巼锛岄伩鍏嶈姹傝繃蹇�
@@ -139,7 +184,7 @@
} catch (InterruptedException ie) {
log.warn("鍚屾浠诲姟琚腑鏂�");
Thread.currentThread().interrupt();
- break;
+ return successCount;
} catch (Exception e) {
log.error("鍚屾鍗曚釜杞繍鍗曞け璐�: ServiceOrdID={}, DispatchOrdID={}",
MapValueUtils.getStringValue(order, "ServiceOrdID"),
@@ -162,13 +207,12 @@
}
}
-// log.info("鍚屾瀹屾垚锛屾垚鍔熷悓姝}鏉¤浆杩愬崟鏁版嵁", successCount);
- return successCount;
-
+ log.info("[杞繍鍗曞悓姝 {} 鍒嗛〉瀹屾垚锛屽叡澶勭悊: {}鏉★紝鏂板鍚屾: {}鏉�", startDateStr, totalProcessed, successCount);
} catch (Exception e) {
- log.error("鍚屾{}澶╁墠鐨勬棫绯荤粺杞繍鍗曟暟鎹紓甯�", daysAgo, e);
- return 0;
+ log.error("鍚屾鍗曞ぉ杞繍鍗曟暟鎹紓甯�: date={}", startDateStr, e);
}
+
+ return successCount;
}
/**
@@ -288,7 +332,13 @@
// log.info("杞繍鍗曞悓姝ユ垚鍔�: ServiceOrdID={}, DispatchOrdID={}, 鍒涘缓鐨勪换鍔D={}", serviceOrdID, dispatchOrdID, result);
try {
- notifyTransferOrderByWechat((long) result, serviceOrdID, dispatchOrdID, serviceOrdNo, ServiceOrd_CC_Time, dept, order);
+ // 鐩存帴浣跨敤鏂规硶澶撮儴宸叉煡璇㈢殑 emergency 鑾峰彇 taskId
+ Long taskId = emergency.getTaskId();
+ if (taskId != null) {
+ notifyTransferOrderByWechat(taskId, serviceOrdID, dispatchOrdID, serviceOrdNo, ServiceOrd_CC_Time, dept, order);
+ } else {
+ log.warn("鏇存柊鍚庢壘涓嶅埌taskId锛岃烦杩囬�氱煡: ServiceOrdID={}", serviceOrdID);
+ }
} catch (Exception e) {
log.error("杞繍鍗曞悓姝ユ垚鍔熷悗鍙戦�佸井淇¢�氱煡澶辫触: ServiceOrdID={}, DispatchOrdID={}", serviceOrdID, dispatchOrdID, e);
}
@@ -873,27 +923,123 @@
SysDept dept,
Map<String, Object> order) {
try {
- // 鑾峰彇閫氱煡鎺ユ敹浜哄垪琛�
- List<SysUser> receivers = getWechatNotifyUsers(dispatchOrdID, dept);
- if (receivers == null || receivers.isEmpty()) {
-// log.info("鏃х郴缁熷悓姝ヨ浆杩愬崟鏃犲彲鐢ㄥ井淇℃帴鏀朵汉锛宼askId={}", taskId);
+ // 1. 鑾峰彇鎵ц浜哄垪琛�
+ List<TaskCreateVO.AssigneeInfo> assignees = queryAssignees(dispatchOrdID);
+ if (assignees.isEmpty()) {
+ log.info("鏃х郴缁熷悓姝ヨ浆杩愬崟鏃犳墽琛屼汉锛宼askId={}", taskId);
return;
}
- // 鎻愬彇鎺ユ敹浜� ID 鍒楄〃
- List<Long> userIds = new ArrayList<>();
- for (SysUser user : receivers) {
- if (user != null && user.getUserId() != null) {
- userIds.add(user.getUserId());
+ // 2. 鏌ヨ浠诲姟鑾峰彇showTaskCode
+ SysTask sysTask = sysTaskService.getTaskDetail(taskId);
+ String showTaskCode = sysTask != null ? sysTask.getShowTaskCode() : serviceOrdNo;
+
+ // 3. 鏋勫缓閫氱煡鍐呭
+ String notifyContent = buildLegacyNotifyContent(showTaskCode, serviceOrdCcTime, order);
+
+ // 4. 鏌ヨ璇askId宸叉湁鐨勯�氱煡璁板綍锛屾敹闆嗗凡瀛樺湪鐨剈serId闆嗗悎
+ List<NotifyTask> existingTasks = notifyTaskService.selectByTaskIdAndType(taskId, NotifyTask.NOTIFY_TYPE_TASK_ASSIGN);
+ Set<Long> existingUserIds = new HashSet<>();
+ List<NotifyTask> pendingTasks = new ArrayList<>();
+ if (existingTasks != null && !existingTasks.isEmpty()) {
+ for (NotifyTask t : existingTasks) {
+ existingUserIds.add(t.getUserId());
+ // 灏嗘湭瀹屾垚鐨勮褰曟敹闆嗙粰寰呭垎鍙戝垪琛�
+ if (!NotifyTask.STATUS_COMPLETED.equals(t.getStatus())) {
+ pendingTasks.add(t);
+ }
}
}
- // 璋冪敤缁熶竴鐨勫井淇¢�氱煡鏈嶅姟
- int successCount = wechatTaskNotifyService.sendTaskNotifyMessage(taskId, userIds);
-// log.info("鏃х郴缁熷悓姝ヨ浆杩愬崟寰俊閫氱煡鍙戦�佸畬鎴愶紝taskId={}, 鎴愬姛={}", taskId, successCount);
+ // 5. 鍙鏂版墽琛屼汉鍒涘缓閫氱煡浠诲姟
+ List<NotifyTask> notifyTasks = new ArrayList<>(pendingTasks);
+ for (TaskCreateVO.AssigneeInfo assignee : assignees) {
+ if (assignee == null || assignee.getUserId() == null) {
+ continue;
+ }
+ // 璇ョ敤鎴峰凡鏈夐�氱煡璁板綍锛岃烦杩�
+ if (existingUserIds.contains(assignee.getUserId())) {
+ log.info("鐢ㄦ埛宸叉湁閫氱煡璁板綍锛岃烦杩囧垱寤猴紝taskId={}, userId={}", taskId, assignee.getUserId());
+ continue;
+ }
+
+ SysUser user = sysUserService.selectUserById(assignee.getUserId());
+ if (user == null) {
+ log.warn("鎵句笉鍒版墽琛屼汉鐢ㄦ埛淇℃伅锛寀serId={}", assignee.getUserId());
+ continue;
+ }
+
+ NotifyTask notifyTask = new NotifyTask();
+ notifyTask.setTaskId(taskId);
+ notifyTask.setTaskCode(showTaskCode);
+ notifyTask.setNotifyType(NotifyTask.NOTIFY_TYPE_TASK_ASSIGN);
+ notifyTask.setUserId(user.getUserId());
+ notifyTask.setUserName(user.getNickName());
+ notifyTask.setUserPhone(user.getPhonenumber());
+ notifyTask.setTitle("杞繍鍗曚换鍔℃淳鍗曢�氱煡");
+ notifyTask.setContent(notifyContent);
+ notifyTask.setCreateBy("绯荤粺鍚屾");
+
+ NotifyTask created = notifyTaskService.createNotifyTask(notifyTask);
+ if (created != null) {
+ notifyTasks.add(created);
+ log.info("鍒涘缓閫氱煡浠诲姟鎴愬姛锛宨d={}, userId={}", created.getId(), user.getUserId());
+ }
+ }
+
+ // 6. 鍒嗗彂閫氱煡浠诲姟
+ if (!notifyTasks.isEmpty()) {
+ int successCount = notifyDispatchService.dispatchNotifies(notifyTasks);
+ log.info("鏃х郴缁熷悓姝ヨ浆杩愬崟閫氱煡鍒嗗彂瀹屾垚锛宼askId={}, 鍒嗗彂鏁伴噺={}, 鎴愬姛鏁伴噺={}",
+ taskId, notifyTasks.size(), successCount);
+ } else {
+ log.info("鏃х郴缁熷悓姝ヨ浆杩愬崟鏃犻渶鏂板閫氱煡锛宼askId={}", taskId);
+ }
+
+ // 5. 鍚屾椂淇濈暀鍘熸湁鐨勫井淇¢�氱煡鏈嶅姟锛堝吋瀹癸級
+ // List<Long> userIds = new ArrayList<>();
+ // for (TaskCreateVO.AssigneeInfo assignee : assignees) {
+ // if (assignee != null && assignee.getUserId() != null) {
+ // userIds.add(assignee.getUserId());
+ // }
+ // }
+ // if (!userIds.isEmpty()) {
+ // int wxCount = wechatTaskNotifyService.sendTaskNotifyMessage(taskId, userIds);
+ // log.info("鏃х郴缁熷悓姝ヨ浆杩愬崟寰俊閫氱煡鍙戦�佸畬鎴愶紝taskId={}, 鎴愬姛={}", taskId, wxCount);
+ // }
+
} catch (Exception e) {
log.error("notifyTransferOrderByWechat鍙戠敓寮傚父, serviceOrdID={}, dispatchOrdID={}", serviceOrdID, dispatchOrdID, e);
}
+ }
+
+ /**
+ * 鏋勫缓鏃х郴缁熷悓姝ヨ浆杩愬崟鐨勯�氱煡鍐呭
+ */
+ private String buildLegacyNotifyContent(String serviceOrdNo, Date serviceOrdCcTime, Map<String, Object> order) {
+ StringBuilder content = new StringBuilder();
+ content.append("鎮ㄦ湁鏂扮殑杞繍浠诲姟锛屼换鍔″崟鍙�:").append(serviceOrdNo);
+
+ // 鍑哄彂鏃堕棿
+ if (serviceOrdCcTime != null) {
+ SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm");
+ content.append("锛屽嚭鍙戞椂闂�:").append(df.format(serviceOrdCcTime));
+ }
+
+ // 鍑哄彂鍦�
+ String departure = MapValueUtils.getStringValue(order, "ServiceOrdTraVia");
+ if (StringUtils.isNotEmpty(departure)) {
+ content.append("锛屽嚭鍙戝湴:").append(departure);
+ }
+
+ // 鐩殑鍦�
+ String destination = MapValueUtils.getStringValue(order, "ServiceOrdTraEnd");
+ if (StringUtils.isNotEmpty(destination)) {
+ content.append("锛岀洰鐨勫湴:").append(destination);
+ }
+
+ content.append("锛岃鍙婃椂澶勭悊銆�");
+ return content.toString();
}
private List<SysUser> getWechatNotifyUsers(Long dispatchOrdID, SysDept dept) {
@@ -901,7 +1047,7 @@
List<SysUser> result = new ArrayList<>();
List<TaskCreateVO.AssigneeInfo> assignees = queryAssignees(dispatchOrdID);
- if (assignees != null && !assignees.isEmpty()) {
+ if (!assignees.isEmpty()) {
for (TaskCreateVO.AssigneeInfo assigneeInfo : assignees) {
if (assigneeInfo == null || assigneeInfo.getUserId() == null) {
continue;
diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/NotifyDispatchServiceImpl.java b/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/NotifyDispatchServiceImpl.java
index 61e9051..7eb559c 100644
--- a/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/NotifyDispatchServiceImpl.java
+++ b/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/NotifyDispatchServiceImpl.java
@@ -191,6 +191,7 @@
sendLog.setSendTime(DateUtils.getNowDate());
sendLog.setSendContent(notifyTask.getContent());
sendLog.setResponseMsg(errorMsg);
+ sendLog.setSendResult(success ? "鍙戦�佹垚鍔�" : ("鍙戦�佸け璐�: " + (errorMsg != null ? errorMsg : "鏈煡鍘熷洜")));
notifySendLogService.insertNotifySendLog(sendLog);
} catch (Exception e) {
@@ -341,40 +342,34 @@
*/
@Override
public boolean sendQyWechatMessage(NotifyTask notifyTask) {
- try {
- // 妫�鏌ヤ紒涓氬井淇℃湇鍔℃槸鍚﹀惎鐢�
- if (!qyWechatService.isEnabled()) {
- log.info("浼佷笟寰俊鏈嶅姟宸插叧闂紝璺宠繃鍙戦��");
- return false;
- }
- Long taskId= notifyTask.getTaskId();
- SysTaskEmergency emergency = this.sysEmergencyTaskService.selectSysTaskEmergencyByTaskId(taskId);
- if(emergency==null){
- return false;
- }
-// Long dispatchOrderId = emergency.getLegacyDispatchOrdId();
-// String oldsiteUrl= sysConfigService.selectConfigByKey("oldsite.url");
-// if(oldsiteUrl==null){
-// oldsiteUrl="https://sys.966120.com.cn/m_DispatchOrder.gds?DispatchOrdID=";
-// }
- String appId=wechatConfig.getAppId();
- String pathPage="/pagesTask/detail?id="+taskId;
- // 鍙戦�佷紒涓氬井淇℃秷鎭�
- boolean success = qyWechatService.sendNotifyMessage(
- notifyTask.getUserId(),
- notifyTask.getTitle(),
- notifyTask.getContent(),appId,pathPage
- );
-
- if (success) {
- log.info("浼佷笟寰俊娑堟伅鍙戦�佹垚鍔燂紝userId={}", notifyTask.getUserId());
- } else {
- log.warn("浼佷笟寰俊娑堟伅鍙戦�佸け璐ワ紝userId={}", notifyTask.getUserId());
- }
- return success;
- } catch (Exception e) {
- log.error("浼佷笟寰俊娑堟伅鍙戦�佸紓甯革紝taskId={}, userId={}", notifyTask.getTaskId(), notifyTask.getUserId(), e);
- return false;
+ // 妫�鏌ヤ紒涓氬井淇℃湇鍔℃槸鍚﹀惎鐢�
+ if (!qyWechatService.isEnabled()) {
+ throw new RuntimeException("浼佷笟寰俊鏈嶅姟鏈惎鐢�");
}
+ Long taskId = notifyTask.getTaskId();
+ SysTaskEmergency emergency = this.sysEmergencyTaskService.selectSysTaskEmergencyByTaskId(taskId);
+ if (emergency == null) {
+ throw new RuntimeException("鎵句笉鍒板搴旂殑鎬ユ晳浠诲姟淇℃伅锛宼askId=" + taskId);
+ }
+ // 妫�鏌ョ敤鎴锋槸鍚︾粦瀹氫紒涓氬井淇�
+ Long userId = notifyTask.getUserId();
+ String qyUserId = qyWechatService.getQyUserIdByUserId(userId);
+ if (qyUserId == null || qyUserId.isEmpty()) {
+ throw new RuntimeException("鐢ㄦ埛鏈粦瀹氫紒涓氬井淇D锛寀serId=" + userId);
+ }
+ String appId = wechatConfig.getAppId();
+ String pathPage = "/pagesTask/detail?id=" + taskId;
+ // 鍙戦�佷紒涓氬井淇℃秷鎭�
+ boolean success = qyWechatService.sendNotifyMessage(
+ userId,
+ notifyTask.getTitle(),
+ notifyTask.getContent(), appId, pathPage
+ );
+ if (success) {
+ log.info("浼佷笟寰俊娑堟伅鍙戦�佹垚鍔燂紝userId={}", userId);
+ } else {
+ throw new RuntimeException("浼佷笟寰俊API杩斿洖澶辫触锛寀serId=" + userId);
+ }
+ return true;
}
}
diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/NotifyTaskServiceImpl.java b/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/NotifyTaskServiceImpl.java
index 98e0ad3..e35bc93 100644
--- a/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/NotifyTaskServiceImpl.java
+++ b/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/NotifyTaskServiceImpl.java
@@ -58,6 +58,14 @@
}
/**
+ * 鎸塼askId鍜岄�氱煡绫诲瀷鏌ヨ鎵�鏈夐�氱煡浠诲姟
+ */
+ @Override
+ public List<NotifyTask> selectByTaskIdAndType(Long taskId, String notifyType) {
+ return notifyTaskMapper.selectByTaskIdAndType(taskId, notifyType);
+ }
+
+ /**
* 鍒涘缓閫氱煡浠诲姟锛堝甫闃查噸锛�
*/
@Override
diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysTaskServiceImpl.java b/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysTaskServiceImpl.java
index 0681c0a..f5708fe 100644
--- a/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysTaskServiceImpl.java
+++ b/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysTaskServiceImpl.java
@@ -1851,6 +1851,13 @@
return count > 0;
}
+ @Override
+ public List<DeptOrderStatVO> selectDeptOrderStat(List<Long> deptIds, String startDate, String endDate) {
+ return sysTaskMapper.selectDeptOrderStat(
+ (deptIds != null && !deptIds.isEmpty()) ? deptIds : null,
+ startDate, endDate);
+ }
+
}
diff --git a/ruoyi-system/src/main/resources/mapper/system/NotifyTaskMapper.xml b/ruoyi-system/src/main/resources/mapper/system/NotifyTaskMapper.xml
index be01049..3a11590 100644
--- a/ruoyi-system/src/main/resources/mapper/system/NotifyTaskMapper.xml
+++ b/ruoyi-system/src/main/resources/mapper/system/NotifyTaskMapper.xml
@@ -73,6 +73,11 @@
where task_id = #{taskId} and user_id = #{userId} and notify_type = #{notifyType}
</select>
+ <select id="selectByTaskIdAndType" resultMap="NotifyTaskResult">
+ <include refid="selectNotifyTaskVo"/>
+ where task_id = #{taskId} and notify_type = #{notifyType}
+ </select>
+
<insert id="insertNotifyTask" parameterType="NotifyTask" useGeneratedKeys="true" keyProperty="id">
insert into sys_notify_task (
task_id, task_code, notify_type, user_id, user_name, user_phone,
diff --git a/ruoyi-system/src/main/resources/mapper/system/SysTaskMapper.xml b/ruoyi-system/src/main/resources/mapper/system/SysTaskMapper.xml
index fb24bf8..5c76e9d 100644
--- a/ruoyi-system/src/main/resources/mapper/system/SysTaskMapper.xml
+++ b/ruoyi-system/src/main/resources/mapper/system/SysTaskMapper.xml
@@ -525,4 +525,25 @@
END,
t.create_time desc
</select>
+
+ <!-- 鎸夊垎鍏徃鎸夊ぉ缁熻褰曞崟鏁伴噺 -->
+ <select id="selectDeptOrderStat" resultType="com.ruoyi.system.domain.vo.DeptOrderStatVO">
+ SELECT
+ d.dept_id AS deptId,
+ d.dept_name AS deptName,
+ DATE_FORMAT(t.create_time, '%Y-%m-%d') AS statDate,
+ COUNT(t.task_id) AS orderCount
+ FROM sys_task t
+ LEFT JOIN sys_dept d ON t.dept_id = d.dept_id
+ WHERE t.del_flag = '0'
+ AND DATE(t.create_time) BETWEEN #{startDate} AND #{endDate}
+ <if test="deptIds != null and deptIds.size() > 0">
+ AND t.dept_id IN
+ <foreach collection="deptIds" item="id" open="(" separator="," close=")">
+ #{id}
+ </foreach>
+ </if>
+ GROUP BY d.dept_id, d.dept_name, DATE_FORMAT(t.create_time, '%Y-%m-%d')
+ ORDER BY d.dept_name, statDate
+ </select>
</mapper>
diff --git a/ruoyi-system/src/main/resources/mapper/system/VehicleGpsMapper.xml b/ruoyi-system/src/main/resources/mapper/system/VehicleGpsMapper.xml
index 984d7f1..df86c7f 100644
--- a/ruoyi-system/src/main/resources/mapper/system/VehicleGpsMapper.xml
+++ b/ruoyi-system/src/main/resources/mapper/system/VehicleGpsMapper.xml
@@ -37,10 +37,10 @@
<if test="direction != null "> and direction = #{direction}</if>
<if test="collectTime != null "> and collect_time = #{collectTime}</if>
<if test="beginTime != null and beginTime != ''"><!-- 寮�濮嬫椂闂存绱� -->
- AND date_format(collect_time,'%y%m%d') >= date_format(#{beginTime},'%y%m%d')
+ AND collect_time >= #{beginTime}
</if>
<if test="endTime != null and endTime != ''"><!-- 缁撴潫鏃堕棿妫�绱� -->
- AND date_format(collect_time,'%y%m%d') <= date_format(#{endTime},'%y%m%d')
+ AND collect_time <= #{endTime}
</if>
</where>
order by collect_time desc
diff --git a/ruoyi-ui/src/api/system/dept.js b/ruoyi-ui/src/api/system/dept.js
index 71aef07..2785154 100644
--- a/ruoyi-ui/src/api/system/dept.js
+++ b/ruoyi-ui/src/api/system/dept.js
@@ -74,3 +74,11 @@
method: 'get'
})
}
+
+// 鑾峰彇鎵�鏈夊垎鍏徃鍒楄〃锛坧arentId=100鐨勯儴闂級
+export function listAllBranches() {
+ return request({
+ url: '/system/dept/branch/all',
+ method: 'get'
+ })
+}
diff --git a/ruoyi-ui/src/api/system/gps.js b/ruoyi-ui/src/api/system/gps.js
index 6b13c2d..96e5717 100644
--- a/ruoyi-ui/src/api/system/gps.js
+++ b/ruoyi-ui/src/api/system/gps.js
@@ -88,4 +88,18 @@
endTime
}
})
+}
+
+// 鎸夎溅鐗屽彿鍜屾椂闂磋寖鍥存煡璇㈣建杩癸紙浠庢湰鍦版暟鎹簱tb_vehicle_gps锛屽ぉ鍦板浘杞ㄨ抗椤典娇鐢級
+export function getTracksByPlate(vehicleNo, beginTime, endTime) {
+ return request({
+ url: '/system/gps/tracksByPlate',
+ method: 'get',
+ params: {
+ vehicleNo,
+ beginTime,
+ endTime
+ },
+ timeout: 180000
+ })
}
\ No newline at end of file
diff --git a/ruoyi-ui/src/api/system/user.js b/ruoyi-ui/src/api/system/user.js
index b5e3edd..9d727db 100644
--- a/ruoyi-ui/src/api/system/user.js
+++ b/ruoyi-ui/src/api/system/user.js
@@ -134,3 +134,12 @@
method: 'get'
})
}
+
+// 鏇存柊鐢ㄦ埛鍙鐞嗗垎鍏徃锛堜紶鍏eptId鏁扮粍锛�
+export function updateUserBranch(userId, deptIds) {
+ return request({
+ url: '/system/user/branch/' + userId,
+ method: 'put',
+ data: deptIds
+ })
+}
diff --git a/ruoyi-ui/src/api/task.js b/ruoyi-ui/src/api/task.js
index 77b459e..b9d6e10 100644
--- a/ruoyi-ui/src/api/task.js
+++ b/ruoyi-ui/src/api/task.js
@@ -339,3 +339,24 @@
method: 'get'
})
}
+
+// ========== 鍒嗗叕鍙稿綍鍗曠粺璁$浉鍏矨PI ==========
+
+// 鎸夊垎鍏徃鎸夊ぉ缁熻褰曞崟鏁伴噺
+export function getDeptOrderStat(params) {
+ return request({
+ url: '/task/stat/deptOrder',
+ method: 'get',
+ params
+ })
+}
+
+// 瀵煎嚭鍒嗗叕鍙稿綍鍗曠粺璁� Excel
+export function exportDeptOrderStat(params) {
+ return request({
+ url: '/task/stat/deptOrder/export',
+ method: 'get',
+ params,
+ responseType: 'blob'
+ })
+}
diff --git a/ruoyi-ui/src/router/index.js b/ruoyi-ui/src/router/index.js
index 341c2b2..f89f19b 100644
--- a/ruoyi-ui/src/router/index.js
+++ b/ruoyi-ui/src/router/index.js
@@ -131,12 +131,18 @@
component: (resolve) => require(['@/views/system/payInfoTest/index'], resolve),
hidden: true,
meta: { title: '鏀粯淇℃伅娴嬭瘯', anonymous: true }
- }
- ,{
+ },
+ {
path: '/system/gps/mapNeed',
component: () => import('@/views/system/gps/mapNeed'),
name: 'GpsMapNeed',
meta: { title: '杞﹁締杞ㄨ抗', icon: 'map' }
+ },
+ {
+ path: '/system/gps/trackMap',
+ component: () => import('@/views/system/gps/trackMap'),
+ name: 'GpsTrackMap',
+ meta: { title: '杞﹁締琛岄┒杞ㄨ抗', icon: 'map' }
},
{
@@ -329,6 +335,19 @@
meta: { title: '浠诲姟璇︽儏', activeMenu: '/task/general' }
}
]
+ },
+ {
+ path: '/task/stat',
+ component: Layout,
+ hidden: false,
+ children: [
+ {
+ path: 'index',
+ component: () => import('@/views/task/stat/index'),
+ name: 'DeptOrderStat',
+ meta: { title: '鍒嗗叕鍙稿綍鍗曠粺璁�', icon: 'chart', noCache: false }
+ }
+ ]
}
]
diff --git a/ruoyi-ui/src/views/system/gps/trackMap.vue b/ruoyi-ui/src/views/system/gps/trackMap.vue
new file mode 100644
index 0000000..80a591c
--- /dev/null
+++ b/ruoyi-ui/src/views/system/gps/trackMap.vue
@@ -0,0 +1,570 @@
+<template>
+ <div class="app-container">
+ <!-- 鏌ヨ鏉′欢 -->
+ <el-form :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch" label-width="80px">
+ <el-form-item label="杞︾墝鍙�" prop="vehicleNo">
+ <el-autocomplete
+ v-model="queryParams.vehicleNo"
+ :fetch-suggestions="queryVehicleSearch"
+ placeholder="杈撳叆杞︾墝鍙锋悳绱�"
+ size="small"
+ style="width: 200px"
+ clearable
+ :trigger-on-focus="true"
+ value-key="vehicleNo"
+ @select="handleVehicleSelect"
+ @keyup.enter.native="handleQuery"
+ >
+ <template slot-scope="{ item }">
+ <span>{{ item.vehicleNo }}</span>
+ <span style="float:right;color:#909399;font-size:12px;margin-left:12px">{{ item.vehicleType || '' }}</span>
+ </template>
+ </el-autocomplete>
+ </el-form-item>
+ <el-form-item label="鏃堕棿鑼冨洿">
+ <el-date-picker
+ v-model="dateRange"
+ size="small"
+ style="width: 340px"
+ value-format="yyyy-MM-dd HH:mm:ss"
+ type="datetimerange"
+ range-separator="-"
+ start-placeholder="寮�濮嬫椂闂�"
+ end-placeholder="缁撴潫鏃堕棿"
+ :default-time="['00:00:00', '23:59:59']"
+ />
+ </el-form-item>
+ <el-form-item>
+ <el-button type="primary" icon="el-icon-search" size="mini" :loading="loading" @click="handleQuery">鏌ヨ</el-button>
+ <el-button icon="el-icon-refresh" size="mini" @click="resetQuery">閲嶇疆</el-button>
+ </el-form-item>
+ </el-form>
+
+ <!-- 缁熻淇℃伅鏉� -->
+ <div class="stats-bar" v-if="gpsList.length > 0">
+ <el-tag type="info" size="small">鍏� {{ gpsList.length }} 涓建杩圭偣</el-tag>
+ <el-tag type="success" size="small" style="margin-left:8px">璧风偣鏃堕棿锛歿{ gpsList[0] && gpsList[0].collectTime }}</el-tag>
+ <el-tag type="warning" size="small" style="margin-left:8px">缁堢偣鏃堕棿锛歿{ gpsList[gpsList.length-1] && gpsList[gpsList.length-1].collectTime }}</el-tag>
+ <el-button-group style="margin-left:16px">
+ <el-button size="mini" @click="showPreviousSegment" :disabled="segmentIndex === 0">
+ <i class="el-icon-arrow-left"></i> 涓婁竴娈�
+ </el-button>
+ <el-button size="mini" @click="showNextSegment" :disabled="(segmentIndex + 1) * segmentSize >= gpsList.length">
+ 涓嬩竴娈� <i class="el-icon-arrow-right"></i>
+ </el-button>
+ <el-button size="mini" style="margin-left:8px">
+ 绗� {{ segmentIndex + 1 }} / {{ Math.ceil(gpsList.length / segmentSize) }} 娈�
+ </el-button>
+ </el-button-group>
+ <el-button-group style="margin-left:8px">
+ <el-button size="mini" type="primary" icon="el-icon-video-play" :disabled="isPlaying" @click="startPlayback">鍥炴斁</el-button>
+ <el-button size="mini" type="danger" icon="el-icon-video-pause" :disabled="!isPlaying" @click="stopPlayback">鍋滄</el-button>
+ <el-select v-model="playSpeed" size="mini" style="width:90px;margin-left:4px">
+ <el-option label="鎱㈤��" :value="1500" />
+ <el-option label="姝e父" :value="800" />
+ <el-option label="蹇��" :value="300" />
+ <el-option label="鏋侀��" :value="80" />
+ </el-select>
+ </el-button-group>
+ </div>
+
+ <el-row :gutter="10" style="margin-top:10px">
+ <!-- 宸︿晶杞ㄨ抗鍒楄〃 -->
+ <el-col :span="6">
+ <el-card class="track-list-card">
+ <div slot="header">
+ <span>杞ㄨ抗鐐瑰垪琛�</span>
+ <el-tag size="mini" style="float:right" v-if="gpsList.length">{{ currentSegmentStart }}-{{ currentSegmentEnd }} / {{ gpsList.length }}</el-tag>
+ </div>
+ <el-table
+ v-loading="loading"
+ :data="currentSegmentList"
+ height="560"
+ size="mini"
+ highlight-current-row
+ @row-click="handleRowClick"
+ >
+ <el-table-column label="鏃堕棿" prop="collectTime" min-width="100">
+ <template slot-scope="scope">
+ <span style="font-size:11px">{{ scope.row.collectTime }}</span>
+ </template>
+ </el-table-column>
+ <el-table-column label="閫熷害" prop="speed" width="65" align="center">
+ <template slot-scope="scope">
+ <el-tag size="mini" :type="getSpeedTagType(scope.row.speed)">{{ formatSpeed(scope.row.speed) }}</el-tag>
+ </template>
+ </el-table-column>
+ <el-table-column label="缁忓害" prop="longitude" width="80" align="center">
+ <template slot-scope="scope">
+ <span style="font-size:11px">{{ scope.row.longitude }}</span>
+ </template>
+ </el-table-column>
+ </el-table>
+ </el-card>
+ </el-col>
+
+ <!-- 鍙充晶澶╁湴鍥� -->
+ <el-col :span="18">
+ <div style="position:relative">
+ <div id="tianMapContainer" style="height:620px;border-radius:4px;overflow:hidden"></div>
+ <!-- 鍦板浘鍔犺浇涓彁绀� -->
+ <div v-if="mapLoading" class="map-loading-mask">
+ <i class="el-icon-loading" style="font-size:32px;color:#409EFF"></i>
+ <p style="margin-top:8px;color:#409EFF">鍦板浘鍔犺浇涓�...</p>
+ </div>
+ <!-- 鏃犳暟鎹彁绀� -->
+ <div v-if="!mapLoading && gpsList.length === 0 && !loading" class="map-empty-tip">
+ <i class="el-icon-map-location" style="font-size:48px;color:#909399"></i>
+ <p style="margin-top:8px;color:#909399">璇疯緭鍏ヨ溅鐗屽彿鍜屾椂闂磋寖鍥存煡璇㈣建杩�</p>
+ </div>
+ </div>
+ </el-col>
+ </el-row>
+ </div>
+</template>
+
+<script>
+import { getTracksByPlate } from '@/api/system/gps'
+import { listVehicle } from '@/api/system/vehicle'
+
+// 澶╁湴鍥� API Key锛堜笌鍚庣閰嶇疆淇濇寔涓�鑷达級
+const TIAN_DI_TU_TK = '079300b89bce333ead2df1476c43ecb0'
+
+export default {
+ name: 'GpsTrackMap',
+ data() {
+ return {
+ loading: false,
+ mapLoading: true,
+ showSearch: true,
+ gpsList: [],
+ dateRange: [],
+ // 杞︾墝鍙蜂笅鎷夋悳绱�
+ vehicleOptions: [],
+ vehicleSearchLoading: false,
+ queryParams: {
+ vehicleNo: '',
+ beginTime: null,
+ endTime: null
+ },
+ // 鍦板浘瀵硅薄
+ map: null,
+ // 杞ㄨ抗瑕嗙洊鐗�
+ trackPolyline: null,
+ markerStart: null,
+ markerEnd: null,
+ markerCurrent: null,
+ infoWindow: null,
+ // 鍒嗘
+ segmentIndex: 0,
+ segmentSize: 200,
+ // 鍥炴斁
+ isPlaying: false,
+ playTimer: null,
+ playIndex: 0,
+ playSpeed: 800
+ }
+ },
+ computed: {
+ currentSegmentStart() {
+ return this.segmentIndex * this.segmentSize + 1
+ },
+ currentSegmentEnd() {
+ return Math.min((this.segmentIndex + 1) * this.segmentSize, this.gpsList.length)
+ },
+ currentSegmentList() {
+ return this.gpsList.slice(this.currentSegmentStart - 1, this.currentSegmentEnd)
+ }
+ },
+ mounted() {
+ // 鍒濆鍖栭粯璁ゆ椂闂翠负浠婂ぉ
+ this.initDefaultDateRange()
+ // 鍔犺浇澶╁湴鍥�
+ this.loadTianDiTuScript().then(() => {
+ this.initMap()
+ })
+ },
+ methods: {
+ /** 杞︾墝鍙疯嚜鍔ㄥ畬鎴愭悳绱� */
+ queryVehicleSearch(queryStr, callback) {
+ const keyword = queryStr || ''
+ listVehicle({ vehicleNo: keyword, pageSize: 30, pageNum: 1 }).then(res => {
+ const list = (res.rows || []).map(item => ({
+ vehicleId: item.vehicleId,
+ vehicleNo: item.vehicleNo,
+ vehicleType: item.vehicleType,
+ value: item.vehicleNo
+ }))
+ callback(list)
+ }).catch(() => {
+ callback([])
+ })
+ },
+ /** 閫変腑杞︾墝鍙� */
+ handleVehicleSelect(item) {
+ this.queryParams.vehicleNo = item.vehicleNo
+ },
+ /** 杩滅▼鎼滅储杞︾墝鍙� */
+ remoteSearchVehicle(query) {
+ if (query === '' || query === null) {
+ this.vehicleOptions = []
+ return
+ }
+ this.vehicleSearchLoading = true
+ listVehicle({ vehicleNo: query, pageSize: 20, pageNum: 1 }).then(res => {
+ this.vehicleOptions = res.rows || []
+ this.vehicleSearchLoading = false
+ }).catch(() => {
+ this.vehicleSearchLoading = false
+ })
+ },
+ /** 鍒濆鍖栭粯璁ゆ椂闂磋寖鍥达紙浠婂ぉ锛� */
+ initDefaultDateRange() {
+ const now = new Date()
+ const start = new Date(now.getFullYear(), now.getMonth(), now.getDate(), 0, 0, 0)
+ const end = new Date(now.getFullYear(), now.getMonth(), now.getDate(), 23, 59, 59)
+ this.dateRange = [this.formatDatetime(start), this.formatDatetime(end)]
+ },
+ /** 鏍煎紡鍖栨棩鏈熶负瀛楃涓� */
+ formatDatetime(date) {
+ const y = date.getFullYear()
+ const m = String(date.getMonth() + 1).padStart(2, '0')
+ const d = String(date.getDate()).padStart(2, '0')
+ const h = String(date.getHours()).padStart(2, '0')
+ const min = String(date.getMinutes()).padStart(2, '0')
+ const s = String(date.getSeconds()).padStart(2, '0')
+ return `${y}-${m}-${d} ${h}:${min}:${s}`
+ },
+ /** 鏍煎紡鍖栭�熷害鏄剧ず */
+ formatSpeed(speed) {
+ if (speed == null) return '0'
+ return parseFloat(speed).toFixed(1)
+ },
+ /** 閫熷害鏍囩绫诲瀷 */
+ getSpeedTagType(speed) {
+ const v = parseFloat(speed) || 0
+ if (v === 0) return 'info'
+ if (v < 60) return 'success'
+ if (v < 100) return 'warning'
+ return 'danger'
+ },
+ /** 鍔犺浇澶╁湴鍥� JS API */
+ loadTianDiTuScript() {
+ return new Promise((resolve, reject) => {
+ if (window.T) {
+ resolve()
+ return
+ }
+ const script = document.createElement('script')
+ script.type = 'text/javascript'
+ script.src = `http://api.tianditu.gov.cn/api?v=4.0&tk=${TIAN_DI_TU_TK}`
+ script.onload = () => {
+ // 绛夊緟 T 瀵硅薄鍙敤
+ const checkT = setInterval(() => {
+ if (window.T) {
+ clearInterval(checkT)
+ resolve()
+ }
+ }, 100)
+ }
+ script.onerror = (err) => {
+ console.error('澶╁湴鍥捐剼鏈姞杞藉け璐�', err)
+ reject(err)
+ }
+ document.head.appendChild(script)
+ })
+ },
+ /** 鍒濆鍖栧ぉ鍦板浘 */
+ initMap() {
+ try {
+ this.map = new T.Map('tianMapContainer')
+ // 榛樿灞呬腑骞垮窞
+ this.map.centerAndZoom(new T.LngLat(113.33, 23.12), 11)
+ // 娣诲姞鎺т欢
+ this.map.addControl(new T.Control.Zoom())
+ this.map.addControl(new T.Control.Scale())
+ this.mapLoading = false
+ } catch (e) {
+ console.error('澶╁湴鍥惧垵濮嬪寲澶辫触', e)
+ this.$message.error('鍦板浘鍒濆鍖栧け璐ワ紝璇峰埛鏂伴噸璇�')
+ this.mapLoading = false
+ }
+ },
+ /** 鏌ヨ鎸夐挳 */
+ handleQuery() {
+ if (!this.queryParams.vehicleNo || !this.queryParams.vehicleNo.trim()) {
+ this.$message.warning('璇疯緭鍏ヨ溅鐗屽彿')
+ return
+ }
+ if (!this.dateRange || this.dateRange.length !== 2) {
+ this.$message.warning('璇烽�夋嫨鏃堕棿鑼冨洿')
+ return
+ }
+ this.queryParams.beginTime = this.dateRange[0]
+ this.queryParams.endTime = this.dateRange[1]
+ this.segmentIndex = 0
+ this.stopPlayback()
+ this.getTrackData()
+ },
+ /** 閲嶇疆 */
+ resetQuery() {
+ this.$refs.queryForm.resetFields()
+ this.initDefaultDateRange()
+ this.gpsList = []
+ this.segmentIndex = 0
+ this.stopPlayback()
+ this.clearMap()
+ },
+ /** 鑾峰彇杞ㄨ抗鏁版嵁 */
+ getTrackData() {
+ this.loading = true
+ getTracksByPlate(
+ this.queryParams.vehicleNo.trim(),
+ this.queryParams.beginTime,
+ this.queryParams.endTime
+ ).then(res => {
+ this.gpsList = (res.rows || []).sort((a, b) => {
+ return new Date(a.collectTime) - new Date(b.collectTime)
+ })
+ this.loading = false
+ if (this.gpsList.length === 0) {
+ this.$message.info('璇ユ椂闂存鍐呮湭鏌ヨ鍒拌建杩规暟鎹�')
+ this.clearMap()
+ } else {
+ this.$message.success(`鍏辨煡璇㈠埌 ${this.gpsList.length} 涓建杩圭偣`)
+ this.drawTrack()
+ }
+ }).catch(err => {
+ this.loading = false
+ this.$message.error('鏌ヨ杞ㄨ抗澶辫触锛�' + (err.message || '鏈煡閿欒'))
+ })
+ },
+ /** 娓呯┖鍦板浘瑕嗙洊鐗� */
+ clearMap() {
+ if (!this.map) return
+ this.map.clearOverLays()
+ this.trackPolyline = null
+ this.markerStart = null
+ this.markerEnd = null
+ this.markerCurrent = null
+ },
+ /** 缁樺埗杞ㄨ抗 */
+ drawTrack() {
+ if (!this.map) return
+ this.clearMap()
+
+ const segment = this.currentSegmentList
+ if (!segment || segment.length === 0) return
+
+ // 鏋勫缓杞ㄨ抗鐐规暟缁�
+ const points = segment
+ .filter(p => p.longitude != null && p.latitude != null)
+ .map(p => new T.LngLat(p.longitude, p.latitude))
+
+ if (points.length < 2) {
+ this.$message.warning('鏈夋晥杞ㄨ抗鐐逛笉瓒筹紝鏃犳硶缁樺埗')
+ return
+ }
+
+ // 缁樺埗杞ㄨ抗绾�
+ this.trackPolyline = new T.Polyline(points, {
+ color: '#3388ff',
+ weight: 4,
+ opacity: 0.85
+ })
+ this.map.addOverLay(this.trackPolyline)
+
+ // 璧风偣鏍囪
+ const startPoint = points[0]
+ const startMarker = this.createStartMarker(startPoint, segment[0])
+ this.map.addOverLay(startMarker)
+ this.markerStart = startMarker
+
+ // 缁堢偣鏍囪锛堣溅杈嗗浘鏍囷級
+ const endIdx = segment.length - 1
+ const endPoint = points[points.length - 1]
+ const endMarker = this.createVehicleMarker(endPoint, segment[endIdx])
+ this.map.addOverLay(endMarker)
+ this.markerEnd = endMarker
+
+ // 鑷�傚簲瑙嗛噹
+ this.map.setViewport(points)
+ },
+ /** 鍒涘缓璧风偣鏍囪 */
+ createStartMarker(lngLat, data) {
+ const icon = new T.Icon({
+ iconUrl: this.createSvgIconUrl('#2ecc71', '璧�'),
+ iconSize: new T.Point(28, 28),
+ iconAnchor: new T.Point(14, 14)
+ })
+ const marker = new T.Marker(lngLat, { icon })
+ marker.addEventListener('click', () => {
+ this.showInfoWindow(lngLat, data, '璧风偣')
+ })
+ return marker
+ },
+ /** 鍒涘缓杞﹁締鏍囪 */
+ createVehicleMarker(lngLat, data) {
+ const direction = data.direction || 0
+ const icon = new T.Icon({
+ iconUrl: this.createCarIconUrl(direction),
+ iconSize: new T.Point(32, 32),
+ iconAnchor: new T.Point(16, 16)
+ })
+ const marker = new T.Marker(lngLat, { icon })
+ marker.addEventListener('click', () => {
+ this.showInfoWindow(lngLat, data, '褰撳墠浣嶇疆')
+ })
+ return marker
+ },
+ /** 鐢熸垚璧风偣/缁堢偣 SVG 鍥炬爣 URL */
+ createSvgIconUrl(color, text) {
+ const svg = `<svg xmlns="http://www.w3.org/2000/svg" width="28" height="28" viewBox="0 0 28 28">
+ <circle cx="14" cy="14" r="13" fill="${color}" stroke="white" stroke-width="2"/>
+ <text x="14" y="18" text-anchor="middle" fill="white" font-size="12" font-weight="bold">${text}</text>
+ </svg>`
+ return 'data:image/svg+xml;charset=utf-8,' + encodeURIComponent(svg)
+ },
+ /** 鐢熸垚姹借溅鍥炬爣 SVG URL锛堝甫鏂瑰悜鏃嬭浆锛� */
+ createCarIconUrl(direction) {
+ const deg = direction || 0
+ const svg = `<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32">
+ <g transform="rotate(${deg}, 16, 16)">
+ <ellipse cx="16" cy="16" rx="10" ry="13" fill="#3388ff" stroke="white" stroke-width="2"/>
+ <polygon points="16,3 21,12 11,12" fill="white"/>
+ <rect x="11" y="22" width="4" height="3" rx="1" fill="white" opacity="0.8"/>
+ <rect x="17" y="22" width="4" height="3" rx="1" fill="white" opacity="0.8"/>
+ </g>
+ </svg>`
+ return 'data:image/svg+xml;charset=utf-8,' + encodeURIComponent(svg)
+ },
+ /** 鏄剧ず淇℃伅绐楀彛 */
+ showInfoWindow(lngLat, data, label) {
+ if (!this.map) return
+ if (this.infoWindow) {
+ this.map.closeInfoWindow()
+ }
+ const content = `
+ <div style="padding:8px;min-width:180px;line-height:1.8">
+ <b style="color:#3388ff">${label || ''}</b><br/>
+ <span>杞︾墝锛�${data.vehicleNo || '-'}</span><br/>
+ <span>鏃堕棿锛�${data.collectTime || '-'}</span><br/>
+ <span>閫熷害锛�${this.formatSpeed(data.speed)} km/h</span><br/>
+ <span>鏂瑰悜锛�${data.direction || 0}掳</span><br/>
+ <span>缁忓害锛�${data.longitude}</span><br/>
+ <span>绾害锛�${data.latitude}</span>
+ </div>
+ `
+ this.infoWindow = new T.InfoWindow(content, {
+ offset: new T.Point(0, -15)
+ })
+ this.map.openInfoWindow(this.infoWindow, lngLat)
+ },
+ /** 鐐瑰嚮鍒楄〃琛� */
+ handleRowClick(row) {
+ if (!row || row.longitude == null) return
+ const lngLat = new T.LngLat(row.longitude, row.latitude)
+ this.map.panTo(lngLat)
+ this.map.setZoom(15)
+ this.showInfoWindow(lngLat, row, '閫変腑鐐�')
+ },
+ /** 涓婁竴娈� */
+ showPreviousSegment() {
+ if (this.segmentIndex > 0) {
+ this.segmentIndex--
+ this.stopPlayback()
+ this.drawTrack()
+ }
+ },
+ /** 涓嬩竴娈� */
+ showNextSegment() {
+ if ((this.segmentIndex + 1) * this.segmentSize < this.gpsList.length) {
+ this.segmentIndex++
+ this.stopPlayback()
+ this.drawTrack()
+ }
+ },
+ /** 寮�濮嬪洖鏀� */
+ startPlayback() {
+ if (this.isPlaying || !this.gpsList.length) return
+ this.isPlaying = true
+ this.playIndex = this.currentSegmentStart - 1
+ this.playNextPoint()
+ },
+ /** 鍥炴斁涓嬩竴涓偣 */
+ playNextPoint() {
+ if (!this.isPlaying) return
+ const allList = this.gpsList
+ if (this.playIndex >= this.currentSegmentEnd) {
+ this.stopPlayback()
+ this.$message.success('杞ㄨ抗鍥炴斁瀹屾垚')
+ return
+ }
+ const item = allList[this.playIndex]
+ if (item && item.longitude != null) {
+ const lngLat = new T.LngLat(item.longitude, item.latitude)
+ // 绉婚櫎鏃х殑鍥炴斁鏍囪
+ if (this.markerCurrent) {
+ this.map.removeOverLay(this.markerCurrent)
+ }
+ const marker = this.createVehicleMarker(lngLat, item)
+ this.map.addOverLay(marker)
+ this.markerCurrent = marker
+ this.map.panTo(lngLat)
+ }
+ this.playIndex++
+ this.playTimer = setTimeout(() => this.playNextPoint(), this.playSpeed)
+ },
+ /** 鍋滄鍥炴斁 */
+ stopPlayback() {
+ if (this.playTimer) {
+ clearTimeout(this.playTimer)
+ this.playTimer = null
+ }
+ this.isPlaying = false
+ }
+ },
+ beforeDestroy() {
+ this.stopPlayback()
+ }
+}
+</script>
+
+<style scoped>
+.stats-bar {
+ display: flex;
+ align-items: center;
+ flex-wrap: wrap;
+ gap: 4px;
+ padding: 8px 12px;
+ background: #f5f7fa;
+ border-radius: 4px;
+ margin-bottom: 4px;
+}
+.track-list-card {
+ height: 660px;
+}
+.track-list-card >>> .el-card__body {
+ padding: 0;
+ overflow: hidden;
+}
+.map-loading-mask {
+ position: absolute;
+ top: 0; left: 0; right: 0; bottom: 0;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ background: rgba(255,255,255,0.85);
+ z-index: 999;
+}
+.map-empty-tip {
+ position: absolute;
+ top: 50%;
+ left: 50%;
+ transform: translate(-50%, -50%);
+ text-align: center;
+ pointer-events: none;
+ z-index: 10;
+}
+</style>
diff --git a/ruoyi-ui/src/views/system/user/index.vue b/ruoyi-ui/src/views/system/user/index.vue
index d10e40c..2f33959 100644
--- a/ruoyi-ui/src/views/system/user/index.vue
+++ b/ruoyi-ui/src/views/system/user/index.vue
@@ -248,16 +248,30 @@
</el-dialog>
<!-- 鏂板锛氱敤鎴风鐞嗗垎鍏徃閰嶇疆瀵硅瘽妗� -->
- <el-dialog title="閰嶇疆绠$悊鍒嗗叕鍙�" :visible.sync="branchDialog.open" width="500px" append-to-body>
- <el-form label-width="100px">
- <el-form-item label="鍙鐞嗗垎鍏徃">
+ <el-dialog title="閰嶇疆绠$悊鍒嗗叕鍙�" :visible.sync="branchDialog.open" width="520px" append-to-body>
+ <el-form label-width="100px" v-loading="branchDialog.loading">
+ <el-form-item label="宸插叧鑱斿垎鍏徃">
+ <div style="margin-bottom:8px;color:#666;font-size:12px;">OA鑷姩鍏宠仈锛堝彧璇伙級锛�</div>
<div>
- <el-tag v-for="bc in branchDialog.companies" :key="bc.deptId" type="info" size="small" style="margin-right:6px;margin-bottom:6px">{{ bc.deptName }}</el-tag>
- <span v-if="branchDialog.companies.length === 0" style="color:#999">鏆傛棤鍒嗗叕鍙�</span>
+ <el-tag v-for="bc in branchDialog.companies" :key="'oa-'+bc.deptId" type="success" size="small" style="margin-right:6px;margin-bottom:6px">{{ bc.deptName }}</el-tag>
+ <span v-if="branchDialog.companies.length === 0" style="color:#999;font-size:12px">鏃燨A鑷姩鍏宠仈</span>
</div>
+ </el-form-item>
+ <el-form-item label="鎵嬪姩娣诲姞">
+ <div style="margin-bottom:6px;color:#666;font-size:12px;">閫夋嫨闇�瑕侀澶栨坊鍔犵殑鍒嗗叕鍙革細</div>
+ <el-checkbox-group v-model="branchDialog.selectedDeptIds">
+ <el-checkbox
+ v-for="branch in branchDialog.allBranches"
+ :key="branch.deptId"
+ :label="branch.deptId"
+ style="display:block;margin-bottom:4px;"
+ >{{ branch.deptName }}</el-checkbox>
+ </el-checkbox-group>
+ <div v-if="branchDialog.allBranches.length === 0" style="color:#999;font-size:12px">鏆傛棤鍒嗗叕鍙告暟鎹�</div>
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
+ <el-button type="primary" @click="submitBranchCompanies" :loading="branchDialog.loading">淇� 瀛�</el-button>
<el-button @click="branchDialog.open = false">鍏� 闂�</el-button>
</div>
</el-dialog>
@@ -266,13 +280,14 @@
<script>
import { listUser, getUser, delUser, addUser, updateUser, resetUserPwd, changeUserStatus, deptTreeSelect } from "@/api/system/user";
+import { updateUserBranch } from "@/api/system/user";
import { getToken } from "@/utils/auth";
import Treeselect from "@riophae/vue-treeselect";
import "@riophae/vue-treeselect/dist/vue-treeselect.css";
import { Splitpanes, Pane } from "splitpanes";
import "splitpanes/dist/splitpanes.css";
import request from "@/utils/request";
-import { listDept, listBranchByUser } from "@/api/system/dept";
+import { listDept, listBranchByUser, listAllBranches } from "@/api/system/dept";
export default {
name: "User",
@@ -388,9 +403,10 @@
branchDialog: {
open: false,
userId: null,
- deptOptions: [],
+ allBranches: [],
selectedDeptIds: [],
- companies: []
+ companies: [],
+ loading: false
},
// 鍩轰簬 oa_order_class 璁$畻鐨勫垎鍏徃鍒楄〃锛堝彧璇诲睍绀猴級
userBranchCompanies: []
@@ -606,22 +622,53 @@
handleManageBranch(row) {
const userId = row.userId;
this.branchDialog.userId = userId;
- // 鍔犺浇鍒嗗叕鍙稿垪琛紙OA鑷姩鎺у埗锛屽彧璇诲睍绀猴級
- listBranchByUser(userId).then(res => {
- const list = res.data || [];
- this.branchDialog.companies = (list || []).map(d => ({ deptId: d.deptId, deptName: d.deptName }));
- this.branchDialog.open = true;
+ this.branchDialog.companies = [];
+ this.branchDialog.allBranches = [];
+ this.branchDialog.selectedDeptIds = [];
+ this.branchDialog.loading = true;
+ this.branchDialog.open = true;
+ // 骞惰鍔犺浇锛氬綋鍓嶇敤鎴稯A鍏宠仈鍒嗗叕鍙� + 鎵�鏈夊垎鍏徃鍒楄〃
+ const p1 = listBranchByUser(userId).then(res => {
+ return (res.data || []).map(d => ({ deptId: d.deptId, deptName: d.deptName }));
+ });
+ const p2 = listAllBranches().then(res => {
+ return res.data || [];
+ });
+ Promise.all([p1, p2]).then(([oaCompanies, allBranches]) => {
+ this.branchDialog.companies = oaCompanies;
+ this.branchDialog.allBranches = allBranches;
+ // 鍒濆鍖栧凡閫夛細浠� userList 涓壘鍒拌鐢ㄦ埛鐨� oaOrderClass锛屽尮閰嶅垎鍏徃
+ const userRow = (this.userList || []).find(u => u.userId === userId);
+ if (userRow && userRow.oaOrderClass) {
+ const codes = userRow.oaOrderClass.split(',').map(s => s.trim()).filter(s => s);
+ const codeSet = new Set(codes);
+ const selected = allBranches
+ .filter(d => {
+ return (d.serviceOrderClass && codeSet.has(d.serviceOrderClass.trim()))
+ || (d.dispatchOrderClass && codeSet.has(d.dispatchOrderClass.trim()));
+ })
+ .map(d => d.deptId);
+ this.branchDialog.selectedDeptIds = selected;
+ }
+ this.branchDialog.loading = false;
}).catch(() => {
this.$modal.msgError('鍔犺浇鍒嗗叕鍙搁厤缃け璐�');
+ this.branchDialog.loading = false;
});
},
/** 淇濆瓨鍒嗗叕鍙搁厤缃� */
- // 宸插彇娑堜繚瀛橈紝OA鑷姩鎺у埗锛堜粎鍙灞曠ず锛�
- // submitBranchCompanies() {
- // const userId = this.branchDialog.userId;
- // const deptIds = this.branchDialog.selectedDeptIds || [];
- // // 淇濈暀鍗犱綅锛岄伩鍏嶈璋冪敤
- // },
+ submitBranchCompanies() {
+ this.branchDialog.loading = true;
+ updateUserBranch(this.branchDialog.userId, this.branchDialog.selectedDeptIds).then(() => {
+ this.$modal.msgSuccess('鍒嗗叕鍙搁厤缃繚瀛樻垚鍔�');
+ this.branchDialog.loading = false;
+ this.branchDialog.open = false;
+ this.getList();
+ }).catch(() => {
+ this.$modal.msgError('淇濆瓨鍒嗗叕鍙搁厤缃け璐�');
+ this.branchDialog.loading = false;
+ });
+ },
/** 鎻愪氦鎸夐挳 */
submitForm: function() {
this.$refs["form"].validate(valid => {
diff --git a/ruoyi-ui/src/views/task/stat/index.vue b/ruoyi-ui/src/views/task/stat/index.vue
new file mode 100644
index 0000000..c9fc5d8
--- /dev/null
+++ b/ruoyi-ui/src/views/task/stat/index.vue
@@ -0,0 +1,219 @@
+<template>
+ <div class="app-container">
+ <!-- 鏌ヨ鏉′欢 -->
+ <el-form :model="queryParams" ref="queryForm" size="small" :inline="true" label-width="80px">
+ <el-form-item label="鍒嗗叕鍙�" prop="deptIds">
+ <el-select
+ v-model="queryParams.deptIds"
+ placeholder="鍏ㄩ儴鍒嗗叕鍙�"
+ clearable
+ filterable
+ multiple
+ collapse-tags
+ style="width: 240px"
+ >
+ <el-option
+ v-for="dept in branchList"
+ :key="dept.deptId"
+ :label="dept.deptName"
+ :value="dept.deptId"
+ />
+ </el-select>
+ </el-form-item>
+ <el-form-item label="鏃堕棿鑼冨洿" prop="dateRange">
+ <el-date-picker
+ v-model="dateRange"
+ type="daterange"
+ value-format="yyyy-MM-dd"
+ range-separator="-"
+ start-placeholder="寮�濮嬫棩鏈�"
+ end-placeholder="缁撴潫鏃ユ湡"
+ style="width: 240px"
+ />
+ </el-form-item>
+ <el-form-item>
+ <el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">鏌ヨ</el-button>
+ <el-button icon="el-icon-refresh" size="mini" @click="resetQuery">閲嶇疆</el-button>
+ </el-form-item>
+ </el-form>
+
+ <!-- 鎿嶄綔鏍� -->
+ <el-row :gutter="10" class="mb8">
+ <el-col :span="1.5">
+ <el-button
+ type="warning"
+ plain
+ icon="el-icon-download"
+ size="mini"
+ :loading="exportLoading"
+ @click="handleExport"
+ v-hasPermi="['task:stat:export']"
+ >瀵煎嚭Excel</el-button>
+ </el-col>
+ </el-row>
+
+ <!-- 鏁版嵁琛ㄦ牸 -->
+ <el-table
+ v-loading="loading"
+ :data="tableData"
+ border
+ style="width: 100%"
+ :span-method="mergeDeptRows"
+ >
+ <el-table-column label="鍒嗗叕鍙�" prop="deptName" align="center" min-width="140" />
+ <el-table-column label="鏃ユ湡" prop="statDate" align="center" width="120" />
+ <el-table-column label="褰曞崟鏁伴噺" prop="orderCount" align="center" width="120">
+ <template slot-scope="scope">
+ <el-tag type="primary">{{ scope.row.orderCount }}</el-tag>
+ </template>
+ </el-table-column>
+ </el-table>
+
+ <!-- 姹囨�讳俊鎭� -->
+ <div v-if="tableData.length > 0" style="margin-top: 12px; color: #606266; font-size: 13px;">
+ 鍏� <strong>{{ tableData.length }}</strong> 鏉¤褰曪紝鍚堣褰曞崟
+ <strong style="color: #E6A23C; font-size: 16px;">{{ totalCount }}</strong> 鍗�
+ </div>
+ </div>
+</template>
+
+<script>
+import { getDeptOrderStat, exportDeptOrderStat } from "@/api/task";
+import { listAllBranches } from "@/api/system/dept";
+
+export default {
+ name: "DeptOrderStat",
+ data() {
+ const now = new Date();
+ const firstDay = new Date(now.getFullYear(), now.getMonth(), 1);
+ const fmt = d => `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')}`;
+ return {
+ loading: false,
+ exportLoading: false,
+ tableData: [],
+ branchList: [],
+ // 榛樿閫夋嫨褰撴湀
+ dateRange: [fmt(firstDay), fmt(now)],
+ queryParams: {
+ deptIds: []
+ },
+ // 鍚堝苟琛岀浉鍏�
+ mergeMap: {}
+ };
+ },
+ computed: {
+ totalCount() {
+ return this.tableData.reduce((sum, row) => sum + (row.orderCount || 0), 0);
+ }
+ },
+ created() {
+ this.loadBranchList();
+ this.getList();
+ },
+ methods: {
+ /** 鍔犺浇鍒嗗叕鍙稿垪琛� */
+ loadBranchList() {
+ listAllBranches().then(res => {
+ this.branchList = res.data || [];
+ }).catch(() => {
+ this.branchList = [];
+ });
+ },
+
+ /** 鏌ヨ缁熻鏁版嵁 */
+ getList() {
+ if (!this.dateRange || this.dateRange.length !== 2) {
+ this.$message.warning("璇烽�夋嫨鏃堕棿鑼冨洿");
+ return;
+ }
+ this.loading = true;
+ const params = {
+ startDate: this.dateRange[0],
+ endDate: this.dateRange[1],
+ deptIds: this.queryParams.deptIds && this.queryParams.deptIds.length > 0
+ ? this.queryParams.deptIds.join(",")
+ : undefined
+ };
+ getDeptOrderStat(params).then(res => {
+ this.tableData = res.data || [];
+ this.buildMergeMap();
+ this.loading = false;
+ }).catch(() => {
+ this.loading = false;
+ });
+ },
+
+ /** 鏋勫缓鍚堝苟琛屾槧灏� */
+ buildMergeMap() {
+ const map = {};
+ const data = this.tableData;
+ let i = 0;
+ while (i < data.length) {
+ let j = i;
+ while (j < data.length && data[j].deptName === data[i].deptName) {
+ j++;
+ }
+ map[i] = j - i;
+ for (let k = i + 1; k < j; k++) {
+ map[k] = 0;
+ }
+ i = j;
+ }
+ this.mergeMap = map;
+ },
+
+ /** 鍚堝苟鍒嗗叕鍙稿垪锛堢浉鍚屽垎鍏徃琛屽悎骞讹級 */
+ mergeDeptRows({ row, column, rowIndex, columnIndex }) {
+ if (columnIndex === 0) {
+ const span = this.mergeMap[rowIndex];
+ if (span !== undefined) {
+ return { rowspan: span, colspan: span === 0 ? 0 : 1 };
+ }
+ }
+ },
+
+ /** 鎼滅储 */
+ handleQuery() {
+ this.getList();
+ },
+
+ /** 閲嶇疆 */
+ resetQuery() {
+ const now = new Date();
+ const firstDay = new Date(now.getFullYear(), now.getMonth(), 1);
+ const fmt = d => `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')}`;
+ this.dateRange = [fmt(firstDay), fmt(now)];
+ this.queryParams.deptIds = [];
+ this.getList();
+ },
+
+ /** 瀵煎嚭 Excel */
+ handleExport() {
+ if (!this.dateRange || this.dateRange.length !== 2) {
+ this.$message.warning("璇烽�夋嫨鏃堕棿鑼冨洿");
+ return;
+ }
+ this.exportLoading = true;
+ const params = {
+ startDate: this.dateRange[0],
+ endDate: this.dateRange[1],
+ deptIds: this.queryParams.deptIds && this.queryParams.deptIds.length > 0
+ ? this.queryParams.deptIds.join(",")
+ : undefined
+ };
+ exportDeptOrderStat(params).then(res => {
+ const blob = new Blob([res], { type: 'application/vnd.ms-excel' });
+ const url = window.URL.createObjectURL(blob);
+ const a = document.createElement('a');
+ a.href = url;
+ a.download = `鍒嗗叕鍙稿綍鍗曠粺璁${params.startDate}_${params.endDate}.xlsx`;
+ a.click();
+ window.URL.revokeObjectURL(url);
+ this.exportLoading = false;
+ }).catch(() => {
+ this.exportLoading = false;
+ });
+ }
+ }
+};
+</script>
diff --git a/ruoyi-ui/vue.config.js b/ruoyi-ui/vue.config.js
index ccb6122..00a4808 100644
--- a/ruoyi-ui/vue.config.js
+++ b/ruoyi-ui/vue.config.js
@@ -11,7 +11,7 @@
const baseUrl = 'http://localhost:8080' // 鍚庣鎺ュ彛
-const port = process.env.port || process.env.npm_config_port || 80 // 绔彛
+const port = process.env.port || process.env.npm_config_port || 8086 // 绔彛
// vue.config.js 閰嶇疆璇存槑
//瀹樻柟vue.config.js 鍙傝�冩枃妗� https://cli.vuejs.org/zh/config/#css-loaderoptions
diff --git a/sql/dept_order_stat_menu.sql b/sql/dept_order_stat_menu.sql
new file mode 100644
index 0000000..58b0aae
--- /dev/null
+++ b/sql/dept_order_stat_menu.sql
@@ -0,0 +1,20 @@
+-- ----------------------------
+-- 鍒嗗叕鍙稿綍鍗曠粺璁� 鑿滃崟鏉冮檺閰嶇疆
+-- 鎸傝浇鍦ㄥ凡鏈夌殑"浠诲姟绠$悊"鐩綍涓�
+-- ----------------------------
+
+-- 鑾峰彇"浠诲姟绠$悊"鐩綍鐨勮彍鍗旾D
+SET @taskParentId = (SELECT menu_id FROM sys_menu WHERE menu_name = '浠诲姟绠$悊' AND menu_type = 'M' LIMIT 1);
+
+-- 鍒嗗叕鍙稿綍鍗曠粺璁� 鑿滃崟锛堝瓙鑿滃崟锛�
+INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
+VALUES ('鍒嗗叕鍙稿綍鍗曠粺璁�', @taskParentId, 9, 'stat/index', 'task/stat/index', 1, 0, 'C', '0', '0', 'task:stat:query', 'chart', 'admin', SYSDATE(), '', NULL, '鍒嗗叕鍙稿綍鍗曠粺璁¤彍鍗�');
+
+-- 鑾峰彇鍒氭彃鍏ョ殑鑿滃崟ID
+SET @statMenuId = (SELECT menu_id FROM sys_menu WHERE menu_name = '鍒嗗叕鍙稿綍鍗曠粺璁�' AND menu_type = 'C' LIMIT 1);
+
+-- 鎸夐挳鏉冮檺
+INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
+VALUES
+('褰曞崟缁熻鏌ヨ', @statMenuId, 1, '#', '', 1, 0, 'F', '0', '0', 'task:stat:query', '#', 'admin', SYSDATE(), '', NULL, ''),
+('褰曞崟缁熻瀵煎嚭', @statMenuId, 2, '#', '', 1, 0, 'F', '0', '0', 'task:stat:export', '#', 'admin', SYSDATE(), '', NULL, '');
diff --git a/sql/vehicle_gps_track_map_menu.sql b/sql/vehicle_gps_track_map_menu.sql
new file mode 100644
index 0000000..38a7c47
--- /dev/null
+++ b/sql/vehicle_gps_track_map_menu.sql
@@ -0,0 +1,53 @@
+-- =====================================================
+-- 杞﹁締琛岄┒杞ㄨ抗锛堝ぉ鍦板浘锛夎彍鍗曢厤缃甋QL
+-- 璇存槑锛氬湪绯荤粺绠$悊鑿滃崟涓坊鍔�"杞﹁締琛岄┒杞ㄨ抗"鑿滃崟椤�
+-- 鍓嶇璺敱锛�/system/gps/trackMap
+-- 缁勪欢璺緞锛歴ystem/gps/trackMap
+-- 鎵�闇�鏉冮檺锛歴ystem:gps:list
+-- 鎵ц鏂瑰紡锛歮ysql -u root -p 鏁版嵁搴撳悕 < vehicle_gps_track_map_menu.sql
+-- =====================================================
+
+-- 娣诲姞"杞﹁締琛岄┒杞ㄨ抗"鑿滃崟锛堢埗鑿滃崟ID=3锛屽嵆杞﹁締绠$悊鐩綍锛�
+-- parent_id 鏍规嵁瀹為檯鏁版嵁搴撲腑鐨勮溅杈嗙鐞嗙洰褰旾D璋冩暣
+INSERT INTO sys_menu (
+ menu_name,
+ parent_id,
+ order_num,
+ path,
+ component,
+ is_frame,
+ is_cache,
+ menu_type,
+ visible,
+ status,
+ perms,
+ icon,
+ create_by,
+ create_time,
+ update_by,
+ update_time,
+ remark
+) VALUES (
+ '杞﹁締琛岄┒杞ㄨ抗',
+ 3, -- 杞﹁締绠$悊鐩綍ID锛堝涓嶅璇蜂慨鏀逛负瀹為檯ID锛�
+ 10,
+ 'trackMap',
+ 'system/gps/trackMap',
+ 1,
+ 0,
+ 'C',
+ '0',
+ '0',
+ 'system:gps:list',
+ 'guide',
+ 'admin',
+ sysdate(),
+ '',
+ null,
+ '杞﹁締琛岄┒杞ㄨ抗锛堝ぉ鍦板浘锛夋煡璇㈤〉闈�'
+);
+
+-- 鏌ヨ缁撴灉楠岃瘉
+SELECT menu_id, menu_name, parent_id, path, component, perms, status
+FROM sys_menu
+WHERE menu_name = '杞﹁締琛岄┒杞ㄨ抗';
diff --git a/test/1.html b/test/1.html
new file mode 100644
index 0000000..6ddfa32
--- /dev/null
+++ b/test/1.html
@@ -0,0 +1,24 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta http-equiv="content-type" content="text/html; charset=utf-8"/>
+ <meta name="keywords" content="澶╁湴鍥�"/>
+ <title>澶╁湴鍥撅紞鍦板浘API锛嶈寖渚嬶紞缁忕含搴︾洿鎶曞湴鍥�</title>
+ <script type="text/javascript" src="http://api.tianditu.gov.cn/api?v=4.0&tk=079300b89bce333ead2df1476c43ecb0"></script>
+ <style type="text/css">body,html{width:100%;height:100%;margin:0;font-family:"Microsoft YaHei"}#mapDiv{width:100%;height:400px}input,b,p{margin-left:5px;font-size:14px}</style>
+ <script>
+ var map;
+ var zoom = 12;
+ function onLoad() {
+ map = new T.Map('mapDiv', {
+ projection: 'EPSG:4326'
+ });
+ map.centerAndZoom(new T.LngLat(116.40769, 39.89945), zoom);
+ }
+ </script>
+</head>
+<body onLoad="onLoad()">
+<div id="mapDiv"></div>
+<p>鏈ず渚嬫紨绀哄浣曟樉绀虹粡绾害鐩存姇鍦板浘銆�</p>
+</body>
+</html>
\ No newline at end of file
--
Gitblit v1.9.1