wlzboy
2025-12-06 5d75fcaea0a3774052b7484a4ffe755258502363
fix:开始执行人员就绪按钮
1个文件已添加
8个文件已修改
450 ■■■■■ 已修改文件
app/api/task.js 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/config.js 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/pagesTask/detail.vue 266 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-admin/src/main/java/com/ruoyi/web/controller/task/SysTaskController.java 18 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-system/src/main/java/com/ruoyi/system/domain/SysTaskAssignee.java 25 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-system/src/main/java/com/ruoyi/system/service/ISysTaskService.java 18 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysTaskServiceImpl.java 92 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-system/src/main/resources/mapper/system/SysTaskAssigneeMapper.xml 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
sql/add_assignee_ready_status.sql 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/api/task.js
@@ -149,3 +149,11 @@
    method: 'get'
  })
}
// 标记执行人就绪
export function setAssigneeReady(taskId) {
  return request({
    url: '/task/' + taskId + '/assignee/ready',
    method: 'post'
  })
}
app/config.js
@@ -59,5 +59,11 @@
        url: "/pages/mine/user-agreement/index"
      }
    ]
  },
  // 功能开关
  features: {
    // 是否显示执行人“就绪”按钮
    showAssigneeReadyButton: true
  }
}
app/pagesTask/detail.vue
@@ -59,6 +59,15 @@
                >
                  {{ getUserTypeLabel(assignee.userType) }}
                </view>
                <view
                  class="ready-badge"
                  :class="{
                    'ready': isAssigneeReady(assignee),
                    'unready': !isAssigneeReady(assignee)
                  }"
                >
                  {{ isAssigneeReady(assignee) ? '已就绪' : '未就绪' }}
                </view>
              </view>
            </view>
          </view>
@@ -362,18 +371,27 @@
        >
          修改
        </button>
        <button
          class="action-btn primary"
          @click="handleTaskAction('depart')"
        >
          出发
        </button>
        <button
          class="action-btn cancel"
          @click="handleTaskAction('cancel')"
        >
          取消
        </button>
        <template v-if="isCurrentUserAssignee()">
          <button
            v-if="showAssigneeReadyFeature() && isMultipleAssignees() && !isCurrentUserReady()"
            class="action-btn primary"
            @click="handleReadyAction()"
          >
            就绪
          </button>
          <button
            class="action-btn primary"
            @click="handleDepartAction()"
          >
            出发
          </button>
          <button
            class="action-btn cancel"
            @click="handleTaskAction('cancel')"
          >
            取消
          </button>
        </template>
      </template>
      
      <!-- 出发中状态: 显示编辑、已到达、强制结束 -->
@@ -384,18 +402,20 @@
        >
          修改
        </button>
        <button
          class="action-btn primary"
          @click="handleTaskAction('arrive')"
        >
          已到达
        </button>
        <button
          class="action-btn cancel"
          @click="handleTaskAction('forceCancel')"
        >
          强制结束
        </button>
        <template v-if="isCurrentUserAssignee()">
          <button
            class="action-btn primary"
            @click="handleTaskAction('arrive')"
          >
            已到达
          </button>
          <button
            class="action-btn cancel"
            @click="handleTaskAction('forceCancel')"
          >
            强制结束
          </button>
        </template>
      </template>
      
      <!-- 已到达状态: 显示编辑、已返程 -->
@@ -406,12 +426,14 @@
        >
          修改
        </button>
        <button
          class="action-btn primary"
          @click="handleTaskAction('return')"
        >
          已返程
        </button>
        <template v-if="isCurrentUserAssignee()">
          <button
            class="action-btn primary"
            @click="handleTaskAction('return')"
          >
            已返程
          </button>
        </template>
      </template>
      
      <!-- 返程中状态: 显示编辑、已完成 -->
@@ -422,12 +444,14 @@
        >
          修改
        </button>
        <button
          class="action-btn primary"
          @click="handleTaskAction('complete')"
        >
          已完成
        </button>
        <template v-if="isCurrentUserAssignee()">
          <button
            class="action-btn primary"
            @click="handleTaskAction('complete')"
          >
            已完成
          </button>
        </template>
      </template>
      
      <!-- 已完成/已取消: 不显示按钮,但如果是转运任务则显示结算按钮 -->
