无人机管理后台前端(已迁走)
张含笑
2025-11-19 f4548acea8b1e364aa08bcbbb5c448bb88c73476
feat:详情demo
2 files modified
584 ■■■■■ changed files
src/views/tickets/ticket.vue 548 ●●●●● patch | view | raw | blame | history
src/views/tickets/ticketComponent/TicketDetailDialog.vue 36 ●●●●● patch | view | raw | blame | history
src/views/tickets/ticket.vue
@@ -270,18 +270,18 @@
    </el-tabs>
    <!-- 新建工单对话框 -->
      <create-ticket-dialog
      v-model="dialogVisible"
      :departments="departments"
      :department-users="departmentUsers"
      :types="types"
      :all-algorithms="allAlgorithms"
      :edit-data="editFormData"
      :map-center="inputMapShowDefaultCenter"
      @create-success="handleCreateSuccess"
      @draft-success="handleDraftSuccess"
      @create-error="handleCreateError"
    />
   <create-ticket-dialog
  v-model="dialogVisible"
  :departments="departments"
  :department-users="departmentUsers"
  :types="types"
  :all-algorithms="allAlgorithms"
  :edit-data="editFormData"
  :map-center="inputMapShowDefaultCenter"
  @create-success="handleCreateSuccess"
  @draft-success="handleDraftSuccess"
  @create-error="handleCreateError"
/>
    <!-- 工单详情对话框 -->
