无人机管理后台前端(已迁走)
张含笑
2025-07-21 3be2e099bbc05e902dfcfabc403ade0a2aba8ef4
src/views/tickets/ticket.vue
@@ -2,7 +2,7 @@
  <basic-container>
    <el-tabs v-model="activeTab" @tab-click="handleTabChange">
      <el-tab-pane v-for="tab in filteredTabs" :key="tab.name" :label="`${tab.label} (${tab.count})`" :name="tab.name">
        <div class="tab-content">
        <basic-main-content>
          <!-- 查询条件筛选栏 -->
          <div class="filter-bar">
            <el-input v-model="filters.keyword" placeholder="请输入关键字" class="filter-item" clearable
@@ -31,9 +31,10 @@
          </div>
          <!-- 表格部分 -->
          <avue-crud ref="avueCrud" v-model="tableData" :option="option" :data="tableData" v-model:page="page"
            @size-change="sizeChange" @current-change="handleCurrentChange" :table-loading="loading"
            @selection-change="handleSelectionChange" :permission="permissionList">
          <avue-crud class="ztzf-public-general-avue-crud" ref="avueCrud" v-model="tableData" :option="option"
            :data="tableData" v-model:page="page" @size-change="sizeChange" @current-change="handleCurrentChange"
            @refresh-change="refreshChange" :table-loading="loading" @selection-change="handleSelectionChange"
            :permission="permissionList" v-if="activeTab === tab.name">
            <template #menu-left>
              <el-button v-if="(activeTab === 'all' || activeTab === 'myTickets') && permissionList.addBtn"
                type="primary" icon="el-icon-plus" @click="handleAdd">新建工单</el-button>
@@ -53,8 +54,11 @@
                <el-button type="text" icon="el-icon-view" @click="handleViewDetail(row)">详情</el-button>
              </template>
              <el-button v-if="row.status === 4 && row.isReview !== 1" type="text" icon="el-icon-check"
                @click="reCheck(row)">复核</el-button>
              <template v-if="permission.tickets_repeat_review">
                <el-button v-if="row.status === 4 && row.isReview !== 1" type="text" icon="el-icon-check"
                  @click="reCheck(row)">复核</el-button>
              </template>
            </template>
            <template #status="{ row }">
              <span :style="getStatusTagType(row.status) ? 'color:' + getStatusTagType(row.status) : ''">
@@ -69,12 +73,13 @@
              <span>{{ showIsReviewText(row) }}</span>
            </template>
          </avue-crud>
        </div>
        </basic-main-content>
      </el-tab-pane>
    </el-tabs>
    <!-- 新建工单对话框 -->
    <el-dialog v-model="dialogVisible" v-if="dialogVisible" title="新建工单" width="70%" :close-on-click-modal="false" @close="resetForm">
    <el-dialog v-model="dialogVisible" v-if="dialogVisible" title="新建工单" width="70%" :close-on-click-modal="false"
      @close="resetForm">
      <el-form :model="form" :rules="rules" ref="form" label-width="90px" class="create-ticket-form">
        <div class="form-section">
          <el-row :gutter="16">
@@ -120,10 +125,10 @@
              </el-form-item>
            </el-col>
            <el-col :span="12">
              <el-form-item label="选择位置" prop="location" required>
              <el-form-item label="选择位置" prop="location">
                <div class="location-wrapper">
                  <avue-input-map v-model="form.location" :params="mapParams" @change="handleLocationChange"
                    type="button">
                  <avue-input-map v-model="form.location" :clearable="false" :params="mapParams"
                    @change="handleLocationChange" type="button">
                    <el-button type="primary" plain class="map-button">
                      <i class="el-icon-map-location"></i> 地图选点
                    </el-button>
@@ -144,11 +149,25 @@
            </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="create-upload">
                <el-upload v-if="createoredit === 1" 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="create-upload">
                  <template v-if="form.photos.length < 1">
                    <i class="el-icon-plus">+</i>
                    <!-- <i class="el-icon-plus">+</i> -->
                    <div class="el-icon-plus">
                      <span>+</span>
                    </div>
                  </template>
                </el-upload>
                <el-upload v-else ref="upload" :action="'#'" :auto-upload="false" list-type="picture-card"
                  :on-change="handleFileChange" :on-remove="handleUploadRemove" :before-upload="beforeUpload"
                  :file-list="popupShowImage(form.photos)" :limit="1" accept="image/*" class="create-upload">
                  <template v-if="form.photos.length < 1">
                    <div class="el-icon-plus">
                      <span>+</span>
                    </div>
                  </template>
                </el-upload>
                <div class="upload-tip">需上传含有地址信息的照片(jpg、jpeg、png),且不超过5M</div>