@@ -445,12 +469,13 @@
</template>
<script>
  import { getTask, changeTaskStatus } from '@/api/task'
  import { getTask, changeTaskStatus, setAssigneeReady } from '@/api/task'
  import { checkVehicleActiveTasks } from '@/api/task'
  import { getPaymentInfo } from '@/api/payment'
  import { formatDateTime } from '@/utils/common'
  import { validateTaskForDepart, validateTaskForSettlement, getTaskVehicleId, checkTaskCanDepart } from '@/utils/taskValidator'
  import AttachmentUpload from './components/AttachmentUpload.vue'
  import config from '@/config'
  
  export default {
    components: {
@@ -771,8 +796,7 @@
      handleTaskAction(action) {
        switch (action) {
          case 'depart':
            // 出发 -> 检查车辆是否有其他正在进行中的任务
            this.checkVehicleAndDepart();
            this.ensureReadyThenDepart();
            break;
            
          case 'cancel':
@@ -1235,6 +1259,145 @@
      // 附件删除成功回调
      onAttachmentDeleted(attachmentId) {
        console.log('附件删除成功:', attachmentId)
      },
      // 是否显示“就绪”功能(配置开关)
      showAssigneeReadyFeature() {
        return !!(config && config.features && config.features.showAssigneeReadyButton)
      },
      // 当前用户是否为该执行人
      isAssigneeSelf(assignee) {
        const userId = this.$store && this.$store.state && this.$store.state.user && this.$store.state.user.userId
        return assignee && (assignee.userId === userId || assignee.oaUserId === userId)
      },
      // 执行人点击“就绪”
      markAssigneeReady(assignee) {
        if (!assignee || !this.taskDetail) {
          this.$modal.showToast('执行人或任务信息不存在')
          return
        }
        const userId = assignee.userId || assignee.oaUserId
        if (!userId) {
          this.$modal.showToast('无法识别执行人ID')
          return
        }
        this.$modal.showLoading && this.$modal.showLoading('提交中...')
        setAssigneeReady(this.taskId).then(() => {
          this.$modal.hideLoading && this.$modal.hideLoading()
          this.$modal.showToast('已就绪')
          // 刷新任务详情
          this.loadTaskDetail()
        }).catch(err => {
          this.$modal.hideLoading && this.$modal.hideLoading()
          console.error('标记就绪失败:', err)
          this.$modal.showToast('标记就绪失败')
        })
      },
      // 是否当前用户是任务执行人
      isCurrentUserAssignee() {
        const userId = this.$store && this.$store.state && this.$store.state.user && this.$store.state.user.userId;
        console.log("当前用户ID:", userId)
        const list = (this.taskDetail && Array.isArray(this.taskDetail.assignees)) ? this.taskDetail.assignees : []
        return list.some(a => a && (a.userId === userId || a.oaUserId === userId))
      },
      // 是否多人执行
      isMultipleAssignees() {
        const list = (this.taskDetail && Array.isArray(this.taskDetail.assignees)) ? this.taskDetail.assignees : []
        return list.length > 1
      },
      // 执行人是否已就绪
      isAssigneeReady(assignee) {
        if (!assignee) return false
        return assignee.isReady === '1' || assignee.ready === true || assignee.readyStatus === 'READY' || assignee.readyFlag === 'Y'
      },
      // 所有执行人是否已就绪
      areAllAssigneesReady() {
        const list = (this.taskDetail && Array.isArray(this.taskDetail.assignees)) ? this.taskDetail.assignees : []
        if (list.length === 0) return false
        return list.every(a => this.isAssigneeReady(a))
      },
      // 获取当前用户对应的执行人记录
      getCurrentUserAssignee() {
        const userId = this.$store && this.$store.state && this.$store.state.user && this.$store.state.user.userId
        console.log('userId', userId)
        const list = (this.taskDetail && Array.isArray(this.taskDetail.assignees)) ? this.taskDetail.assignees : []
        return list.find(a => a && (a.userId === userId || a.oaUserId === userId)) || null
      },
      // 操作区就绪按钮(多人任务)
      markCurrentAssigneeReady() {
        const me = this.getCurrentUserAssignee()
        if (!me) {
          this.$modal.showToast('仅任务执行人可操作')
          return
        }
        this.markAssigneeReady(me)
      },
      // 当前用户是否已就绪
      isCurrentUserReady() {
        const me = this.getCurrentUserAssignee()
        return me ? this.isAssigneeReady(me) : false
      },
      // 处理就绪按钮点击
      async handleReadyAction() {
        const me = this.getCurrentUserAssignee()
        if (!me) {
          this.$modal.showToast('仅任务执行人可操作')
          return
        }
        try {
          await setAssigneeReady(this.taskId)
          this.$modal.showToast('已就绪')
          // 刷新任务详情
          await this.loadTaskDetail()
        } catch (err) {
          console.error('标记就绪失败:', err)
          this.$modal.showToast('标记就绪失败')
        }
      },
      // 处理出发按钮点击
      async handleDepartAction() {
        if (!this.taskDetail) return
        const list = (this.taskDetail && Array.isArray(this.taskDetail.assignees)) ? this.taskDetail.assignees : []
        // 如果开启了就绪功能且是多人任务,需要检查所有人是否就绪
        if (this.showAssigneeReadyFeature() && list.length > 1) {
          if (!this.areAllAssigneesReady()) {
            this.$modal.showToast('其他人未就绪,所有人就绪后才能出发')
            return
          }
        }
        // 单人任务或未开启就绪功能:自动标记就绪
        if (this.showAssigneeReadyFeature() && list.length === 1) {
          const me = this.getCurrentUserAssignee()
          if (me && !this.isAssigneeReady(me)) {
            try {
              await setAssigneeReady(this.taskId)
            } catch (e) {
              console.error('自动就绪失败:', e)
            }
          }
        }
        // 执行出发流程
        this.checkVehicleAndDepart()
      },
      // 出发前保证就绪(保留向后兼容)
      async ensureReadyThenDepart() {
        this.handleDepartAction()
      }
    }
  }
@@ -1383,6 +1546,31 @@
                  background-color: #AF52DE;
                }
              }
              .assignee-ready-btn {
                margin-left: 12rpx;
                padding: 8rpx 16rpx;
                font-size: 24rpx;
                border-radius: 6rpx;
                background-color: #34C759;
                color: #fff;
                border: none;
              }
              .ready-badge {
                display: inline-block;
                margin-left: 12rpx;
                padding: 4rpx 12rpx;
                font-size: 22rpx;
                border-radius: 6rpx;
                &.ready {
                  background-color: #e6ffed;
                  color: #34C759;
                }
                &.unready {
                  background-color: #f0f0f0;
                  color: #999;
                }
              }
            }
          }
        }
