无人机管理后台前端(已迁走)
张含笑
2025-08-05 c832bf2e80ac465e71b7a1c1f7a59d4252030989
src/views/tickets/ticket.vue
@@ -18,7 +18,7 @@
              @clear="handleKeyWords"
              @keyup.enter="handleSearch"
            />
            <el-select
            <!-- <el-select
              v-model="filters.department"
              placeholder="请选择所属单位"
              class="filter-item"
@@ -30,7 +30,7 @@
                :label="item.label"
                :value="item.value"
              />
            </el-select>
            </el-select> -->
            <el-select
              v-model="filters.type"
              placeholder="请选择工单类型"
@@ -98,22 +98,20 @@
          </div>
          <!-- 表格部分 -->
          <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"
            @cell-click="handleCellClick"
            :permission="permissionList"
            v-if="activeTab === tab.name"
          >
          <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 #orderNumber="{ row }">
              <el-tooltip-copy :content="row.orderNumber" :showCopyText="true" textAlign="left">
                {{ row.orderNumber }}
              </el-tooltip-copy>
            </template>
            <template #orderName="{ row }">
              <el-tooltip-copy :content="row.orderName" :showCopyText="true" textAlign="left">
                {{ row.orderName }}
              </el-tooltip-copy>
            </template>
            <template #menu-left>
              <el-button
                v-if="(activeTab === 'all' || activeTab === 'myTickets') && permissionList.addBtn"
@@ -210,7 +208,8 @@
            </el-col>
            <el-col :span="12">
              <el-form-item label="工单类型" prop="type">
                <el-select v-model="form.type" placeholder="请选择工单类型" class="full-width">
                <el-select  @change="handleTypeChange" v-model="form.type" placeholder="请选择工单类型" class="full-width" >
                  <el-option
                    v-for="item in types"
                    :key="item.value"
@@ -267,6 +266,7 @@
                  multiple
                  placeholder="请选择关联算法"
                  class="full-width"
                  :disabled="!form.type"
                >
                  <el-option
                    v-for="item in algorithms"
@@ -361,17 +361,20 @@
      </el-form>
      <template #footer>
        <div class="dialog-footer">
          <el-button type="danger" :loading="submitLoading" @click="submitForm">发布</el-button>
          <el-button type="infoprimary" plain :loading="draftLoading" @click="saveDraft"
            >存草稿</el-button
          >
          <el-button @click="dialogVisible = false">取 消</el-button>
          <el-button  @click="handleCancel">取 消</el-button>
        </div>
      </template>
    </el-dialog>
    <!-- 工单详情对话框 -->
    <el-dialog v-model="detailVisible" title="工单详情" width="80%" append-to-body>
    <el-dialog   class="custom-dialog" align-center v-model="detailVisible" title="工单详情" width="80%"  append-to-body>
      <div class="detail-container">
        <div class="detail-top-title">
          <div class="event-title-center event-orderNumber">
@@ -414,13 +417,14 @@
          </el-steps>
        </div>
        <!-- 基本信息表格 -->
      <div class="PopUpTableScrolls">
          <!-- 基本信息表格 -->
        <el-table :show-header="false" :data="formattedDetailFields" border class="tableCss">
          <el-table-column prop="label1" label="基本信息" width="150">
            <template #default="{ row }">
              <!-- 添加必填星号的标签 -->
              <span
                v-if="currentDetail.status === 0 && row.label1 === '工单名称'"
                v-if="currentDetail.status === 0 &&(row.label1 === '关联算法' ||  row.label1 === '工单名称')"
                class="required-label"
              >
                <span class="required-star">*</span>{{ row.label1 }}
