无人机管理后台前端(已迁走)
shuishen
2025-04-15 df42fa7e97c4a93caa17b2aed5aecdc768d8bbd7
Merge branch 'master' of http://139.196.74.78:10010/r/drone/drone-web-manage
1 files modified
460 ■■■■■ changed files
src/views/tickets/ticket.vue 460 ●●●●● patch | view | raw | blame | history
src/views/tickets/ticket.vue
@@ -32,18 +32,10 @@
            @size-change="sizeChange" @current-change="handleCurrentChange" :table-loading="loading"
            @selection-change="handleSelectionChange">
            <template #menu-left>
              <el-button
                v-if="activeTab === 'all' || activeTab === 'myTickets'"
                type="primary"
                icon="el-icon-plus"
                @click="handleAdd"
              >新建工单</el-button>
              <el-button
    v-if="activeTab === 'pending'"
    type="success"
    icon="el-icon-check"
    @click="openReviewDialog"
  >批量审核</el-button>
              <el-button v-if="activeTab === 'all' || activeTab === 'myTickets'" type="primary" icon="el-icon-plus"
                @click="handleAdd">新建工单</el-button>
              <el-button v-if="activeTab === 'pending'" type="success" icon="el-icon-check"
                @click="openReviewDialog">批量审核</el-button>
              <el-button type="success" plain icon="el-icon-download" @click="exportData">导出</el-button>
            </template>
            <template #menu="{ row }">
@@ -89,14 +81,16 @@
          <el-row :gutter="16">
            <el-col :span="12">
              <el-form-item label="所属部门" prop="department">
                <el-select v-model="form.department" placeholder="请选择所属部门" @change="handleDepartmentChange" class="full-width">
                <el-select v-model="form.department" placeholder="请选择所属部门" @change="handleDepartmentChange"
                  class="full-width">
                  <el-option v-for="dept in departments" :key="dept.value" :label="dept.label" :value="dept.value" />
                </el-select>
              </el-form-item>
            </el-col>
            <el-col :span="12">
              <el-form-item label="处理人员" prop="handler">
                <el-select v-model="form.handler" placeholder="请先选择所属部门" :disabled="!form.department" class="full-width">
                <el-select v-model="form.handler" placeholder="请先选择所属部门" :disabled="!form.department"
                  class="full-width">
                  <el-option v-for="user in availableHandlers" :key="user.id" :label="user.name" :value="user.id" />
                </el-select>
              </el-form-item>
@@ -114,7 +108,8 @@
            <el-col :span="12">
              <el-form-item label="选择位置" prop="location" required>
                <div class="location-wrapper">
                  <avue-input-map v-model="form.location" :params="mapParams" @change="handleLocationChange" type="button">
                  <avue-input-map v-model="form.location" :params="mapParams" @change="handleLocationChange"
                    type="button">
                    <el-button type="primary" plain class="map-button">
                      <i class="el-icon-map-location"></i> 地图选点
                    </el-button>
@@ -133,18 +128,9 @@
            </el-col>
            <el-col :span="12">
              <el-form-item label="附件图片" prop="photos" required class="upload-wrapper">
                <el-upload ref="upload"
                  :action="'#'"
                  :auto-upload="false"
                  list-type="picture-card"
                  :on-change="handleFileChange"
                  :on-remove="handleUploadRemove"
                  :before-upload="beforeUpload"
                  :file-list="form.photos"
                  :limit="1"
                  accept="image/*"
                  class="uploader"
                >
                <el-upload ref="upload" :action="'#'" :auto-upload="false" list-type="picture-card"
                  :on-change="handleFileChange" :on-remove="handleUploadRemove" :before-upload="beforeUpload"
                  :file-list="form.photos" :limit="1" accept="image/*" class="uploader">
                  <i class="el-icon-plus"></i>
                </el-upload>
                <div class="upload-tip">支持jpg/png格式图片,单张不超过5MB</div>