@@ -169,7 +188,10 @@
    <!-- 工单详情对话框 -->
    <el-dialog v-model="detailVisible" title="工单详情" width="80%" append-to-body>
      <div class="detail-container">
        <div class="event-title-center">{{ currentDetail.orderName || '事件名称' }}</div>
        <div class="detail-top-title">
          <div class="event-title-center event-orderNumber">{{ currentDetail.orderNumber || '工单编号' }}</div>
          <div class="event-title-center">{{ currentDetail.orderName || '事件名称' }}</div>
        </div>
        <div v-if="totalTime" class="event-total-time">
          总耗时:{{ totalTime }}
        </div>
@@ -301,7 +323,10 @@
            :before-upload="beforeUpload" :file-list="currentDetail.photos || []" :limit="1" accept="image/*"
            class="detail-upload">
            <template v-if="!currentDetail.photos || currentDetail.photos.length < 1">
              <i class="el-icon-plus">+</i>
              <!-- <i class="el-icon-plus">+</i> -->
              <div class="el-icon-plus">
                <span>+</span>
              </div>
            </template>
          </el-upload>
          <div class="el-upload__tip" v-if="hasProcessedAndOverBtnPermission()">
@@ -489,16 +514,18 @@
      @close="reCheckDialog = false">
      <div class="dialog-footer">
        <el-button type="primary" @click="reCheckConfirm(1)">人工复核</el-button>
        <el-button type="primary" @click="reCheckConfirm(2)">AI复核</el-button>
        <el-button type="primary" @click="reCheckConfirm(2)">无人机复核</el-button>
      </div>
    </el-dialog>
  </basic-container>