@@ -443,16 +447,37 @@
                  class="required-input"
                />
              </template>
               <template
                v-else-if="
                  currentDetail.status === 0 &&
                  row.label1 === '关联算法' &&
                  hasProcessingBtnPermission()
                "
              >
                <el-select
                  v-model="currentDetail.aiType"
                  placeholder="请选择关联算法"
                  class="required-input"
                >
                  <el-option
                    v-for="item in algorithms"
                    :key="item.value"
                    :label="item.label"
                    :value="item.value"
                  />
                </el-select>
              </template>
              <template v-else>{{ row.value1 }}</template>
            </template>
          </el-table-column>
          <el-table-column prop="label2" label="基本信息" width="150">
            <template #default="{ row }">
              <!-- 添加必填星号的标签 -->
              <span
                v-if="
                  currentDetail.status === 0 &&
                  (row.label2 === '工单类型' || row.label2 === '工单内容')
                   row.label2 === '工单内容'
                "
                class="required-label"
              >
@@ -464,28 +489,10 @@
          <el-table-column>
            <template #default="{ row }">
              <!-- 修改工单类型和工单内容的显示 -->
              <template
                v-if="
                  currentDetail.status === 0 &&
                  row.label2 === '工单类型' &&
                  hasProcessingBtnPermission()
                "
              >
                <el-select
                  v-model="currentDetail.type"
                  placeholder="请选择工单类型"
                  class="required-input"
                >
                  <el-option
                    v-for="item in types"
                    :key="item.value"
                    :label="item.label"
                    :value="item.value"
                  />
                </el-select>
              </template>
              <template
                v-else-if="
                  currentDetail.status === 0 &&
                  row.label2 === '工单内容' &&
                  hasProcessingBtnPermission()
@@ -568,16 +575,8 @@
        <!-- 图片和地图部分 -->
        <div class="media-section">
          <el-row :gutter="20">
            <el-col :span="1">
              <div
                class="leftBtn"
                :class="currentIndex === 0 ? 'disableds' : ''"
                @click="leftClick"
              >
                <
              </div>
            </el-col>
            <el-col :span="11">
            <el-col :span="12">
              <div class="media-box">
                <div class="media-title">事件图片</div>
                <div class="media-content">
@@ -605,7 +604,7 @@
                </div>
              </div>
            </el-col>
            <el-col :span="11">
            <el-col :span="12">
              <div class="media-box">
                <!-- 根据状态显示不同的标题和内容 -->
@@ -642,21 +641,23 @@
                </template>
              </div>
            </el-col>
            <el-col :span="1">
              <div
                :class="currentIndex === tableData.length - 1 ? 'disableds' : ''"
                class="leftBtn"
                @click="rightClick"
              >
                >
              </div>
            </el-col>
          </el-row>
        </div>
      </div>
        <!-- 操作按钮 -->
        <div class="dialog-footer">
          <template v-if="currentDetail.status === 2">
            <div
                class="leftBtn"
                :class="currentIndex === 0 ? 'disableds' : ''"
                @click="leftClick"
              >
               上一页
              </div>
       <div class="btngroups">
           <template v-if="currentDetail.status === 2">
            <!-- 待审核 -->
            <el-button
              v-if="hasReviewBtnPermission()"
@@ -708,6 +709,14 @@
              @click="finalizeTicket">完结工单</el-button> -->
            <el-button @click="detailVisible = false">取消</el-button>
          </template>
       </div>
            <div
                :class="currentIndex === tableData.length - 1 ? 'disableds' : ''"
                class="leftBtn"
                @click="rightClick"
              >
              下一页
              </div>
        </div>
      </div>
    </el-dialog>
