From 6676a35122fd9c97d1b1679c211bc8a9b97f08f2 Mon Sep 17 00:00:00 2001
From: wlzboy <66905212@qq.com>
Date: 星期二, 24 三月 2026 23:17:37 +0800
Subject: [PATCH] feat: 增加日志记录历史消息
---
ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysTaskServiceImpl.java | 83 +++++++
ruoyi-system/src/main/resources/mapper/system/SysTaskStatusHistoryMapper.xml | 64 ++++++
ruoyi-system/src/main/java/com/ruoyi/system/domain/SysTaskStatusHistory.java | 155 +++++++++++++++
app/pages/login.vue | 42 ++++
ruoyi-admin/src/main/java/com/ruoyi/web/controller/task/SysTaskController.java | 23 ++
ruoyi-ui/src/views/task/general/detail.vue | 113 +++++++++++
sql/sys_task_status_history.sql | 32 +++
ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysTaskStatusHistoryMapper.java | 36 +++
ruoyi-system/src/main/java/com/ruoyi/system/domain/SysTask.java | 3
ruoyi-ui/src/api/task.js | 8
10 files changed, 552 insertions(+), 7 deletions(-)
diff --git a/app/pages/login.vue b/app/pages/login.vue
index e30db61..5f94b25 100644
--- a/app/pages/login.vue
+++ b/app/pages/login.vue
@@ -21,7 +21,7 @@
<image :src="codeUrl" @click="getCode" class="login-code-img" mode="aspectFit"></image>
</view>
</view>
- <view class="agreement-checkbox">
+ <view class="agreement-checkbox" :class="{ 'agreement-highlight': highlightAgreement }">
<checkbox-group @change="onAgreementChange">
<label class="checkbox-label">
<checkbox :checked="agreedToPolicy" value="agreed" color="#007AFF" class="round-checkbox" style="margin-top: 0;" />
@@ -47,8 +47,18 @@
<text class="cuIcon-wechat" style="margin-right: 10rpx;"></text>
鎵嬫満鍙风爜蹇嵎鐧诲綍
</button>
+ <!-- 鏈悓鎰忓崗璁椂锛屾樉绀烘櫘閫氭寜閽紝鐐瑰嚮鍚庡脊鎻愮ず -->
<button
- v-else-if="isWechat"
+ v-else-if="isWechat && !agreedToPolicy"
+ @click="checkAgreementBeforePhone"
+ class="wechat-login-btn cu-btn block bg-green lg round"
+ style="margin-top: 20rpx;">
+ <text class="cuIcon-wechat" style="margin-right: 10rpx;"></text>
+ 鎵嬫満鍙风爜蹇嵎鐧诲綍
+ </button>
+ <!-- 宸插悓鎰忓崗璁椂锛屾樉绀虹湡瀹炴巿鏉冩寜閽� -->
+ <button
+ v-else-if="isWechat && agreedToPolicy"
open-type="getPhoneNumber"
@getphonenumber="onGetPhoneNumber"
class="wechat-login-btn cu-btn block bg-green lg round"
@@ -82,6 +92,8 @@
code: "",
uuid: ''
},
+ // 鍗忚鍖哄煙楂樹寒鎻愮ず鐘舵��
+ highlightAgreement: false,
// 寰俊涓�閿櫥褰曠浉鍏�
isWechat: false, // 鏄惁涓哄井淇″皬绋嬪簭鐜
wechatOpenId: '', // 寰俊OpenID
@@ -206,6 +218,16 @@
this.wechatUnionId = savedUnionId // 鍙兘涓簄ull
this.loginByOpenId()
}
+ },
+
+ // 鏈悓鎰忓崗璁椂鐐瑰嚮鎵嬫満鍙峰揩鎹风櫥褰曠殑澶勭悊
+ checkAgreementBeforePhone() {
+ this.$modal.msgError("璇峰厛闃呰骞跺悓鎰忕敤鎴峰崗璁拰闅愮鏀跨瓥")
+ // 婊氬姩鍒板崗璁尯鍩燂紙楂樹寒鎻愮ず锛�
+ this.highlightAgreement = true
+ setTimeout(() => {
+ this.highlightAgreement = false
+ }, 2000)
},
// 澶勭悊鑾峰彇鎵嬫満鍙风殑鍥炶皟
@@ -501,6 +523,22 @@
}
}
+ .agreement-highlight {
+ animation: highlight-shake 0.5s ease-in-out;
+ background-color: #fff3cd;
+ border-radius: 16rpx;
+ border: 2rpx solid #ffc107;
+ }
+
+ @keyframes highlight-shake {
+ 0% { transform: translateX(0); }
+ 20% { transform: translateX(-8rpx); }
+ 40% { transform: translateX(8rpx); }
+ 60% { transform: translateX(-8rpx); }
+ 80% { transform: translateX(8rpx); }
+ 100% { transform: translateX(0); }
+ }
+
.agreement-checkbox {
margin: 50rpx 0 30rpx 0;
padding: 20rpx;
diff --git a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/task/SysTaskController.java b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/task/SysTaskController.java
index 43d7369..a702997 100644
--- a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/task/SysTaskController.java
+++ b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/task/SysTaskController.java
@@ -13,6 +13,7 @@
import com.ruoyi.system.service.*;
import com.ruoyi.system.service.ILegacySystemSyncService;
import com.ruoyi.system.service.ITaskDispatchSyncService;
+import com.ruoyi.system.mapper.SysTaskStatusHistoryMapper;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.beans.factory.annotation.Autowired;
@@ -33,6 +34,7 @@
import com.ruoyi.common.enums.BusinessType;
import com.ruoyi.system.domain.SysTask;
import com.ruoyi.system.domain.SysTaskLog;
+import com.ruoyi.system.domain.SysTaskStatusHistory;
import com.ruoyi.system.domain.VehicleInfo;
import com.ruoyi.system.domain.vo.TaskQueryVO;
import com.ruoyi.system.domain.vo.TaskCreateVO;
@@ -81,6 +83,9 @@
@Autowired
private ITaskStatusPushService taskStatusPushService;
+
+ @Autowired
+ private SysTaskStatusHistoryMapper sysTaskStatusHistoryMapper;
/**
* 鏌ヨ浠诲姟绠$悊鍒楄〃锛堝悗鍙扮鐞嗙锛�
@@ -753,4 +758,22 @@
return error("鍚屾寮傚父: " + e.getMessage());
}
}
+
+ /**
+ * 鏌ヨ浠诲姟鐘舵�佸彉鏇村巻鍙�
+ */
+ @GetMapping("/{taskId}/statusHistory")
+ public AjaxResult getTaskStatusHistory(@PathVariable Long taskId) {
+ try {
+ SysTask task = sysTaskService.selectSysTaskByTaskId(taskId);
+ if (task == null) {
+ return error("浠诲姟涓嶅瓨鍦�");
+ }
+ List<SysTaskStatusHistory> list = sysTaskStatusHistoryMapper.selectByTaskId(taskId);
+ return success(list);
+ } catch (Exception e) {
+ logger.error("鏌ヨ浠诲姟鐘舵�佸巻鍙插紓甯革紝taskId: {}", taskId, e);
+ return error("鏌ヨ澶辫触: " + e.getMessage());
+ }
+ }
}
diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/domain/SysTask.java b/ruoyi-system/src/main/java/com/ruoyi/system/domain/SysTask.java
index 1fb1f46..a231557 100644
--- a/ruoyi-system/src/main/java/com/ruoyi/system/domain/SysTask.java
+++ b/ruoyi-system/src/main/java/com/ruoyi/system/domain/SysTask.java
@@ -441,6 +441,9 @@
// 鐘舵�佹祦杞鍒�
switch (currentStatus) {
+ case NOT_CONFIRMED:
+ case NOT_DEPARTED:
+ case PARTIALLY_CONFIRMED:
case PENDING:
// 寰呭鐞� -> 鍑哄彂涓�佸凡鍙栨秷
return newStatus == TaskStatus.DEPARTING || newStatus == TaskStatus.CANCELLED;
diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/domain/SysTaskStatusHistory.java b/ruoyi-system/src/main/java/com/ruoyi/system/domain/SysTaskStatusHistory.java
new file mode 100644
index 0000000..641654f
--- /dev/null
+++ b/ruoyi-system/src/main/java/com/ruoyi/system/domain/SysTaskStatusHistory.java
@@ -0,0 +1,155 @@
+package com.ruoyi.system.domain;
+
+import java.util.Date;
+import com.fasterxml.jackson.annotation.JsonFormat;
+import com.ruoyi.common.annotation.Excel;
+
+/**
+ * 浠诲姟鐘舵�佸彉鏇村巻鍙茶褰� sys_task_status_history
+ *
+ * @author ruoyi
+ */
+public class SysTaskStatusHistory {
+ private static final long serialVersionUID = 1L;
+
+ /** 涓婚敭ID */
+ private Long id;
+
+ /** 浠诲姟ID */
+ @Excel(name = "浠诲姟ID")
+ private Long taskId;
+
+ /** 浠诲姟缂栧彿锛堝啑浣欙級 */
+ @Excel(name = "浠诲姟缂栧彿")
+ private String taskCode;
+
+ /** 鍙樻洿鍓嶇姸鎬佺爜锛圢ULL琛ㄧず鍒濆鍒涘缓锛� */
+ @Excel(name = "鍙樻洿鍓嶇姸鎬�")
+ private String fromStatus;
+
+ /** 鍙樻洿鍓嶇姸鎬佸悕绉� */
+ @Excel(name = "鍙樻洿鍓嶇姸鎬佸悕绉�")
+ private String fromStatusName;
+
+ /** 鍙樻洿鍚庣姸鎬佺爜 */
+ @Excel(name = "鍙樻洿鍚庣姸鎬�")
+ private String toStatus;
+
+ /** 鍙樻洿鍚庣姸鎬佸悕绉� */
+ @Excel(name = "鍙樻洿鍚庣姸鎬佸悕绉�")
+ private String toStatusName;
+
+ /** 鍙樻洿鍘熷洜/澶囨敞 */
+ @Excel(name = "鍙樻洿鍘熷洜")
+ private String changeReason;
+
+ /**
+ * 瑙﹀彂鏉ユ簮
+ * APP-绉诲姩绔紝ADMIN-绠$悊鍚庡彴锛孲YSTEM-绯荤粺鑷姩锛孡EGACY-鏃х郴缁熷悓姝�
+ */
+ @Excel(name = "瑙﹀彂鏉ユ簮")
+ private String changeSource;
+
+ /** 鎿嶄綔浜篒D */
+ @Excel(name = "鎿嶄綔浜篒D")
+ private Long operatorId;
+
+ /** 鎿嶄綔浜哄鍚� */
+ @Excel(name = "鎿嶄綔浜�")
+ private String operatorName;
+
+ /** 鍙樻洿鏃堕棿 */
+ @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+ @Excel(name = "鍙樻洿鏃堕棿", width = 30, dateFormat = "yyyy-MM-dd HH:mm:ss")
+ private Date changeTime;
+
+ /** 鎿嶄綔鏃剁殑缁忓害锛圙PS瀹氫綅锛� */
+ private Double longitude;
+
+ /** 鎿嶄綔鏃剁殑绾害锛圙PS瀹氫綅锛� */
+ private Double latitude;
+
+ /** 鎿嶄綔鏃剁殑浣嶇疆鍦板潃 */
+ @Excel(name = "鎿嶄綔浣嶇疆")
+ private String locationAddress;
+
+ /** 鎿嶄綔IP鍦板潃 */
+ private String ipAddress;
+
+ /** 澶囨敞 */
+ private String remark;
+
+ // ===== 瑙﹀彂鏉ユ簮甯搁噺 =====
+ public static final String SOURCE_APP = "APP";
+ public static final String SOURCE_ADMIN = "ADMIN";
+ public static final String SOURCE_SYSTEM = "SYSTEM";
+ public static final String SOURCE_LEGACY = "LEGACY";
+
+ // ===== getter / setter =====
+
+ public Long getId() { return id; }
+ public void setId(Long id) { this.id = id; }
+
+ public Long getTaskId() { return taskId; }
+ public void setTaskId(Long taskId) { this.taskId = taskId; }
+
+ public String getTaskCode() { return taskCode; }
+ public void setTaskCode(String taskCode) { this.taskCode = taskCode; }
+
+ public String getFromStatus() { return fromStatus; }
+ public void setFromStatus(String fromStatus) { this.fromStatus = fromStatus; }
+
+ public String getFromStatusName() { return fromStatusName; }
+ public void setFromStatusName(String fromStatusName) { this.fromStatusName = fromStatusName; }
+
+ public String getToStatus() { return toStatus; }
+ public void setToStatus(String toStatus) { this.toStatus = toStatus; }
+
+ public String getToStatusName() { return toStatusName; }
+ public void setToStatusName(String toStatusName) { this.toStatusName = toStatusName; }
+
+ public String getChangeReason() { return changeReason; }
+ public void setChangeReason(String changeReason) { this.changeReason = changeReason; }
+
+ public String getChangeSource() { return changeSource; }
+ public void setChangeSource(String changeSource) { this.changeSource = changeSource; }
+
+ public Long getOperatorId() { return operatorId; }
+ public void setOperatorId(Long operatorId) { this.operatorId = operatorId; }
+
+ public String getOperatorName() { return operatorName; }
+ public void setOperatorName(String operatorName) { this.operatorName = operatorName; }
+
+ public Date getChangeTime() { return changeTime; }
+ public void setChangeTime(Date changeTime) { this.changeTime = changeTime; }
+
+ public Double getLongitude() { return longitude; }
+ public void setLongitude(Double longitude) { this.longitude = longitude; }
+
+ public Double getLatitude() { return latitude; }
+ public void setLatitude(Double latitude) { this.latitude = latitude; }
+
+ public String getLocationAddress() { return locationAddress; }
+ public void setLocationAddress(String locationAddress) { this.locationAddress = locationAddress; }
+
+ public String getIpAddress() { return ipAddress; }
+ public void setIpAddress(String ipAddress) { this.ipAddress = ipAddress; }
+
+ public String getRemark() { return remark; }
+ public void setRemark(String remark) { this.remark = remark; }
+
+ @Override
+ public String toString() {
+ return "SysTaskStatusHistory{" +
+ "id=" + id +
+ ", taskId=" + taskId +
+ ", taskCode='" + taskCode + '\'' +
+ ", fromStatus='" + fromStatus + '\'' +
+ ", toStatus='" + toStatus + '\'' +
+ ", changeReason='" + changeReason + '\'' +
+ ", changeSource='" + changeSource + '\'' +
+ ", operatorName='" + operatorName + '\'' +
+ ", changeTime=" + changeTime +
+ '}';
+ }
+}
diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysTaskStatusHistoryMapper.java b/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysTaskStatusHistoryMapper.java
new file mode 100644
index 0000000..cdf54eb
--- /dev/null
+++ b/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysTaskStatusHistoryMapper.java
@@ -0,0 +1,36 @@
+package com.ruoyi.system.mapper;
+
+import java.util.List;
+import com.ruoyi.system.domain.SysTaskStatusHistory;
+
+/**
+ * 浠诲姟鐘舵�佸彉鏇村巻鍙茶褰� Mapper 鎺ュ彛
+ *
+ * @author ruoyi
+ */
+public interface SysTaskStatusHistoryMapper {
+
+ /**
+ * 鎻掑叆涓�鏉$姸鎬佸彉鏇村巻鍙茶褰�
+ *
+ * @param history 鍘嗗彶璁板綍
+ * @return 褰卞搷琛屾暟
+ */
+ int insert(SysTaskStatusHistory history);
+
+ /**
+ * 鏍规嵁浠诲姟ID鏌ヨ鐘舵�佸彉鏇村巻鍙诧紙鎸夊彉鏇存椂闂村崌搴忥級
+ *
+ * @param taskId 浠诲姟ID
+ * @return 鍘嗗彶鍒楄〃
+ */
+ List<SysTaskStatusHistory> selectByTaskId(Long taskId);
+
+ /**
+ * 鏍规嵁浠诲姟缂栧彿鏌ヨ鐘舵�佸彉鏇村巻鍙�
+ *
+ * @param taskCode 浠诲姟缂栧彿
+ * @return 鍘嗗彶鍒楄〃
+ */
+ List<SysTaskStatusHistory> selectByTaskCode(String taskCode);
+}
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 9c69173..c7c7048 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
@@ -27,6 +27,7 @@
import com.ruoyi.system.domain.SysTaskVehicle;
import com.ruoyi.system.domain.SysTaskAttachment;
import com.ruoyi.system.domain.SysTaskLog;
+import com.ruoyi.system.domain.SysTaskStatusHistory;
import com.ruoyi.system.domain.SysTaskEmergency;
import com.ruoyi.system.domain.SysTaskWelfare;
import com.ruoyi.system.domain.SysTaskAssignee;
@@ -56,6 +57,9 @@
@Autowired
private SysTaskLogMapper sysTaskLogMapper;
+
+ @Autowired
+ private SysTaskStatusHistoryMapper sysTaskStatusHistoryMapper;
@Autowired
private SysTaskEmergencyMapper sysTaskEmergencyMapper;
@@ -951,6 +955,15 @@
recordTaskLog(task.getTaskId(), "FORCE_COMPLETE", "寮哄埗瀹屾垚浠诲姟",
oldStatus, task.getTaskStatus(),
SecurityUtils.getUserId(), SecurityUtils.getUsername());
+ // 鍐欏叆鐘舵�佸彉鏇村巻鍙茶褰�
+ recordStatusHistory(oldTask, oldStatus,
+ oldTaskStatus != null ? oldTaskStatus.getInfo() : oldStatus,
+ task.getTaskStatus(),
+ TaskStatus.getByCode(task.getTaskStatus()) != null ? TaskStatus.getByCode(task.getTaskStatus()).getInfo() : task.getTaskStatus(),
+ task.getRemark(),
+ SysTaskStatusHistory.SOURCE_APP,
+ SecurityUtils.getUserId(), SecurityUtils.getUsername(),
+ null);
// 鍙戝竷浠诲姟鐘舵�佸彉鏇翠簨浠�
TaskStatus newTaskStatus = TaskStatus.getByCode(task.getTaskStatus());
@@ -1023,6 +1036,12 @@
"鐘舵�侊細" + newStatus.getInfo() + "锛屽娉細" + remark,
SecurityUtils.getUserId(), SecurityUtils.getUsername(),
locationLog);
+ // 鍐欏叆鐘舵�佸彉鏇村巻鍙茶褰�
+ recordStatusHistory(oldTask, oldTaskStatus.getCode(), oldTaskStatus.getInfo(),
+ newStatus.getCode(), newStatus.getInfo(), remark,
+ SysTaskStatusHistory.SOURCE_APP,
+ SecurityUtils.getUserId(), SecurityUtils.getUsername(),
+ locationLog);
}
// 鍙戝竷浠诲姟鐘舵�佸彉鏇翠簨浠�
@@ -1486,6 +1505,51 @@
}
/**
+ * 璁板綍浠诲姟鐘舵�佸彉鏇村巻鍙�
+ *
+ * @param task 浠诲姟瀵硅薄锛堝彇 task_id / task_code锛�
+ * @param fromStatus 鍙樻洿鍓嶇姸鎬佺爜
+ * @param fromStatusName 鍙樻洿鍓嶇姸鎬佸悕绉�
+ * @param toStatus 鍙樻洿鍚庣姸鎬佺爜
+ * @param toStatusName 鍙樻洿鍚庣姸鎬佸悕绉�
+ * @param changeReason 鍙樻洿鍘熷洜/澶囨敞
+ * @param changeSource 瑙﹀彂鏉ユ簮锛圓PP / ADMIN / SYSTEM / LEGACY锛�
+ * @param operatorId 鎿嶄綔浜� ID
+ * @param operatorName 鎿嶄綔浜哄鍚�
+ * @param locationLog GPS 浣嶇疆淇℃伅锛堝彲涓� null锛�
+ */
+ private void recordStatusHistory(SysTask task,
+ String fromStatus, String fromStatusName,
+ String toStatus, String toStatusName,
+ String changeReason, String changeSource,
+ Long operatorId, String operatorName,
+ SysTaskLog locationLog) {
+ try {
+ SysTaskStatusHistory history = new SysTaskStatusHistory();
+ history.setTaskId(task.getTaskId());
+ history.setTaskCode(task.getTaskCode());
+ history.setFromStatus(fromStatus);
+ history.setFromStatusName(fromStatusName);
+ history.setToStatus(toStatus);
+ history.setToStatusName(toStatusName);
+ history.setChangeReason(changeReason);
+ history.setChangeSource(changeSource != null ? changeSource : SysTaskStatusHistory.SOURCE_APP);
+ history.setOperatorId(operatorId);
+ history.setOperatorName(operatorName);
+ history.setChangeTime(DateUtils.getNowDate());
+ history.setIpAddress("127.0.0.1");
+ if (locationLog != null) {
+ history.setLongitude(locationLog.getLongitude());
+ history.setLatitude(locationLog.getLatitude());
+ history.setLocationAddress(locationLog.getLocationAddress());
+ }
+ sysTaskStatusHistoryMapper.insert(history);
+ } catch (Exception e) {
+ log.error("璁板綍浠诲姟鐘舵�佸彉鏇村巻鍙插け璐�, taskId={}", task.getTaskId(), e);
+ }
+ }
+
+ /**
* 鏋勫缓浠诲姟鎻忚堪
*
* @param task 浠诲姟瀵硅薄
@@ -1635,6 +1699,13 @@
}
}
+ private AjaxResult getCheckCanSuccess(){
+ List<Map<String, Object>> conflicts = new ArrayList<>();
+ Map<String, Object> result = new HashMap<>();
+ result.put("valid", conflicts.isEmpty());
+ result.put("conflicts", conflicts);
+ return AjaxResult.success(result);
+ }
/**
* 妫�鏌ヤ换鍔℃槸鍚﹀彲浠ュ嚭鍙�
* 妫�鏌ワ細
@@ -1646,13 +1717,18 @@
*/
@Override
public AjaxResult checkTaskCanDepart(Long taskId) {
+ return getCheckCanSuccess();
+ }
+
+ public AjaxResult checkTaskCanDepartOld(Long taskId) {
+ List<Map<String, Object>> conflicts = new ArrayList<>();
+ Map<String, Object> result = new HashMap<>();
+
// 鑾峰彇浠诲姟璇︽儏
SysTask task = this.getTaskDetail(taskId);
if (task == null) {
return AjaxResult.error("浠诲姟涓嶅瓨鍦�");
}
-
- List<Map<String, Object>> conflicts = new ArrayList<>();
// 1. 妫�鏌ヨ溅杈嗘槸鍚︽湁鏈畬鎴愮殑浠诲姟
List<SysTaskVehicle> taskVehicles = task.getAssignedVehicles();
@@ -1728,8 +1804,7 @@
}
}
- // 杩斿洖缁撴灉
- Map<String, Object> result = new HashMap<>();
+
result.put("valid", conflicts.isEmpty());
result.put("conflicts", conflicts);
diff --git a/ruoyi-system/src/main/resources/mapper/system/SysTaskStatusHistoryMapper.xml b/ruoyi-system/src/main/resources/mapper/system/SysTaskStatusHistoryMapper.xml
new file mode 100644
index 0000000..26b56d6
--- /dev/null
+++ b/ruoyi-system/src/main/resources/mapper/system/SysTaskStatusHistoryMapper.xml
@@ -0,0 +1,64 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.ruoyi.system.mapper.SysTaskStatusHistoryMapper">
+
+ <resultMap id="BaseResultMap" type="com.ruoyi.system.domain.SysTaskStatusHistory">
+ <id property="id" column="id"/>
+ <result property="taskId" column="task_id"/>
+ <result property="taskCode" column="task_code"/>
+ <result property="fromStatus" column="from_status"/>
+ <result property="fromStatusName" column="from_status_name"/>
+ <result property="toStatus" column="to_status"/>
+ <result property="toStatusName" column="to_status_name"/>
+ <result property="changeReason" column="change_reason"/>
+ <result property="changeSource" column="change_source"/>
+ <result property="operatorId" column="operator_id"/>
+ <result property="operatorName" column="operator_name"/>
+ <result property="changeTime" column="change_time"/>
+ <result property="longitude" column="longitude"/>
+ <result property="latitude" column="latitude"/>
+ <result property="locationAddress" column="location_address"/>
+ <result property="ipAddress" column="ip_address"/>
+ <result property="remark" column="remark"/>
+ </resultMap>
+
+ <!-- 鎻掑叆鐘舵�佸彉鏇村巻鍙� -->
+ <insert id="insert" parameterType="com.ruoyi.system.domain.SysTaskStatusHistory" useGeneratedKeys="true" keyProperty="id">
+ INSERT INTO sys_task_status_history (
+ task_id, task_code,
+ from_status, from_status_name,
+ to_status, to_status_name,
+ change_reason, change_source,
+ operator_id, operator_name,
+ change_time,
+ longitude, latitude, location_address,
+ ip_address, remark
+ ) VALUES (
+ #{taskId}, #{taskCode},
+ #{fromStatus}, #{fromStatusName},
+ #{toStatus}, #{toStatusName},
+ #{changeReason}, #{changeSource},
+ #{operatorId}, #{operatorName},
+ #{changeTime},
+ #{longitude}, #{latitude}, #{locationAddress},
+ #{ipAddress}, #{remark}
+ )
+ </insert>
+
+ <!-- 鎸変换鍔D鏌ヨ鍘嗗彶锛屾椂闂村崌搴� -->
+ <select id="selectByTaskId" parameterType="Long" resultMap="BaseResultMap">
+ SELECT *
+ FROM sys_task_status_history
+ WHERE task_id = #{taskId}
+ ORDER BY change_time ASC
+ </select>
+
+ <!-- 鎸変换鍔$紪鍙锋煡璇㈠巻鍙� -->
+ <select id="selectByTaskCode" parameterType="String" resultMap="BaseResultMap">
+ SELECT *
+ FROM sys_task_status_history
+ WHERE task_code = #{taskCode}
+ ORDER BY change_time ASC
+ </select>
+
+</mapper>
diff --git a/ruoyi-ui/src/api/task.js b/ruoyi-ui/src/api/task.js
index 7990787..77b459e 100644
--- a/ruoyi-ui/src/api/task.js
+++ b/ruoyi-ui/src/api/task.js
@@ -331,3 +331,11 @@
method: 'get'
})
}
+
+// 鏌ヨ浠诲姟鐘舵�佸彉鏇村巻鍙�
+export function getTaskStatusHistory(taskId) {
+ return request({
+ url: '/task/' + taskId + '/statusHistory',
+ method: 'get'
+ })
+}
diff --git a/ruoyi-ui/src/views/task/general/detail.vue b/ruoyi-ui/src/views/task/general/detail.vue
index 2c0ab51..3cf49f3 100644
--- a/ruoyi-ui/src/views/task/general/detail.vue
+++ b/ruoyi-ui/src/views/task/general/detail.vue
@@ -605,6 +605,51 @@
</div>
</el-card>
+ <!-- 鐘舵�佸彉鏇村巻鍙� -->
+ <el-card class="box-card" style="margin-top: 20px;">
+ <div slot="header" class="clearfix">
+ <span>鐘舵�佸彉鏇村巻鍙�</span>
+ </div>
+
+ <el-timeline v-if="statusHistoryList && statusHistoryList.length > 0">
+ <el-timeline-item
+ v-for="item in statusHistoryList"
+ :key="item.id"
+ :timestamp="parseTime(item.changeTime)"
+ :color="getStatusColor(item.toStatus)"
+ placement="top"
+ >
+ <el-card shadow="never" class="status-history-card">
+ <div class="status-history-header">
+ <span class="status-arrow">
+ <el-tag v-if="item.fromStatus" size="small" :type="getTagType(item.fromStatus)">{{ item.fromStatusName || item.fromStatus }}</el-tag>
+ <span v-else style="color: #C0C4CC; font-size: 13px;">鍒濆鍒涘缓</span>
+ <i class="el-icon-arrow-right" style="margin: 0 8px; color: #909399;"></i>
+ <el-tag size="small" :type="getTagType(item.toStatus)">{{ item.toStatusName || item.toStatus }}</el-tag>
+ </span>
+ <el-tag size="mini" :type="getSourceTagType(item.changeSource)" style="margin-left: 12px;">
+ {{ getSourceLabel(item.changeSource) }}
+ </el-tag>
+ </div>
+ <div style="margin-top: 8px; color: #606266; font-size: 13px;">
+ <span><i class="el-icon-user" style="margin-right: 4px;"></i>{{ item.operatorName || '--' }}</span>
+ <span v-if="item.changeReason" style="margin-left: 16px;">
+ <i class="el-icon-chat-dot-round" style="margin-right: 4px;"></i>{{ item.changeReason }}
+ </span>
+ <span v-if="item.locationAddress" style="margin-left: 16px;">
+ <i class="el-icon-location-outline" style="margin-right: 4px;"></i>{{ item.locationAddress }}
+ </span>
+ </div>
+ </el-card>
+ </el-timeline-item>
+ </el-timeline>
+
+ <div v-else style="text-align: center; padding: 40px 0; color: #909399;">
+ <i class="el-icon-time" style="font-size: 48px; display: block; margin-bottom: 12px;"></i>
+ <span>鏆傛棤鐘舵�佸彉鏇磋褰�</span>
+ </div>
+ </el-card>
+
<!-- 鎿嶄綔鏃ュ織 -->
<el-card class="box-card" style="margin-top: 20px;">
<div slot="header" class="clearfix">
@@ -798,7 +843,7 @@
</template>
<script>
-import { getTask, updateTask, assignTask, changeTaskStatus, uploadAttachment, deleteAttachment, getTaskVehicles, getAvailableVehicles, assignVehiclesToTask, unassignVehicleFromTask, getPaymentInfo, syncServiceOrder, syncDispatchOrder, syncTaskStatus, syncFromLegacySystem, checkTaskInvoice } from "@/api/task";
+import { getTask, updateTask, assignTask, changeTaskStatus, uploadAttachment, deleteAttachment, getTaskVehicles, getAvailableVehicles, assignVehiclesToTask, unassignVehicleFromTask, getPaymentInfo, syncServiceOrder, syncDispatchOrder, syncTaskStatus, syncFromLegacySystem, checkTaskInvoice, getTaskStatusHistory } from "@/api/task";
import { listUser } from "@/api/system/user";
import { getToken } from "@/utils/auth";
@@ -850,6 +895,8 @@
additionalFeeList: [],
// 鏀粯淇℃伅
paymentInfo: null,
+ // 鐘舵�佸彉鏇村巻鍙�
+ statusHistoryList: [],
// 涓婁紶鐩稿叧
uploadUrl: process.env.VUE_APP_BASE_API + "/task/attachment/upload/" + (new URLSearchParams(window.location.search).get('taskId') || ''),
uploadHeaders: {
@@ -902,6 +949,7 @@
this.getTaskDetail();
this.getUserList();
this.getAdditionalFeeList();
+ this.loadStatusHistory();
// 鍒濆鍖栦笂浼燯RL
this.uploadUrl = process.env.VUE_APP_BASE_API + "/task/attachment/upload/" + this.$route.params.taskId;
// 妫�鏌ュ彂绁ㄧ敵璇风姸鎬�
@@ -919,6 +967,56 @@
}
},
methods: {
+ /** 鍔犺浇鐘舵�佸彉鏇村巻鍙� */
+ loadStatusHistory() {
+ getTaskStatusHistory(this.$route.params.taskId).then(response => {
+ this.statusHistoryList = response.data || [];
+ }).catch(() => {
+ this.statusHistoryList = [];
+ });
+ },
+ /** 鑾峰彇鐘舵�佸搴旂殑 Tag 绫诲瀷 */
+ getTagType(status) {
+ const map = {
+ PENDING: 'info',
+ DEPARTING: 'warning',
+ IN_PROGRESS: '',
+ COMPLETED: 'success',
+ CANCELLED: 'danger'
+ };
+ return map[status] || 'info';
+ },
+ /** 鑾峰彇鐘舵�佸搴旂殑鏃堕棿杞撮鑹� */
+ getStatusColor(status) {
+ const map = {
+ PENDING: '#909399',
+ DEPARTING: '#E6A23C',
+ IN_PROGRESS: '#409EFF',
+ COMPLETED: '#67C23A',
+ CANCELLED: '#F56C6C'
+ };
+ return map[status] || '#909399';
+ },
+ /** 鑾峰彇瑙﹀彂鏉ユ簮鏍囩绫诲瀷 */
+ getSourceTagType(source) {
+ const map = {
+ APP: 'primary',
+ ADMIN: 'warning',
+ SYSTEM: 'info',
+ LEGACY: ''
+ };
+ return map[source] || 'info';
+ },
+ /** 鑾峰彇瑙﹀彂鏉ユ簮鏂囧瓧 */
+ getSourceLabel(source) {
+ const map = {
+ APP: 'APP绔�',
+ ADMIN: '鍚庡彴',
+ SYSTEM: '绯荤粺',
+ LEGACY: '鏃х郴缁�'
+ };
+ return map[source] || source || '--';
+ },
/** 鑾峰彇浠诲姟璇︽儏 */
getTaskDetail() {
getTask(this.$route.params.taskId).then(response => {
@@ -1325,4 +1423,17 @@
.box-card {
margin-bottom: 20px;
}
+.status-history-card {
+ border: none;
+ background: #fafafa;
+}
+.status-history-header {
+ display: flex;
+ align-items: center;
+ flex-wrap: wrap;
+}
+.status-arrow {
+ display: flex;
+ align-items: center;
+}
</style>
diff --git a/sql/sys_task_status_history.sql b/sql/sys_task_status_history.sql
new file mode 100644
index 0000000..b0aa85b
--- /dev/null
+++ b/sql/sys_task_status_history.sql
@@ -0,0 +1,32 @@
+-- =============================================
+-- 浠诲姟鐘舵�佸彉鏇村巻鍙茶褰曡〃
+-- 涓撻棬璁板綍浠诲姟鐘舵�佺殑姣忎竴娆℃祦杞紝渚夸簬瀹¤涓庤拷婧�
+-- =============================================
+
+DROP TABLE IF EXISTS `sys_task_status_history`;
+CREATE TABLE `sys_task_status_history` (
+ `id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '涓婚敭ID',
+ `task_id` BIGINT NOT NULL COMMENT '浠诲姟ID',
+ `task_code` VARCHAR(64) DEFAULT NULL COMMENT '浠诲姟缂栧彿锛堝啑浣欙紝鏂逛究鏌ヨ锛�',
+ `from_status` VARCHAR(32) DEFAULT NULL COMMENT '鍙樻洿鍓嶇姸鎬佺爜锛圢ULL琛ㄧず鍒濆鍒涘缓锛�',
+ `from_status_name` VARCHAR(64) DEFAULT NULL COMMENT '鍙樻洿鍓嶇姸鎬佸悕绉�',
+ `to_status` VARCHAR(32) NOT NULL COMMENT '鍙樻洿鍚庣姸鎬佺爜',
+ `to_status_name` VARCHAR(64) DEFAULT NULL COMMENT '鍙樻洿鍚庣姸鎬佸悕绉�',
+ `change_reason` VARCHAR(500) DEFAULT NULL COMMENT '鍙樻洿鍘熷洜/澶囨敞',
+ `change_source` VARCHAR(32) DEFAULT 'APP' COMMENT '瑙﹀彂鏉ユ簮锛欰PP-绉诲姩绔紝ADMIN-绠$悊鍚庡彴锛孲YSTEM-绯荤粺鑷姩锛孡EGACY-鏃х郴缁熷悓姝�',
+ `operator_id` BIGINT DEFAULT NULL COMMENT '鎿嶄綔浜篒D',
+ `operator_name` VARCHAR(64) DEFAULT NULL COMMENT '鎿嶄綔浜哄鍚�',
+ `change_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '鍙樻洿鏃堕棿',
+ `longitude` DOUBLE DEFAULT NULL COMMENT '鎿嶄綔鏃剁殑缁忓害锛圙PS瀹氫綅锛�',
+ `latitude` DOUBLE DEFAULT NULL COMMENT '鎿嶄綔鏃剁殑绾害锛圙PS瀹氫綅锛�',
+ `location_address` VARCHAR(255) DEFAULT NULL COMMENT '鎿嶄綔鏃剁殑浣嶇疆鍦板潃',
+ `ip_address` VARCHAR(128) DEFAULT NULL COMMENT '鎿嶄綔IP鍦板潃',
+ `remark` VARCHAR(500) DEFAULT NULL COMMENT '澶囨敞',
+ PRIMARY KEY (`id`),
+ INDEX `idx_task_id` (`task_id`),
+ INDEX `idx_task_code` (`task_code`),
+ INDEX `idx_to_status` (`to_status`),
+ INDEX `idx_change_time`(`change_time`),
+ INDEX `idx_operator_id`(`operator_id`),
+ CONSTRAINT `fk_status_history_task` FOREIGN KEY (`task_id`) REFERENCES `sys_task`(`task_id`) ON DELETE CASCADE
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='浠诲姟鐘舵�佸彉鏇村巻鍙茶褰曡〃';
--
Gitblit v1.9.1