@@ -169,18 +155,11 @@
        <div class="custom-steps-container">
          <!-- 标题行 -->
          <div class="steps-titles">
            <div
              class="step-title"
              :class="{ active: true }"
            >
            <div class="step-title" :class="{ active: true }">
              发起任务
            </div>
            <div
              v-for="(status, index) in dynamicFixedStatuses"
              :key="index"
              :class="{ active: Number(currentDetail.status) >= Number(status) }"
              class="step-title"
            >
            <div v-for="(status, index) in dynamicFixedStatuses" :key="index"
              :class="{ active: Number(currentDetail.status) >= Number(status) }" class="step-title">
              {{ mapStatus(status) }}
            </div>
          </div>
@@ -203,7 +182,7 @@
                  {{ getStepHandler(status) }}
                </span>
                <div class="step-description" v-if="getStepTime(status)">
                 <span style="         position: absolute;
                  <span style="         position: absolute;
          right: 80%;  
          top: 50%;
          transform: translateY(-50%);
@@ -212,7 +191,7 @@
          color: #666;
          font-size: 12px;">耗时:{{ getStepTime(status) }}</span>
                </div>
                <div class="step-description" >
                <div class="step-description">
                  {{ getStepCreateTime(status) }}
                </div>
              </template>
@@ -407,62 +386,49 @@
    </el-dialog>
    <!-- 添加在其他 dialog 组件之后 -->
<el-dialog v-model="reviewDialogVisible" title="批量审核" width="70%" append-to-body custom-class="review-dialog">
  <div class="review-container">
    <!-- 图片浏览区域 -->
    <div class="review-image-wrapper">
      <!-- 左箭头 -->
      <div v-show="selections.length > 1" class="arrow-button left" @click="handlePrevImage">
        <el-icon><ArrowLeft /></el-icon>
    <el-dialog v-model="reviewDialogVisible" title="批量审核" width="70%" append-to-body custom-class="review-dialog">
      <div class="review-container">
        <div class="review-image-wrapper">
          <!-- 修改左右箭头的显示条件 -->
          <div v-if="selections.length > 1 && currentReviewImage" class="arrow-button left" @click="handlePrevImage">
            <i class="el-icon-arrow-left"></i>
          </div>
          <div class="review-image-container">
            <el-image v-if="currentReviewImage" :src="currentReviewImage" fit="cover" :preview-src-list="getImageList()"
              :initial-index="currentImageIndex - 1" class="preview-image">
              <template #error>
                <div class="image-error">
                  <i class="el-icon-picture-outline"></i>
                  <span>图片加载失败</span>
                </div>
              </template>
            </el-image>
            <div v-else class="no-image">暂无图片</div>
          </div>
          <!-- 修改右箭头的显示条件 -->
          <div v-if="selections.length > 1 && currentReviewImage" class="arrow-button right" @click="handleNextImage">
            <i class="el-icon-arrow-right"></i>
          </div>
        </div>
        <!-- 修改分页器的显示条件 -->
        <div class="review-pagination" v-if="selections.length > 1 && currentReviewImage">
          <el-pagination small layout="prev, pager, next" hide-on-single-page :total="selections.length"
            :current-page="currentImageIndex" :page-size="1" @current-change="handleImagePageChange">
          </el-pagination>
        </div>
      </div>
      <!-- 图片显示区域 -->
      <div class="review-image-container">
        <el-image
          :src="currentReviewImage"
          fit="cover"
          :preview-src-list="getImageList()"
          :initial-index="currentImageIndex - 1"
          class="preview-image"
        >
          <template #error>
            <div class="image-error">
              <i class="el-icon-picture-outline"></i>
              <span>图片加载失败</span>
            </div>
          </template>
        </el-image>
      </div>
      <!-- 右箭头 -->
      <div v-show="selections.length > 1" class="arrow-button right" @click="handleNextImage">
        <el-icon><ArrowRight /></el-icon>
      </div>
    </div>
    <!-- 分页指示器 -->
    <div class="review-pagination" v-if="selections.length > 1">
      <el-pagination
        small
        layout="prev, pager, next"
        hide-on-single-page
        :total="selections.length"
        :current-page.sync="currentImageIndex"
        :page-size="1"
        @current-change="handleImagePageChange"
      >
      </el-pagination>
    </div>
  </div>
  <template #footer>
    <div class="dialog-footer">
      <el-button type="primary" @click="handleBatchApprove">通过</el-button>
      <el-button type="danger" @click="handleBatchReject">不通过</el-button>
      <el-button @click="reviewDialogVisible = false">取消</el-button>
    </div>
  </template>