ruoyi-admin/src/main/java/com/ruoyi/web/controller/task/SysTaskController.java
@@ -353,6 +353,24 @@
    }
    /**
     * 执行人点击就绪(APP端)
     */
    @PostMapping("/{taskId}/assignee/ready")
    public AjaxResult setAssigneeReady(@PathVariable Long taskId) {
        Long userId = getUserId();
        return sysTaskService.setAssigneeReady(taskId, userId);
    }
    /**
     * 执行人取消就绪(APP端)
     */
    @PostMapping("/{taskId}/assignee/cancel-ready")
    public AjaxResult cancelAssigneeReady(@PathVariable Long taskId) {
        Long userId = getUserId();
        return sysTaskService.cancelAssigneeReady(taskId, userId);
    }
    /**
     * 分配任务请求对象
     */
    public static class AssignTaskRequest {
ruoyi-system/src/main/java/com/ruoyi/system/domain/SysTaskAssignee.java
@@ -39,6 +39,15 @@
    @Excel(name = "是否为主要执行人", readConverterExp = "0=否,1=是")
    private String isPrimary;
    /** 是否已就绪:0-未就绪,1-已就绪 */
    @Excel(name = "是否已就绪", readConverterExp = "0=未就绪,1=已就绪")
    private String isReady;
    /** 就绪时间 */
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    @Excel(name = "就绪时间", width = 30, dateFormat = "yyyy-MM-dd HH:mm:ss")
    private Date readyTime;
    /** 排序顺序(用于确定领队) */
    @Excel(name = "排序顺序")
    private Integer sortOrder;
@@ -98,4 +107,20 @@
    public void setSortOrder(Integer sortOrder) {
        this.sortOrder = sortOrder;
    }
    public String getIsReady() {
        return isReady;
    }
    public void setIsReady(String isReady) {
        this.isReady = isReady;
    }
    public Date getReadyTime() {
        return readyTime;
    }
    public void setReadyTime(Date readyTime) {
        this.readyTime = readyTime;
    }
}
ruoyi-system/src/main/java/com/ruoyi/system/service/ISysTaskService.java
@@ -284,4 +284,22 @@
     */
    public com.ruoyi.common.core.domain.AjaxResult checkTaskCanDepart(Long taskId);
    /**
     * 执行人点击就绪
     *
     * @param taskId 任务ID
     * @param userId 用户ID
     * @return 结果
     */
    public com.ruoyi.common.core.domain.AjaxResult setAssigneeReady(Long taskId, Long userId);
    /**
     * 取消执行人就绪
     *
     * @param taskId 任务ID
     * @param userId 用户ID
     * @return 结果
     */
    public com.ruoyi.common.core.domain.AjaxResult cancelAssigneeReady(Long taskId, Long userId);
}
ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysTaskServiceImpl.java
@@ -36,6 +36,7 @@
import com.ruoyi.system.service.ISysTaskEmergencyService;
import com.ruoyi.system.service.ITaskAttachmentSyncService;
import com.ruoyi.system.service.IMapService;
import com.ruoyi.system.service.ISysConfigService;
import com.ruoyi.system.event.TaskCreatedEvent;
import com.ruoyi.system.event.TaskAssignedEvent;
import com.ruoyi.system.event.TaskStatusChangedEvent;
@@ -99,6 +100,9 @@
    @Autowired(required = false)
    private IMapService mapService;
    @Autowired
    private ISysConfigService configService;
    /**
     * 查询任务管理
@@ -2344,6 +2348,22 @@
            }
        }
        
        // 3. 检查执行人是否全部就绪(受配置开关控制)
        String readyCheckEnabled = configService.selectConfigByKey("task.assignee.ready.check.enabled");
        if ("true".equalsIgnoreCase(readyCheckEnabled)) {
             assignees = task.getAssignees();
            if (assignees != null && !assignees.isEmpty()) {
                boolean allReady = assignees.stream()
                    .allMatch(a -> "1".equals(a.getIsReady()));
                if (!allReady) {
                    Map<String, Object> conflict = new HashMap<>();
                    conflict.put("type", "assigneeReady");
                    conflict.put("message", "存在未就绪的执行人,请等待所有执行人点击就绪后再出车");
                    conflicts.add(conflict);
                }
            }
        }
        // 返回结果
        Map<String, Object> result = new HashMap<>();
        result.put("valid", conflicts.isEmpty());
@@ -2351,5 +2371,77 @@
        
        return com.ruoyi.common.core.domain.AjaxResult.success(result);
    }
    /**
     * 执行人点击就绪
     *
     * @param taskId 任务ID
     * @param userId 用户ID
     * @return 结果
     */
    @Override
    @Transactional
    public com.ruoyi.common.core.domain.AjaxResult setAssigneeReady(Long taskId, Long userId) {
        // 1. 查询执行人关联信息
        List<SysTaskAssignee> assignees = sysTaskAssigneeMapper.selectSysTaskAssigneeByTaskId(taskId);
        SysTaskAssignee targetAssignee = assignees.stream()
            .filter(a -> a.getUserId().equals(userId))
            .findFirst()
            .orElse(null);
        if (targetAssignee == null) {
            return com.ruoyi.common.core.domain.AjaxResult.error("您不是该任务的执行人");
        }
        // 2. 更新就绪状态
        targetAssignee.setIsReady("1");
        targetAssignee.setReadyTime(new Date());
        targetAssignee.setUpdateBy(SecurityUtils.getUsername());
        targetAssignee.setUpdateTime(new Date());
        sysTaskAssigneeMapper.updateSysTaskAssignee(targetAssignee);
        // 3. 检查是否所有执行人都已就绪
        boolean allReady = assignees.stream()
            .allMatch(a -> a.getUserId().equals(userId) || "1".equals(a.getIsReady()));
        Map<String, Object> result = new HashMap<>();
        result.put("allReady", allReady);
        result.put("message", "就绪成功");
        return com.ruoyi.common.core.domain.AjaxResult.success(result);
    }
    /**
     * 取消执行人就绪
     *
     * @param taskId 任务ID
     * @param userId 用户ID
     * @return 结果
     */
    @Override
    @Transactional
    public com.ruoyi.common.core.domain.AjaxResult cancelAssigneeReady(Long taskId, Long userId) {
        // 查询执行人关联信息
        List<SysTaskAssignee> assignees = sysTaskAssigneeMapper.selectSysTaskAssigneeByTaskId(taskId);
        SysTaskAssignee targetAssignee = assignees.stream()
            .filter(a -> a.getUserId().equals(userId))
            .findFirst()
            .orElse(null);
        if (targetAssignee == null) {
            return com.ruoyi.common.core.domain.AjaxResult.error("您不是该任务的执行人");
        }
        // 更新就绪状态
        targetAssignee.setIsReady("0");
        targetAssignee.setReadyTime(null);
        targetAssignee.setUpdateBy(SecurityUtils.getUsername());
        targetAssignee.setUpdateTime(new Date());
        sysTaskAssigneeMapper.updateSysTaskAssignee(targetAssignee);
        return com.ruoyi.common.core.domain.AjaxResult.success("已取消就绪");
    }
   
}
ruoyi-system/src/main/resources/mapper/system/SysTaskAssigneeMapper.xml
@@ -11,6 +11,8 @@
        <result property="userName"    column="user_name"    />
        <result property="userType"    column="user_type"    />
        <result property="isPrimary"    column="is_primary"    />
        <result property="isReady"    column="is_ready"    />
        <result property="readyTime"    column="ready_time"    />
        <result property="sortOrder"    column="sort_order"    />
        <result property="createTime"    column="create_time"    />
        <result property="createBy"    column="create_by"    />