</template>
<script>
import { getSmallImg, getShowImg } from '@/utils/util'
import { ElMessageBox, ElLoading } from 'element-plus'
import { calculateDefaultRange } from '@/utils/util'
import { gcj02ToWgs84, wgs84ToGcj02 } from '@/utils/coordinateTransformation'
import _ from 'lodash'
import {
  getList,
  createTicket,
@@ -511,7 +538,6 @@
} from '@/api/tickets/ticket'
import { export_json_to_excel } from '@/utils/exportExcel'
import geoJson from '@/assets/geoJson.json'
import { mapGetters } from 'vuex'
import { getAdcodeObj } from '@/utils/disposeData'
function regExp (label, name) {
@@ -531,6 +557,7 @@
      dispatchLoading: false,
      completeLoading: false,
      finalizeLoading: false,
      createoredit: '',
      activeTab: 'all',
      // tabs 只保留静态结构,不做权限判断
      tabs: [
@@ -585,6 +612,10 @@
        addBtn: false,
        menu: true,
        page: true,
        height: 'auto',
        calcHeight: 196,
        column: [
          // { label: "序号", prop: "id", width: 70 },
          { label: '工单编号', prop: 'orderNumber', width: 120, overHidden: true, tooltip: true },
@@ -648,14 +679,17 @@
        algorithm: [{ required: true, message: '请选择关联算法', trigger: 'change' }],
        location: [
          {
            required: true,
            validator: (rule, value, callback) => {
              if (!value || value.length < 2) {
                callback(new Error('请选择位置信息'))
              } else if (!value[0] || !value[1]) {
                callback(new Error('请选择位置信息'))
              } else {
                callback()
              }
            },
            trigger: 'blur',
            trigger: 'change'
          },
        ],
        photos: [
@@ -710,6 +744,8 @@
    }
  },
  created () {
    this.inputMapShowDefaultCenter = null
    this.loadAMapScripts()
    this.fetchDropdownData()
    console.log('permission.tickets_processing_btn', this.permission.tickets_processing_btn)
@@ -718,6 +754,11 @@
  mounted () {
    const href = this.$route.href
    if (this.$route?.query?.status !== undefined && this.$route?.query?.status !== null) {
      this.filters.status = this.$route?.query?.status + ''
      this.$router.replace({})
    }
    let curQueryParams = {}
    if (href.indexOf('?') != -1 && href.split('?').length > 0) {
@@ -879,7 +920,7 @@
        { label: '关联任务', value: this.currentDetail.job_name || '/' },
        { label: '任务发起人', value: this.currentDetail.creator },
        { label: '当前状态', value: this.mapStatus(this.currentDetail.status) },
        { label: '事件地址', value: this.currentDetail.address }, // 包含经纬度信息
        { label: '事件地址', value: this.currentDetail.address || this.currentDetail.latAndLon }, // 包含经纬度信息
        { label: '关联算法', value: this.currentDetail.aiType },
        { label: '发起单位', value: this.currentDetail.department },
        { label: '发起任务时间', value: this.currentDetail.startTime },
@@ -956,6 +997,16 @@
        return '/'
      }
    },
    popupShowImage () {
      return (list) => {
        return list.map(item => ({
          ...item,
          url: getShowImg(item.url)
        }))
      }
    }
  },
  methods: {
@@ -996,19 +1047,21 @@
        // 直接从返回对象中获取正确的路径
        const center = adcodeObj?.payload?.objects?.collection?.geometries?.[0]?.properties?.center
        if (Array.isArray(center) && center.length === 2) {
          this.mapParams.center = center
          this.inputMapShowDefaultCenter = center
        } else {
          // 如果找不到中心点,尝试使用 bbox 的中心点
          const bbox = adcodeObj?.payload?.bbox
          if (Array.isArray(bbox) && bbox.length === 4) {
            const centerX = (bbox[0] + bbox[2]) / 2
            const centerY = (bbox[1] + bbox[3]) / 2
            this.mapParams.center = [centerX, centerY]
            this.inputMapShowDefaultCenter = [centerX, centerY]
          } else {
            this.mapParams.center = [115.861365, 28.621311]
            this.inputMapShowDefaultCenter = [115.861365, 28.621311]
          }
        }
        this.mapParams.center = [...this.inputMapShowDefaultCenter]
        this.mapLoaded = true
      } catch (error) {
@@ -1300,6 +1353,13 @@
          handlerValue = undefined
        }
        // 验证位置信息
        if (!this.form.location || this.form.location.length < 2 || !this.form.location[0] || !this.form.location[1]) {
          this.$message.warning('请在地图上选择位置')
          return
        }
        let [lng, lat] = this.disposeLocation(true, this.form)
        const submitData = {
@@ -1320,7 +1380,7 @@
        // 草稿时也至少需要工单名称
        if (!submitData.eventName) {
          this.$message.warning('请至少输入工单名称')
          this.$message.warning('请输入工单名称')
          return
        }
@@ -1338,7 +1398,18 @@
        if (response.data.code === 0) {
          this.$message.success('草稿保存成功')
          this.dialogVisible = false
          this.fetchTableData()
          this.form = {
            name: '',
            type: '',
            department: '',
            handler: '',
            algorithm: [], // 关联算法改为数组
            location: [], // 将存储为[经度, 纬度, 地址]格式
            address: '',
            photos: [],
            content: '', // 新增字段,用于存储后端返回的 content
          },
            this.fetchTableData()
        } else {
          throw new Error(response.data.msg || '保存失败')
        }
@@ -1386,10 +1457,10 @@
      if (status === -1 || status === '-1') return ''
      // 状态颜色映射
      const colorMap = {
        0: '#ffb6b6', // 待处理-淡红
        3: '#e57373', // 处理中-更淡红
        2: '#faad14', // 待审核-橙色
        4: '#a0cfff', // 已完成-淡蓝
        0: '#FF7411', // 待处理-淡红
        3: '#FFC300', // 处理中-更淡红
        2: '#FF472F', // 待审核-橙色
        4: '#0291A1', // 已完成-淡蓝
      }
      return colorMap[String(status)] || ''
    },
@@ -1451,6 +1522,8 @@
    },
    async handleCurrentChange (val) {
      // 先更新页码
      this.page.currentPage = val
      // 等待 DOM 更新后再请求数据
@@ -1459,6 +1532,7 @@
    },
    async sizeChange (val) {
      this.page.pageSize = val
      this.page.currentPage = 1 // 重置到第一页
      await this.$nextTick()
@@ -1466,8 +1540,9 @@
    },
    handleAdd () {
      this.createoredit = 1
      this.dialogVisible = true
      this.mapParams.center = null
      this.mapParams.center = [...this.inputMapShowDefaultCenter]
      this.form.location = []
    },
@@ -1497,6 +1572,7 @@
    },
    async handleViewDetail (row) {
      // 找到当前行在tableData中的索引
      this.currentIndex = this.tableData.findIndex(item => item.id === row.id)
      // 先设置workType,直接从row读取
@@ -1546,6 +1622,8 @@
          })
        }
        this.currentDetail.status = row.status
      } catch (error) {
        if (this.activeTab === 'myTickets') {
          const statusArr = this.workType === 1 ? ['3', '4'] : this.fixedStatuses
@@ -1558,8 +1636,14 @@
          this.stepInfos = []
        }
      }
      console.log(detailData, 'detailDatadetailDatadetailData')
      this.currentDetail = {
        ...detailData,
        address: null,
        latAndLon: _.round(detailData.location[0], 6) + ',' + _.round(detailData.location[1], 6)
      }
      this.currentDetail = detailData
      console.log('this.currentDetail', this.currentDetail)
      this.detailVisible = true
      this.$nextTick(() => {
        if (this.$refs.MapContainer && this.$refs.MapContainer.initAddEntity) {
@@ -1973,6 +2057,7 @@
    // 添加编辑方法
    handleEdit (row) {
      this.createoredit = 2
      console.log('编辑原始数据:', row)
      // 尝试从row.dept_id或通过部门名称查找对应的部门ID
@@ -2048,6 +2133,7 @@
        address: row.address || '',
        content: row.content,
        photos: [],
      }
      let curLocation = []
@@ -2064,7 +2150,7 @@
      if (Array.isArray(this.form.location) && this.form.location.length >= 2) {
        this.mapParams.center = [Number(this.form.location[0]), Number(this.form.location[1])]
      } else {
        this.mapParams.center = null // 没有经纬度时用默认
        this.mapParams.center = [...this.inputMapShowDefaultCenter]
      }
      // 如果有图片,添加到表单中
      if (row.photo_url) {
@@ -2121,6 +2207,7 @@
    // 添加选择变化处理方法
    handleSelectionChange (selection) {
      this.selections = selection
      console.log('已选择的行:', selection)
    },
@@ -2371,20 +2458,23 @@
     * @param goWgs84 是否由国测转换到84,默认false----由84转换到国测
     */
    disposeLocation (goWgs84 = false, data) {
      let lng, lat
      if (goWgs84) {
        lng = data.location?.[0] ? String(data.location[0]) : undefined
        lat = data.location?.[1] ? String(data.location[1]) : undefined
      let lng = '', lat = ''
        if (lng && lat) {
          [lng, lat] = gcj02ToWgs84(Number(lng), Number(lat))
        }
      } else {
        lng = Number(data.location[0])
        lat = Number(data.location[1])
      if (Array.isArray(data.location) && data.location.length > 0) {
        if (goWgs84) {
          lng = data.location?.[0] ? String(data.location[0]) : undefined
          lat = data.location?.[1] ? String(data.location[1]) : undefined
        if (lng && lat) {
          [lng, lat] = wgs84ToGcj02(Number(lng), Number(lat))
          if (lng && lat) {
            [lng, lat] = gcj02ToWgs84(Number(lng), Number(lat))
          }
        } else {
          lng = Number(data.location[0])
          lat = Number(data.location[1])
          if (lng && lat) {
            [lng, lat] = wgs84ToGcj02(Number(lng), Number(lat))
          }
        }
      }
@@ -2449,12 +2539,51 @@
</script>
<style lang="scss" scoped>
:deep() {
  .avue-crud__body {
    .el-form {
      height: 655px;
      overflow: auto;
::v-deep(.el-tabs) {
  height: 100%;
  display: flex;
  flex-direction: column;
  .el-tabs__header {
    order: 1;
  }
  .el-tabs__content {
    order: 2;
  }
  .el-tabs__content {
    height: 0;
    flex: 1;
    display: flex;
    flex-direction: column;
    .el-tab-pane {
      height: 0;
      flex: 1;
      display: flex;
      flex-direction: column;
    }
  }
}
.filter-bar {
  display: flex;
  align-items: center;
  margin-bottom: 15px;
  flex-wrap: wrap;
  gap: 8px; // 使用 gap 统一设置间距
  .filter-item {
    width: 160px; // 减小宽度
  }
  .date-picker {
    width: 240px; // 日期选择器宽度适当调整
  }
  .el-button {
    margin-left: 0; // 覆盖 element-ui 默认的按钮间距
  }
}
@@ -2485,57 +2614,8 @@
  margin-bottom: 12px
}
.tab-content {
  padding: 10px;
}
.filter-bar {
  display: flex;
  align-items: center;
  margin-bottom: 15px;
  flex-wrap: wrap;
  gap: 8px; // 使用 gap 统一设置间距
  .filter-item {
    width: 160px; // 减小宽度
  }
  .date-picker {
    width: 240px; // 日期选择器宽度适当调整
  }
  .el-button {
    margin-left: 0; // 覆盖 element-ui 默认的按钮间距
  }
}
.action-bar {
  margin-bottom: 16px;
}
.el-tabs {
  :deep(.el-tabs__content) {
    overflow: visible;
  }
}
.tab-content {
  min-height: 200px;
}
.detail-form {
  :deep(.el-form-item) {
    margin-bottom: 10px;
  }
  :deep(.el-form-item__label) {
    color: #606266;
    font-weight: normal;
  }
  :deep(.el-form-item__content) {
    color: #303133;
  }
}
.el-dialog {
@@ -2755,6 +2835,16 @@
.detail-container {
  padding: 20px;
  .detail-top-title {
    display: flex;
    justify-content: center;
    align-items: center;
    .event-orderNumber {
      margin-right: 10px;
    }
  }
}
.status-flow {