@@ -866,17 +875,21 @@
  getStepInfo,
  getReviewById,
  getCreateEventJob,
} 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) {
  var reg = new RegExp(label + '=([^&]*)(&|$)', 'g');
  return name.match(reg)[0].split('=')[1];
} 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'
import elTooltipCopy from '@/components/ElTooltipCopy.vue'
import getBaseConfig from '@/buildConfig/config'
const { envName } = getBaseConfig()
function regExp (label, name) {
  var reg = new RegExp(label + '=([^&]*)(&|$)', 'g')
  return name.match(reg)[0].split('=')[1]
}
export default {
  components: { elTooltipCopy },
  name: 'TicketPage',
  data() {
    return {
@@ -911,6 +924,7 @@
      },
      departments: [],
      types: [],
      allAlgorithms: [],
      handlers: [
        { label: '处理人A', value: 'handlerA' },
        { label: '处理人B', value: 'handlerB' },
@@ -949,15 +963,15 @@
        column: [
          // { label: "序号", prop: "id", width: 70 },
          { label: '工单编号', prop: 'orderNumber', width: 120, overHidden: true, tooltip: true },
          { label: '工单编号', prop: 'orderNumber', width: 170 },
          { label: '工单名称', prop: 'orderName', width: 150, overHidden: true, tooltip: true },
          { label: '所属单位', prop: 'department', overHidden: true, tooltip: true },
          { label: '所属单位', prop: 'department',width: 150, overHidden: true, tooltip: true },
          { label: '发起时间', prop: 'startTime', width: 160 },
          { label: '关联算法', prop: 'aiType', width: 150, overHidden: true, tooltip: true },
          {
            label: '工单类型',
            prop: 'type',
            width: 130,
            width: 110,
            overHidden: true,
            tooltip: true,
            type: 'select',
@@ -970,11 +984,11 @@
            width: 152,
            overHidden: true,
          },
          { label: '创建人', prop: 'creator', width: 100 },
          { label: '处理人', prop: 'handler', width: 100 },
          { label: '创建人', prop: 'creator', width: 70 },
          { label: '处理人', prop: 'handler', width: 70 },
          {
            slot: true,
            hide: false,
            hide: envName === 'jiangwu'? true : false,
            label: '复核状态',
            prop: 'isReview',
            width: 90,
@@ -1084,6 +1098,7 @@
  },
  mounted() {
    const href = this.$route.href;
    if (this.$route?.query?.status !== undefined && this.$route?.query?.status !== null) {
      this.filters.status = this.$route?.query?.status + '';
@@ -1127,12 +1142,12 @@
      if (orderNumber) {
        this.filters.keyword = orderNumber;
        this.$nextTick(() => {
          this.isShowInfo = true;
          const find = this.$store.state.tags.bsTagList.find(i => i.path === '/tickets/ticket')
          find && (find.query = {})
        });
        console.log('orderNumber', orderNumber);
      }
    }
@@ -1254,20 +1269,29 @@
        { label: '任务发起人', value: this.currentDetail.creator },
        { label: '当前状态', value: this.mapStatus(this.currentDetail.status) },
        { label: '事件地址', value: this.currentDetail.address || this.currentDetail.latAndLon }, // 包含经纬度信息
        { label: '关联算法', value: this.currentDetail.aiType },
        { label: '关联算法',
        //  value: this.currentDetail.aiType
         value:
            this.algorithms.find(t => t.value === this.currentDetail.aiType)?.label ||
            this.currentDetail.aiType ||
            '/',
        },
        { label: '发起单位', value: this.currentDetail.department },
        { label: '发起任务时间', value: this.currentDetail.startTime },
        { label: '工单内容', value: this.currentDetail.content },
      ];
      // 过滤掉值为 '/' 的列
      const filteredFields = fields.filter(field => field.value !== '/');
      // 将字段分成两列
      const formattedFields = [];
      for (let i = 0; i < fields.length; i += 2) {
      for (let i = 0; i < filteredFields.length; i += 2) {
        formattedFields.push({
          label1: fields[i]?.label || '',
          value1: fields[i]?.value || '暂无数据',
          label2: fields[i + 1]?.label || '',
          value2: fields[i + 1]?.value || '暂无数据',
          label1: filteredFields[i]?.label || '',
          value1: filteredFields[i]?.value || (filteredFields[i]?.label ? '暂无数据' : ''),
          label2: filteredFields[i + 1]?.label || '',
          value2: filteredFields[i + 1]?.value || (filteredFields[i + 1]?.label ? '暂无数据' : ''),
        });
      }
      return formattedFields;
@@ -1391,7 +1415,7 @@
        const subAreaCode = areaCode ? areaCode.substring(0, 6) : '';
        const adcodeObj = getAdcodeObj(geoJson, 'adcode', subAreaCode);
        console.log('区域代码:', subAreaCode);
       // console.log('区域代码:', subAreaCode);
        // 直接从返回对象中获取正确的路径
        const center = adcodeObj?.payload?.objects?.collection?.geometries?.[0]?.properties?.center;
@@ -1477,7 +1501,8 @@
    async fetchDropdownData() {
      try {
        const response = await getTicketInfo();
        const { dept_data, event_type, ai_type } = response.data.data;
        const { dept_data, event_type, ai_type,info } = response.data.data;
        this.departments = dept_data.map(item => ({
          label: item.dept_name,
@@ -1496,16 +1521,19 @@
        const columnType = this.findObject(this.option.column, 'type');
        columnType.dicData = this.types;
        this.allAlgorithms  = info
        console.log('工单类型',this.types);
        console.log('关联算法',this.allAlgorithms );
        // 确保算法数据的映射一致
        this.algorithms =
          ai_type?.map(item => ({
            dict_key: item.dict_key,
            dict_value: item.dict_value,
            // 同时添加 label 和 value 以兼容两处使用
            label: item.dict_value,
            value: item.dict_key,
          })) || [];
        // this.algorithms =
        //   ai_type?.map(item => ({
        //     dict_key: item.dict_key,
        //     dict_value: item.dict_value,
        //     // 同时添加 label 和 value 以兼容两处使用
        //     label: item.dict_value,
        //     value: item.dict_key,
        //   })) || [];
        // 构建用户ID和名称的映射关系
        this.userNameToIdMap = {};
@@ -1517,6 +1545,34 @@
      } catch (error) {
        this.$message.error('加载下拉框数据失败');
      }
    },
    // 工单类型变化时触发
    handleTypeChange(typeValue) {
      if (!typeValue) {
        // 未选择类型时清空算法列表
        this.algorithms = [];
        return;
      }
      const matchedCategory = this.allAlgorithms.find(
        category => category.dict_key === typeValue
      );
      if (!matchedCategory || !matchedCategory.algorithms || matchedCategory.algorithms.length === 0) {
        // 无匹配的算法时清空
        this.algorithms = [];
        this.$message.info('该工单类型暂无关联算法');
        return;
      }
// console.log('matchedCategory',matchedCategory);
      this.algorithms = matchedCategory.algorithms.map(algo => ({
        label: algo.dict_value,
        value: algo.dict_key,
        dict_key: algo.dict_key,
        dict_value: algo.dict_value
      }));
    },
    async fetchTableData() {
@@ -1771,7 +1827,10 @@
        this.draftLoading = false;
      }
    },
    handleCancel (){
      this.resetForm();
       this.dialogVisible = false;
},
    handleLocationChange(val) {
      let locationValue = val.value;
      if (locationValue && locationValue.length >= 2) {
@@ -1872,8 +1931,6 @@
      this.page.currentPage = 1;
      this.$router.replace({});  //清除url参数
      this.fetchTableData();
      console.log('handleSearch', this.$router);
    },
    handleKeyWords(){
      this.$router.replace({});  //清除url参数
@@ -1997,6 +2054,7 @@
      console.log('this.currentDetail', this.currentDetail);
      this.detailVisible = true;
      this.handleTypeChange(this.currentDetail.type)
      this.$nextTick(() => {
        if (this.$refs.MapContainer && this.$refs.MapContainer.initAddEntity) {
          this.$refs.MapContainer.initAddEntity('point', this.currentDetail.location);
@@ -2302,7 +2360,7 @@
    },
    hasProcessingBtnPermission() {
      // undefined 或 false 都返回 false,只有 true 返回 true
      // console.log('权限检查:', this.permission)
      //  console.log('权限检查:', this.permission.tickets_processing_btn)
      return this.permission && this.permission.tickets_processing_btn === true;
    },
    hasProcessedAndOverBtnPermission() {
@@ -2432,8 +2490,7 @@
      // 获取工单类型值 - 从types中找到匹配的值
      const typeValue =
        this.types.find(t => t.label === row.type)?.value || row.work_order_type_dict_key;
        this.types.find(t => t.value === row.type)?.value || row.work_order_type_dict_key;
      // 获取处理人ID - 使用userNameToIdMap映射
      const handlerId = this.userNameToIdMap[row.handler] || row.handler;
@@ -2891,11 +2948,15 @@
  },
  activated() {
    this.handleReset();
  }
  },
};
</script>
<style lang="scss">
.custom-dialog {  height: 96vh; /* 80% 视口高度 */}
</style>
<style lang="scss" scoped>
::v-deep(.el-tabs) {
  height: 100%;
  display: flex;
@@ -2950,7 +3011,7 @@
.tableCss {
  width: 100%;
  margin-bottom: 20px;
  margin-bottom: 10px;
}
.step-timer {
@@ -3100,19 +3161,29 @@
}
.dialog-footer {
  text-align: center;
  padding-top: 16px;
  border-top: 1px solid #ebeef5;
  position: sticky;
    bottom: 28px;
    left: 0;
    right: 0;
    background: white;
    z-index: 10;
    padding: 16px 20px;
    border-top: 1px solid #ebeef5;
    display: flex;
    justify-content: space-between;
    align-items: center;
  .el-button + .el-button {
    margin-left: 12px;
  }
.btngroups {
margin-left: 12px;
}
  .el-button {
    padding: 9px 20px;
    &:last-child {
      margin-left: 12px;
      margin-right: 12px;
    }
  }
}
@@ -3191,7 +3262,7 @@
}
.detail-container {
  padding: 20px;
  padding:0 20px;
  .detail-top-title {
    display: flex;
@@ -3269,7 +3340,7 @@
}
.media-section {
  margin-bottom: 20px;
  // margin-bottom: 20px;
  .el-row {
    display: flex;
@@ -3278,15 +3349,16 @@
}
.leftBtn {
  width: 36px;
  height: 36px;
  width: 70px;
  height: 32px;
  background-color: #999;
  border-radius: 50%;
  border-radius: 5px;
  text-align: center;
  line-height: 36px;
line-height: 32px;
  color: #fff;
  cursor: pointer;
  opacity: 0.8;
}
.disableds {
@@ -3295,7 +3367,11 @@
  pointer-events: none;
  opacity: 0.3 !important;
}
.PopUpTableScrolls{
height: 600px;
overflow-y: scroll;
overflow-x: hidden;
}
.media-box {
  width: 100%;
  border: 1px solid #dcdfe6;
@@ -3306,7 +3382,7 @@
  .media-title {
    font-weight: bold;
    margin-bottom: 10px;
    margin-bottom: 5px;
  }
  .media-content {
@@ -3321,7 +3397,7 @@
      .el-image__inner {
        width: 100%;
        height: 100%;
        object-fit: contain;
        object-fit: cover !important;
      }
    }
  }
@@ -3374,12 +3450,12 @@
  background-color: #f5f7fa;
  padding: 12px;
  border-radius: 4px;
  min-height: 40px;
  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;
@@ -3397,23 +3473,23 @@
.custom-steps-container {
  width: 100%;
  margin: 20px 0;
  margin: 10px 0;
}
.steps-titles {
  display: flex;
  justify-content: space-between;
  margin-bottom: 16px;
  margin-bottom: 14px;
  position: relative;
}
.step-title {
  text-align: center;
  flex: 1;
  font-size: 16px;
  font-size: 14px;
  color: #999;
  position: relative;
  padding-bottom: 10px;
  padding-bottom: 5px;
}
.step-title.active {
@@ -3425,7 +3501,7 @@
  text-align: center;
  font-size: 20px;
  font-weight: bold;
  margin-bottom: 12px;
  margin-bottom: 5px;
  color: #333;
}