@@ -19,7 +21,7 @@
    </resultMap>
    <sql id="selectSysTaskAssigneeVo">
        select id, task_id, user_id, user_name, user_type, is_primary, sort_order, create_time, create_by, update_time, update_by
        select id, task_id, user_id, user_name, user_type, is_primary, is_ready, ready_time, sort_order, create_time, create_by, update_time, update_by
        from sys_task_assignee
    </sql>
@@ -78,6 +80,8 @@
            <if test="userName != null and userName != ''">user_name = #{userName},</if>
            <if test="userType != null and userType != ''">user_type = #{userType},</if>
            <if test="isPrimary != null">is_primary = #{isPrimary},</if>
            <if test="isReady != null">is_ready = #{isReady},</if>
            <if test="readyTime != null">ready_time = #{readyTime},</if>
            <if test="sortOrder != null">sort_order = #{sortOrder},</if>
            <if test="updateTime != null">update_time = #{updateTime},</if>
            <if test="updateBy != null and updateBy != ''">update_by = #{updateBy},</if>
sql/add_assignee_ready_status.sql
New file
@@ -0,0 +1,11 @@
-- 为执行人表添加就绪状态字段
ALTER TABLE sys_task_assignee
ADD COLUMN is_ready CHAR(1) DEFAULT '0' COMMENT '是否已就绪(0未就绪 1已就绪)' AFTER is_primary,
ADD COLUMN ready_time DATETIME COMMENT '就绪时间' AFTER is_ready;
-- 添加系统配置:是否启用执行人就绪检查
INSERT INTO sys_config (config_name, config_key, config_value, config_type, remark, create_by, create_time, update_by, update_time)
VALUES ('执行人就绪检查开关', 'task.assignee.ready.check.enabled', 'false', 'Y', '是否启用执行人就绪检查功能(true启用,false关闭)', 'admin', NOW(), 'admin', NOW());
-- 为已存在的执行人数据设置默认值(可选)
UPDATE sys_task_assignee SET is_ready = '0' WHERE is_ready IS NULL;