</el-dialog>
      <template #footer>
        <div class="dialog-footer">
          <el-button type="primary" @click="handleBatchApprove">通过</el-button>
          <el-button type="danger" @click="handleBatchReject">不通过</el-button>
          <el-button @click="cancleBatchReject">取消</el-button>
        </div>
      </template>
    </el-dialog>
  </basic-container>
</template>
@@ -573,7 +539,7 @@
        handler: [{ required: true, message: '请选择处理人员', trigger: 'change' }],
        content: [{ required: true, message: '请输入工单内容', trigger: 'blur' }],
        algorithm: [{ required: true, message: '请选择关联算法', trigger: 'change' }],
        location: [{
        location: [{
          validator: (rule, value, callback) => {
            if (!value || value.length < 2) {
              callback(new Error('请选择位置信息'));
@@ -583,7 +549,7 @@
          },
          trigger: 'change'
        }],
        photos: [{
        photos: [{
          validator: (rule, value, callback) => {
            if (!this.form.photos || this.form.photos.length === 0) {
              callback(new Error('请上传工单图片'));
@@ -777,42 +743,64 @@
      }
    },
    async handleBatchApprove() {
    try {
      await this.$confirm('确认批量审核通过选中的工单?', '提示', {
        confirmButtonText: '确定',
        cancelButtonText: '取消',
        type: 'warning'
      });
      try {
        if (this.selections.length === 0) {
          this.$message.warning('没有选中的工单');
          return;
        }
      const promises = this.selections.map(async (item) => {
        const currentItem = this.selections[this.currentImageIndex - 1];
        if (!currentItem) {
          this.$message.warning('当前工单数据无效');
          return;
        }
        await this.$confirm('确认审核通过当前工单?', '提示', {
          confirmButtonText: '确定',
          cancelButtonText: '取消',
          type: 'warning'
        });
        const data = {
          id: item.id,
          status: item.status,
          isPass: 0, // 0 表示通过
          eventNum: item.orderNumber,
          eventName: item.orderName,
          eventType: item.type,
          id: currentItem.id,
          status: currentItem.status,
          isPass: 0,
          eventNum: currentItem.orderNumber,
          eventName: currentItem.orderName,
          eventType: currentItem.type,
        };
        return flowEvent(data);
      });
      const results = await Promise.all(promises);
      const successCount = results.filter(res => res.data.code === 0).length;
      if (successCount > 0) {
        this.$message.success(`成功审核通过 ${successCount} 个工单`);
        // 清空选择
        this.clearSelection();
        // 刷新数据
        this.fetchTableData();
      } else {
        throw new Error('批量审核失败');
        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 || '审核失败,请稍后重试');
      }
    } catch (error) {
      if (error === 'cancel') return;
      this.$message.error(error.message || '批量审核失败,请稍后重试');
    }
  },
    },
    async fetchDropdownData() {
      try {
        const response = await getTicketInfo();
@@ -939,7 +927,7 @@
      try {
        // 提交时需要完整验证
        await this.$refs.form.validate();
        // 验证位置信息
        if (!this.form.location || this.form.location.length < 2) {
          this.$message.warning('请在地图上选择位置');
@@ -1238,10 +1226,10 @@
    },
    getStepTime(status) {
  const step = this.stepInfos.find(step => step.status === status);
  // 如果 step 不存在或 step.time 为 0,返回 false
  return step && step.time && step.time !== '0' ? step.time : false;
},
      const step = this.stepInfos.find(step => step.status === status);
      // 如果 step 不存在或 step.time 为 0,返回 false
      return step && step.time && step.time !== '0' ? step.time : false;
    },
    getStepCreateTime(status) {
      const step = this.stepInfos.find(step => step.status === status);
      return step ? step.create_time : null;
@@ -1282,7 +1270,7 @@
      // 修改导出方法,添加选中数据的处理
      try {
        this.loading = true;
        // 如果有选中的数据,则只导出选中的数据
        let exportData = [];
        if (this.selections.length > 0) {
@@ -1421,7 +1409,7 @@
          id: this.currentDetail.id,
          status: this.currentDetail.status,
          isPass: 0, // 0 表示通过
          eventNum: this.currentDetail.orderNumber,
          eventNum: this.currentDetail.orderNumber,
          eventName: this.currentDetail.orderName, // 工单名称
          eventType: this.currentDetail.type, // 工单类型
          departmentId: this.dispatchForm.department, // 派发部门 ID
@@ -1597,17 +1585,17 @@
    // 添加编辑方法
    handleEdit(row) {
      console.log('编辑原始数据:', row);
      // 尝试从row.dept_id或通过部门名称查找对应的部门ID
      let deptId = row.dept_id;  // 优先使用原始数据中的dept_id
      // 如果没有dept_id,则通过部门名称查找
      if (!deptId) {
        // 输出所有可用的部门数据用于调试
        console.log('可用部门列表:', this.departments);
        console.log('当前部门名称:', row.department);
        const deptInfo = this.departments.find(dept =>
        const deptInfo = this.departments.find(dept =>
          dept.label && dept.label.trim() === row.department.trim()
        );
        deptId = deptInfo?.value;
@@ -1615,10 +1603,10 @@
      // 获取工单类型值 - 从types中找到匹配的值
      const typeValue = this.types.find(t => t.label === row.type)?.value || row.event_dict_key;
      // 获取处理人ID - 使用userNameToIdMap映射
      const handlerId = this.userNameToIdMap[row.handler] || row.handler;
      console.log('数据映射:', {
        部门名称: row.department,
        找到的部门ID: deptId,
@@ -1721,8 +1709,22 @@
    // 更新当前审核图片
    updateCurrentReviewImage() {
      if (this.selections.length > 0 && this.currentImageIndex > 0) {
        const currentItem = this.selections[this.currentImageIndex - 1];
      // 修正索引范围
      if (!this.selections || this.selections.length === 0) {
        this.currentReviewImage = '';
        this.currentImageIndex = 1;
        return;
      }
      // 如果当前索引超出范围,自动回退到最后一张
      if (this.currentImageIndex > this.selections.length) {
        this.currentImageIndex = this.selections.length;
      }
      if (this.currentImageIndex < 1) {
        this.currentImageIndex = 1;
      }
      const index = this.currentImageIndex - 1;
      if (index >= 0 && index < this.selections.length) {
        const currentItem = this.selections[index];
        this.currentReviewImage = currentItem.photo_url || '';
      } else {
        this.currentReviewImage = '';
@@ -1731,89 +1733,138 @@
    // 处理图片分页变化
    handleImagePageChange(page) {
      this.currentImageIndex = page;
      this.updateCurrentReviewImage();
      if (page > 0 && page <= this.selections.length) {
        this.currentImageIndex = page;
        this.updateCurrentReviewImage();
      }
    },
    // 批量审核通过
    async handleBatchApprove() {
      try {
        await this.$confirm('确认批量审核通过选中的工单?', '提示', {
        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 promises = this.selections.map(async (item) => {
          const data = {
            id: item.id,
            status: item.status,
            isPass: 0, // 0 表示通过
            eventNum: item.orderNumber,
            eventName: item.orderName,
            eventType: item.type,
          };
          return flowEvent(data);
        });
        const data = {
          id: currentItem.id,
          status: currentItem.status,
          isPass: 0,
          eventNum: currentItem.orderNumber,
          eventName: currentItem.orderName,
          eventType: currentItem.type,
        };
        console.log('删除前:', this.selections)
        const response = await flowEvent(data);
        if (response.data.code === 0) {
          this.$message.success('工单审核通过');
        const results = await Promise.all(promises);
        const successCount = results.filter(res => res.data.code === 0).length;
        if (successCount > 0) {
          this.$message.success(`成功审核通过 ${successCount} 个工单`);
          // 清空选择
          this.clearSelection();
          // 刷新数据
          this.fetchTableData();
          // 关闭审核对话框
          this.reviewDialogVisible = false;
          // 创建新的数组而不是修改原数组
          const newSelections = [...this.selections];
          newSelections.splice(this.currentImageIndex - 1, 1);
          // 修正索引并更新图片
          if (newSelections.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();
          }
          // 刷新表格数据
          // this.fetchTableData();
          this.selections = newSelections;
        } else {
          throw new Error('批量审核失败');
          throw new Error(response.data.msg || '审核失败');
        }
      } catch (error) {
        if (error === 'cancel') return;
        this.$message.error(error.message || '批量审核失败,请稍后重试');
        this.$message.error(error.message || '审核失败,请稍后重试');
      }
    },
    cancleBatchReject(){
      reviewDialogVisible = false;
      this.fetchTableData();
    },
    // 批量审核不通过
    async handleBatchReject() {
      try {
        await this.$confirm('确认批量审核不通过选中的工单?', '提示', {
        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 promises = this.selections.map(async (item) => {
          const data = {
            id: item.id,
            status: item.status,
            isPass: 1, // 1 表示不通过
            eventNum: item.orderNumber,
            eventName: item.orderName,
            eventType: item.type,
          };
          return flowEvent(data);
        });
        const data = {
          id: currentItem.id,
          status: currentItem.status,
          isPass: 1,
          eventNum: currentItem.orderNumber,
          eventName: currentItem.orderName,
          eventType: currentItem.type,
        };
        const results = await Promise.all(promises);
        const successCount = results.filter(res => res.data.code === 0).length;
        if (successCount > 0) {
          this.$message.success(`成功审核不通过 ${successCount} 个工单`);
          // 清空选择
          this.clearSelection();
          // 刷新数据
          this.fetchTableData();
          // 关闭审核对话框
          this.reviewDialogVisible = false;
        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();
          }
          // 刷新表格数据
          // this.fetchTableData();
        } else {
          throw new Error('批量审核失败');
          throw new Error(response.data.msg || '驳回失败');
        }
      } catch (error) {
        if (error === 'cancel') return;
        this.$message.error(error.message || '批量审核失败,请稍后重试');
        this.$message.error(error.message || '驳回失败,请稍后重试');
      }
    },
@@ -1947,13 +1998,14 @@
  .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;
      }
@@ -2030,13 +2082,13 @@
  padding-top: 16px;
  border-top: 1px solid #ebeef5;
  .el-button + .el-button {
  .el-button+.el-button {
    margin-left: 12px;
  }
  .el-button {
    padding: 9px 20px;
    &:last-child {
      margin-left: 12px;
    }
@@ -2253,7 +2305,7 @@
  line-height: 1.5;
  display: block;
  text-align: left;
  &:first-child {
    font-weight: bold;
    margin-bottom: 4px;
@@ -2310,7 +2362,7 @@
  line-height: 1.5;
  display: block;
  text-align: center;
  &:first-child {
    font-weight: bold;
    margin-bottom: 4px;
@@ -2346,7 +2398,7 @@
.review-container {
  position: relative;
  .review-image-wrapper {
    position: relative;
    display: flex;
@@ -2362,7 +2414,7 @@
    display: flex;
    align-items: center;
    justify-content: center;
    :deep(.el-image) {
      width: 100%;
      height: 100%;
@@ -2423,6 +2475,7 @@
    background-color: #fff;
    :deep(.el-pagination) {
      .btn-prev,
      .btn-next {
        display: none;
@@ -2443,16 +2496,15 @@
    align-items: center;
    justify-content: center;
    color: #909399;
    i {
      font-size: 48px;
      margin-bottom: 16px;
    }
    span {
      font-size: 16px;
    }
  }
}
</style>
```
</style>