| | |
| | | <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;" /> |
| | |
| | | <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" |
| | |
| | | code: "", |
| | | uuid: '' |
| | | }, |
| | | // 协议区域高亮提示状态 |
| | | highlightAgreement: false, |
| | | // 微信一键登录相关 |
| | | isWechat: false, // 是否为微信小程序环境 |
| | | wechatOpenId: '', // 微信OpenID |
| | |
| | | this.wechatUnionId = savedUnionId // 可能为null |
| | | this.loginByOpenId() |
| | | } |
| | | }, |
| | | |
| | | // 未同意协议时点击手机号快捷登录的处理 |
| | | checkAgreementBeforePhone() { |
| | | this.$modal.msgError("请先阅读并同意用户协议和隐私政策") |
| | | // 滚动到协议区域(高亮提示) |
| | | this.highlightAgreement = true |
| | | setTimeout(() => { |
| | | this.highlightAgreement = false |
| | | }, 2000) |
| | | }, |
| | | |
| | | // 处理获取手机号的回调 |
| | |
| | | } |
| | | } |
| | | |
| | | .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; |
| | |
| | | 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; |
| | |
| | | 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; |
| | |
| | | |
| | | @Autowired |
| | | private ITaskStatusPushService taskStatusPushService; |
| | | |
| | | @Autowired |
| | | private SysTaskStatusHistoryMapper sysTaskStatusHistoryMapper; |
| | | |
| | | /** |
| | | * 查询任务管理列表(后台管理端) |
| | |
| | | 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()); |
| | | } |
| | | } |
| | | } |
| | |
| | | |
| | | // 状态流转规则 |
| | | switch (currentStatus) { |
| | | case NOT_CONFIRMED: |
| | | case NOT_DEPARTED: |
| | | case PARTIALLY_CONFIRMED: |
| | | case PENDING: |
| | | // 待处理 -> 出发中、已取消 |
| | | return newStatus == TaskStatus.DEPARTING || newStatus == TaskStatus.CANCELLED; |
| New file |
| | |
| | | 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; |
| | | |
| | | /** 变更前状态码(NULL表示初始创建) */ |
| | | @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-管理后台,SYSTEM-系统自动,LEGACY-旧系统同步 |
| | | */ |
| | | @Excel(name = "触发来源") |
| | | private String changeSource; |
| | | |
| | | /** 操作人ID */ |
| | | @Excel(name = "操作人ID") |
| | | 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; |
| | | |
| | | /** 操作时的经度(GPS定位) */ |
| | | private Double longitude; |
| | | |
| | | /** 操作时的纬度(GPS定位) */ |
| | | 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 + |
| | | '}'; |
| | | } |
| | | } |
| New file |
| | |
| | | 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); |
| | | } |
| | |
| | | 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; |
| | |
| | | |
| | | @Autowired |
| | | private SysTaskLogMapper sysTaskLogMapper; |
| | | |
| | | @Autowired |
| | | private SysTaskStatusHistoryMapper sysTaskStatusHistoryMapper; |
| | | |
| | | @Autowired |
| | | private SysTaskEmergencyMapper sysTaskEmergencyMapper; |
| | |
| | | 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()); |
| | |
| | | "状态:" + 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); |
| | | } |
| | | |
| | | // 发布任务状态变更事件 |
| | |
| | | } |
| | | |
| | | /** |
| | | * 记录任务状态变更历史 |
| | | * |
| | | * @param task 任务对象(取 task_id / task_code) |
| | | * @param fromStatus 变更前状态码 |
| | | * @param fromStatusName 变更前状态名称 |
| | | * @param toStatus 变更后状态码 |
| | | * @param toStatusName 变更后状态名称 |
| | | * @param changeReason 变更原因/备注 |
| | | * @param changeSource 触发来源(APP / 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 任务对象 |
| | |
| | | } |
| | | } |
| | | |
| | | 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); |
| | | } |
| | | /** |
| | | * 检查任务是否可以出发 |
| | | * 检查: |
| | |
| | | */ |
| | | @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(); |
| | |
| | | } |
| | | } |
| | | |
| | | // 返回结果 |
| | | Map<String, Object> result = new HashMap<>(); |
| | | |
| | | result.put("valid", conflicts.isEmpty()); |
| | | result.put("conflicts", conflicts); |
| | | |
| New file |
| | |
| | | <?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> |
| | | |
| | | <!-- 按任务ID查询历史,时间升序 --> |
| | | <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> |
| | |
| | | method: 'get' |
| | | }) |
| | | } |
| | | |
| | | // 查询任务状态变更历史 |
| | | export function getTaskStatusHistory(taskId) { |
| | | return request({ |
| | | url: '/task/' + taskId + '/statusHistory', |
| | | method: 'get' |
| | | }) |
| | | } |
| | |
| | | </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"> |
| | |
| | | </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"; |
| | | |
| | |
| | | additionalFeeList: [], |
| | | // 支付信息 |
| | | paymentInfo: null, |
| | | // 状态变更历史 |
| | | statusHistoryList: [], |
| | | // 上传相关 |
| | | uploadUrl: process.env.VUE_APP_BASE_API + "/task/attachment/upload/" + (new URLSearchParams(window.location.search).get('taskId') || ''), |
| | | uploadHeaders: { |
| | |
| | | this.getTaskDetail(); |
| | | this.getUserList(); |
| | | this.getAdditionalFeeList(); |
| | | this.loadStatusHistory(); |
| | | // 初始化上传URL |
| | | this.uploadUrl = process.env.VUE_APP_BASE_API + "/task/attachment/upload/" + this.$route.params.taskId; |
| | | // 检查发票申请状态 |
| | |
| | | } |
| | | }, |
| | | 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 => { |
| | |
| | | .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> |
| New file |
| | |
| | | -- ============================================= |
| | | -- 任务状态变更历史记录表 |
| | | -- 专门记录任务状态的每一次流转,便于审计与追溯 |
| | | -- ============================================= |
| | | |
| | | 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 '变更前状态码(NULL表示初始创建)', |
| | | `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 '触发来源:APP-移动端,ADMIN-管理后台,SYSTEM-系统自动,LEGACY-旧系统同步', |
| | | `operator_id` BIGINT DEFAULT NULL COMMENT '操作人ID', |
| | | `operator_name` VARCHAR(64) DEFAULT NULL COMMENT '操作人姓名', |
| | | `change_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '变更时间', |
| | | `longitude` DOUBLE DEFAULT NULL COMMENT '操作时的经度(GPS定位)', |
| | | `latitude` DOUBLE DEFAULT NULL COMMENT '操作时的纬度(GPS定位)', |
| | | `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='任务状态变更历史记录表'; |