<ticket-detail-dialog
@@ -448,7 +448,13 @@
  data() {
    return {
      currentIndex: null, // 当前显示的数据索引
      approveLoading: false,
      rejectLoading: false,
      dispatchLoading: false,
      completeLoading: false,
      finalizeLoading: false,
      createoredit: '',
      activeTab: 'all',
      // tabs 只保留静态结构,不做权限判断
@@ -552,7 +558,7 @@
      },
      dialogVisible: false,
       editFormData: null, // 专门用于存储编辑数据的对象
        createoredit: '', // 可以删除这个字段,因为组件内部会判断
      detailVisible: false,
      currentDetail: {},
    
@@ -568,6 +574,8 @@
      dispatchDepartment: '', // 新增:派发部门
      dispatchHandler: '', // 新增:派发处理人
      dispatchDialogVisible: false, // 新增:派发对话框可见性
      stepInfos: [], // 新增:存储步骤信息
      fixedStatuses: ['2', '0', '3', '4'], // 固定的五个状态
      userNameToIdMap: {}, // 新增用户名到ID的映射
      workType: 0, // 新增:当前工单work_type
@@ -575,7 +583,7 @@
      reviewDialogVisible: false, // 新增:审核对话框可见性
      currentReviewImage: '', // 新增:当前审核图片
      currentImageIndex: 1, // 新增:当前图片索引
      totalTime: '',
      isShowInfo: false,
      // 配置时间选择器默认配置
@@ -939,9 +947,21 @@
        });
      }
    },
     // 上一项
    handlePrevItem() {
      this.currentIndex = Math.max(0, this.currentIndex - 1);
      this.updateCurrentDetail();
    },
    // 下一项
    handleNextItem() {
      this.currentIndex = Math.min(this.tableData.length - 1, this.currentIndex + 1);
      this.updateCurrentDetail();
    },
 
    // 更新当前详情
      updateCurrentDetail() {
    updateCurrentDetail() {
      this.currentDetail = this.tableData[this.currentIndex];
      this.currentDetail.mediaUrl = this.currentDetail.photo_url;
      this.currentDetail.updatePhotoUrl = this.currentDetail.update_photo_url;
@@ -949,10 +969,12 @@
      this.currentDetail.showQR = false;
      this.currentDetail.latAndLon = _.round(this.currentDetail.location[0], 6) + ',' + _.round(this.currentDetail.location[1], 6);
    },
     // 更新当前详情数据
    // 更新当前详情数据
    handleCurrentDetailUpdate(updatedDetail) {
      this.currentDetail = updatedDetail;
    },
    // 受理并派发
    handleApproveAndDispatch(detail) {
      this.currentDetail = detail;
@@ -992,64 +1014,7 @@
        this.$message.error('地图加载失败,请检查网络或API Key配置');
      }
    },
    async handleBatchApprove() {
      try {
        if (this.selections.length === 0) {
          this.$message.warning('没有选中的工单');
          return;
        }
        const currentItem = this.selections[this.currentImageIndex - 1];
        if (!currentItem) {
          this.$message.warning('当前工单数据无效');
          return;
        }
        await this.$confirm('确认审核通过当前工单?', '提示', {
          confirmButtonText: '确定',
          cancelButtonText: '取消',
          type: 'warning',
        });
        const data = {
          id: currentItem.id,
          status: currentItem.status,
          isPass: 0,
          eventNum: currentItem.orderNumber,
          eventName: currentItem.orderName,
        };
        const response = await flowEvent(data);
        if (response.data.code === 0) {
          this.$message.success('工单审核通过');
          // 创建新的数组而不是修改原数组
          const newSelections = [...this.selections];
          newSelections.splice(this.currentImageIndex - 1, 1);
          this.selections = newSelections;
          // 修正索引并更新图片
          if (this.selections.length > 0) {
            if (this.currentImageIndex > this.selections.length) {
              this.currentImageIndex = this.selections.length;
            }
            this.updateCurrentReviewImage();
          } else {
            this.reviewDialogVisible = false;
            this.currentImageIndex = 1;
            this.currentReviewImage = '';
          }
          // 刷新表格数据
          this.fetchTableData();
        } else {
          throw new Error(response.data.msg || '审核失败');
        }
      } catch (error) {
        if (error === 'cancel') return;
        this.$message.error(error.message || '审核失败,请稍后重试');
      }
    },
    async fetchDropdownData() {
      try {
        const response = await getTicketInfo();
@@ -1334,25 +1299,92 @@
      }
      return `${location[0].toFixed(6)}, ${location[1].toFixed(6)}`;
    },
    handleViewDetail(row) {
    async handleViewDetail(row) {
      // 找到当前行在tableData中的索引
      this.currentIndex = this.tableData.findIndex(item => item.id === row.id);
      // 先设置workType,直接从row读取
      this.workType = row.work_type !== undefined ? Number(row.work_type) : 0;
      // 重置上传组件的文件列表
      this.$nextTick(() => {
        if (this.$refs.MapContainer && this.$refs.MapContainer.initAddEntity) {
          this.$refs.MapContainer.initAddEntity('point', this.currentDetail.location);
        }
      });
      const detailData = {
        ...row,
        processingDetail: row.processing_details || '',
        mediaUrl: row.photo_url || row.video_url || '',
        updatePhotoUrl: row.update_photo_url || '',
        photos: [],
        job_name: row.job_name || '',
        showQR: false,
        latAndLon: _.round(row.location[0], 6) + ',' + _.round(row.location[1], 6),
        job_name: row.job_name || '', // 新增
      };
      this.currentDetail = detailData;
      let stepArr = [];
      try {
        const stepResponse = await getStepInfo(row.orderNumber);
        const steps = Array.isArray(stepResponse.data.data)
          ? stepResponse.data.data
          : stepResponse.data.data?.steps || [];
        const finishedStep = steps.find(s => String(s.status) === '4');
        this.totalTime = finishedStep && finishedStep.total_time ? finishedStep.total_time : '';
        if (this.activeTab !== 'myTickets') {
          this.stepInfos = steps.map(step => ({
            status: String(step.status),
            name: step.name,
            time: step.time,
            create_time: step.create_time,
          }));
        } else {
          const statusArr = this.workType === 1 ? ['3', '4'] : this.fixedStatuses;
          this.stepInfos = statusArr.map(status => {
            const step = steps.find(s => String(s.status) === String(status));
            return {
              status,
              name: step ? step.name : '',
              time: step ? step.time : null,
              create_time: step ? step.create_time : null,
            };
          });
          console.log('this.stepInfos', this.stepInfos);
        }
        this.currentDetail.status = row.status;
      } catch (error) {
        if (this.activeTab === 'myTickets') {
          const statusArr = this.workType === 1 ? ['3', '4'] : this.fixedStatuses;
          this.stepInfos = statusArr.map(status => ({
            status,
            name: status === row.status ? row.handler || '未分配' : '未处理',
            time: status === row.status ? row.startTime || '未知时间' : null,
          }));
        } else {
          this.stepInfos = [];
        }
      }
      console.log(detailData, 'detailDatadetailDatadetailData');
      this.currentDetail = {
        ...detailData,
        // address: null,
        showQR: false,
        latAndLon: _.round(detailData.location[0], 6) + ',' + _.round(detailData.location[1], 6),
      };
      console.log('this.currentDetail', this.currentDetail);
      this.detailVisible = true;
      this.$nextTick(() => {
        if (this.$refs.MapContainer && this.$refs.MapContainer.initAddEntity) {
          this.$refs.MapContainer.initAddEntity('point', this.currentDetail.location);
        }
      });
    },
    // 详情成功回调
 // 详情成功回调
    handleDetailSuccess() {
      this.fetchTableData();
    },
@@ -1361,20 +1393,6 @@
    handleDetailError(error) {
      console.error('工单操作失败:', error);
    },
   // 上一项
    handlePrevItem() {
      this.currentIndex = Math.max(0, this.currentIndex - 1);
      this.updateCurrentDetail();
    },
    // 下一项
    handleNextItem() {
      this.currentIndex = Math.min(this.tableData.length - 1, this.currentIndex + 1);
      this.updateCurrentDetail();
    },
    getStepHandler(status) {
      const step = this.stepInfos.find(step => step.status === status);
      return step ? step.name : '';
@@ -1486,8 +1504,57 @@
        工单状态: this.mapStatus(Number(item.status || 0)),
      };
    },
    async approveTicket() {
      if (this.approveLoading) return;
      this.approveLoading = true;
      try {
        const data = {
          id: this.currentDetail.id,
          status: this.currentDetail.status,
          isPass: 0, // 0 表示通过
          eventNum: this.currentDetail.orderNumber,
        };
        const file = this.currentDetail.file || null; // 如果没有文件,则为 null
        const response = await flowEvent(data, file);
        if (response.data.code === 0) {
          this.$message.success('工单已通过');
          this.detailVisible = false;
          this.fetchTableData();
        } else {
          throw new Error(response.data.msg || '操作失败');
        }
      } catch (error) {
        this.$message.error(error.message || '操作失败,请稍后重试');
      } finally {
        this.approveLoading = false;
      }
    },
    async rejectTicket() {
      if (this.rejectLoading) return;
      this.rejectLoading = true;
      try {
        const data = {
          id: this.currentDetail.id,
          status: this.currentDetail.status,
          isPass: 1, // 1 表示不通过
        };
        const response = await flowEvent(data);
        if (response.data.code === 0) {
          this.$message.success('工单未通过');
          this.detailVisible = false;
          this.fetchTableData();
        } else {
          throw new Error(response.data.msg || '操作失败');
        }
      } catch (error) {
        this.$message.error(error.message || '操作失败,请稍后重试');
      } finally {
        this.rejectLoading = false;
      }
    },
    async submitProcessing() {
      if (this.currentDetail.status !== 3) {
        this.$message.warning('只有处理中状态的工单可以提交处理详情');
@@ -1520,7 +1587,7 @@
    markAsCompleted() {
      this.$message.success('工单已标记为完成');
    },
    async approveAndDispatch() {
      if (this.dispatchLoading) return;
@@ -1554,10 +1621,6 @@
      // 可以在这里处理错误,或者保持空实现
      console.error('派发失败:', error);
    },
    hasProcessingBtnPermission() {
      // undefined 或 false 都返回 false,只有 true 返回 true
      //  console.log('权限检查:', this.permission.tickets_processing_btn)
@@ -1573,8 +1636,6 @@
      // console.log('权限检查:', this.permission)
      return this.permission && this.permission.tickets_review_btn === true;
    },
    async finalizeTicket() {
      if (this.finalizeLoading) return;
      this.finalizeLoading = true;
@@ -1669,11 +1730,6 @@
    // 添加全选方法
    handleSelectAll(val) {
      this.$refs.avueCrud.toggleSelection(val);
    },
    // 添加单行选择方法
    handleSelect(selection, row) {
      console.log('选中行变化:', selection, row);
    },
    // 如果需要手动选中某些行
@@ -2057,7 +2113,10 @@
  margin-bottom: 32px;
}
.tableCss {
  width: 100%;
  margin-bottom: 10px;
}
.step-timer {
  position: absolute;
@@ -2070,7 +2129,12 @@
  font-size: 12px;
}
.event-total-time {
  text-align: center;
  color: #666;
  font-size: 15px;
  margin-bottom: 12px;
}
.action-bar {
  margin-bottom: 16px;
@@ -2102,7 +2166,103 @@
  margin-top: 12px;
}
.create-ticket-form {
  padding: 20px 10px;
  .form-section {
    background-color: #fff;
    border-radius: 4px;
    .el-row {
      margin-bottom: 16px;
      &:last-child {
        margin-bottom: 0;
      }
    }
  }
  .location-wrapper {
    width: 100%; // 修改为100%以适应父容器
    height: auto; // 修改为auto以自适应内容
    .map-button {
      width: 100%; // 让按钮填满容器宽度
      height: 36px; // 与其他输入框保持一致的高度
      display: flex;
      align-items: center;
      justify-content: center;
      i {
        margin-right: 4px;
      }
    }
    .location-text {
      margin-top: 8px;
      padding: 8px 12px;
      background-color: #f5f7fa;
      border-radius: 4px;
      color: #606266;
      font-size: 13px;
      line-height: 1.4;
    }
  }
  .upload-wrapper {
    .uploader {
      :deep(.el-upload--picture-card) {
        width: 120px;
        height: 100px;
        line-height: 128px;
      }
      :deep(.el-upload-list__item) {
        width: 120px;
        height: 120px;
      }
    }
    .upload-tip {
      font-size: 12px;
      color: #909399;
      line-height: 1.4;
      margin-top: 8px;
    }
  }
  .el-form-item {
    margin-bottom: 18px;
    &:last-child {
      margin-bottom: 0;
    }
  }
  :deep(.el-form-item__label) {
    font-weight: 500;
    color: #606266;
  }
  // :deep(.el-input__inner) {
  //   height: 36px;
  //   line-height: 36px;
  // }
  :deep(.el-textarea__inner) {
    padding: 8px 12px;
  }
  // :deep(.el-input__inner),
  // :deep(.el-select),
  // :deep(.el-select .el-input) {
  //   width: 100%; // 确保所有输入框和选择框宽度一致
  // }
  .full-width {
    width: 100%;
  }
}
:deep(.el-dialog__body) {
  border-top: 0.1rem solid #f0f0f0;
@@ -2244,65 +2404,65 @@
  }
}
// .status-flow {
//   margin-bottom: 20px;
.status-flow {
  margin-bottom: 20px;
//   .custom-steps {
//     .el-step__description {
//       position: relative;
//       margin: 0;
//       padding: 0;
//     }
  .custom-steps {
    .el-step__description {
      position: relative;
      margin: 0;
      padding: 0;
    }
//     // 添加发起任务步骤的特殊样式
//     .init-step-info {
//       display: flex;
//       flex-direction: column;
//       align-items: center;
//       text-align: center;
//       width: 130px;
    // 添加发起任务步骤的特殊样式
    .init-step-info {
      display: flex;
      flex-direction: column;
      align-items: center;
      text-align: center;
      width: 130px;
//       .creator-name {
//         font-size: 14px;
//         font-weight: bold;
//         color: #303133;
//         margin-bottom: 4px;
//       }
      .creator-name {
        font-size: 14px;
        font-weight: bold;
        color: #303133;
        margin-bottom: 4px;
      }
//       .create-time {
//         font-size: 10px;
//         color: #909399;
//       }
//     }
      .create-time {
        font-size: 10px;
        color: #909399;
      }
    }
//     // 保持其他步骤的原有样式
//     .step-info {
//       display: flex;
//       margin: 0;
//       padding: 0;
//       justify-content: space-between;
//       align-items: center;
//       width: 200px;
    // 保持其他步骤的原有样式
    .step-info {
      display: flex;
      margin: 0;
      padding: 0;
      justify-content: space-between;
      align-items: center;
      width: 200px;
//       .process-time {
//         font-size: 10px;
//         color: #909399;
//         text-align: left;
//         margin-left: -50%;
//         padding-left: 0%;
//         flex: 1;
//       }
      .process-time {
        font-size: 10px;
        color: #909399;
        text-align: left;
        margin-left: -50%;
        padding-left: 0%;
        flex: 1;
      }
//       .handler-name {
//         font-size: 14px;
//         font-weight: bold;
//         color: #303133;
//         text-align: left;
//         flex: 1;
//       }
//     }
//   }
// }
      .handler-name {
        font-size: 14px;
        font-weight: bold;
        color: #303133;
        text-align: left;
        flex: 1;
      }
    }
  }
}
.basic-info {
  margin-bottom: 20px;
@@ -2336,7 +2496,11 @@
  opacity: 0.3 !important;
}
.PopUpTableScrolls {
  max-height: 600px;
  overflow-y: scroll;
  overflow-x: hidden;
}
.media-box {
  width: 100%;
@@ -2421,22 +2585,7 @@
  word-break: break-word;
}
.readonly-processing-detail {
  background-color: #f5f7fa;
  padding: 12px;
  border-radius: 4px;
  min-height: 30px;
  color: #606266;
  line-height: 1.5;
  display: block;
  text-align: left;
  margin-bottom: 5px;
  &:first-child {
    font-weight: bold;
    margin-bottom: 4px;
  }
}
// 添加删除按钮样式
.danger-button {
@@ -2447,9 +2596,39 @@
  color: #f78989;
}
.custom-steps-container {
  width: 100%;
  margin: 10px 0;
}
.steps-titles {
  display: flex;
  justify-content: space-between;
  margin-bottom: 14px;
  position: relative;
}
.step-title {
  text-align: center;
  flex: 1;
  font-size: 14px;
  color: #999;
  position: relative;
  padding-bottom: 5px;
}
.step-title.active {
  color: #409eff;
  font-weight: bold;
}
.event-title-center {
  text-align: center;
  font-size: 20px;
  font-weight: bold;
  margin-bottom: 5px;
  color: #333;
}
.custom-steps {
  margin-top: -20px;
@@ -2460,7 +2639,18 @@
  }
}
.step-description {
  font-size: 14px;
  color: #666;
  line-height: 1.5;
  display: block;
  text-align: center;
  &:first-child {
    font-weight: bold;
    margin-bottom: 4px;
  }
}
// 覆盖其他相关样式
.status-flow {
src/views/tickets/ticketComponent/TicketDetailDialog.vue
@@ -585,6 +585,8 @@
    getActiveStep() {
      const arr = this.stepStatusList;
      console.log('stepStatusList',this.stepStatusList);
      const index = arr.indexOf(String(this.currentDetail.status));
      return index !== -1 ? index + 2 : 1;
    },
@@ -754,14 +756,26 @@
  }
};
</script>
<style lang="scss" scoped>
<style lang="scss">
.custom-dialog {
  max-height: 96vh;
  max-height: 95vh;
  :deep(.el-dialog__body) {
    border-top: 0.1rem solid #f0f0f0;
  }
}
</style>
<style lang="scss" scoped>
.step-timer {
  position: absolute;
  right: 80%;
  top: 50%;
  transform: translateY(-50%);
  width: 100px;
  margin-left: 4px;
  color: #666;
  font-size: 12px;
}
.detail-container {
  padding: 0 20px;
@@ -1061,4 +1075,20 @@
  pointer-events: none;
  opacity: 0.3 !important;
}
.readonly-processing-detail {
  background-color: #f5f7fa;
  padding: 12px;
  border-radius: 4px;
  min-height: 30px;
  color: #606266;
  line-height: 1.5;
  display: block;
  text-align: left;
  margin-bottom: 5px;
  &:first-child {
    font-weight: bold;
    margin-bottom: 4px;
  }
}
</style>