无人机管理后台前端(已迁走)
张含笑
2025-11-20 771cd475a2aad9938f4e2085ca018ba31bdc1d7f
feat:工单详情
3 files modified
1723 ■■■■ changed files
src/views/tickets/ticket.vue 471 ●●●● patch | view | raw | blame | history
src/views/tickets/ticketComponent/CreateTicketDialog.vue 5 ●●●● patch | view | raw | blame | history
src/views/tickets/ticketComponent/TicketDetailDialog.vue 1247 ●●●●● patch | view | raw | blame | history
src/views/tickets/ticket.vue
@@ -9,7 +9,6 @@
      >
        <basic-main-content>
          <!-- 查询条件筛选栏 -->
          <!-- 查询条件筛选栏 -->
          <div class="ztzf-form-search">
            <el-form :model="filters" inline>
              <el-row :gutter="24">
@@ -26,33 +25,7 @@
                  </el-form-item>
                </el-col>
                <!-- <el-select
                  v-model="filters.department"
                  placeholder="请选择所属部门"
                  class="filter-item"
                  clearable
                >
                  <el-option
                    v-for="item in departments"
                    :key="item.value"
                    :label="item.label"
                    :value="item.value"
                  />
                </el-select> -->
                <!-- <el-select
                  v-model="filters.type"
                  placeholder="请选择工单类型"
                  class="filter-item"
                  clearable
                  @change="handleSearch"
                >
                  <el-option
                    v-for="item in types"
                    :key="item.value"
                    :label="item.label"
                    :value="item.value"
                  />
                </el-select> -->
                <el-col :span="5">
                  <el-form-item label="选择日期:">
                  <el-date-picker
@@ -87,34 +60,6 @@
                  </el-form-item>
                </el-col>
                <!-- <el-select
                  v-model="filters.algorithm"
                  placeholder="请选择关联算法"
                  class="filter-item"
                  clearable
                  @change="handleSearch"
                >
                  <el-option
                    v-for="item in algorithms"
                    :key="item.dict_key"
                    :label="item.dict_value"
                    :value="item.dict_key"
                  />
                </el-select> -->
                <!-- <el-tree-select
                  popper-class="custom-tree-select"
                  :style="{ width: pxToRem(186) }"
                  placeholder="请选择关联算法"
                  v-model="dictKey"
                  :data="dataList"
                  :default-expanded-keys="[dictKey]"
                  check-strictly
                  node-key="id"
                  :props="treePropsSF"
                  @node-click="handleSFNodeClick"
                  clearable
                  @clear="handleClear"
                /> -->
                <el-col :span="4">
                  <el-form-item label="选择算法:">
                  <el-tree-select
@@ -270,37 +215,37 @@
    </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
  v-model="detailVisible"
  :current-detail="currentDetail"
  :current-index="currentIndex"
  :total-items="tableData.length"
  :algorithms="algorithms"
  :types="types"
  :permission="permission"
  :step-status-list="stepStatusList"
  :work-type="workType"
  @detail-success="handleDetailSuccess"
  @detail-error="handleDetailError"
  @prev-item="handlePrevItem"
  @next-item="handleNextItem"
  @update:current-detail="handleCurrentDetailUpdate"
  @approve-and-dispatch="handleApproveAndDispatch"
/>
      <ticket-detail-dialog
        v-model="detailVisible"
        :current-detail="currentDetail"
        :current-index="currentIndex"
        :total-items="tableData.length"
        :algorithms="algorithms2"
        :types="types"
        :permission="permission"
        :step-status-list="stepStatusList"
        :work-type="workType"
        @detail-success="handleDetailSuccess"
        @detail-error="handleDetailError"
        @prev-item="handlePrevItem"
        @next-item="handleNextItem"
        @update:current-detail="handleCurrentDetailUpdate"
        @approve-and-dispatch="handleApproveAndDispatch"
      />
    <!-- 派发工单对话框 -->
      <dispatch-dialog
@@ -418,8 +363,6 @@
  flowEvent,
  getstatusCount,
  getStepInfo,
  getReviewById,
  getCreateEventJob,
  exportTheTicket
} from '@/api/tickets/ticket';
import { getSFDictionaryTree } from '@/api/job/task';
@@ -448,12 +391,6 @@
  data() {
    return {
      currentIndex: null, // 当前显示的数据索引
      approveLoading: false,
      rejectLoading: false,
      dispatchLoading: false,
      completeLoading: false,
      finalizeLoading: false,
      createoredit: '',
      activeTab: 'all',
@@ -486,6 +423,7 @@
      algorithms: [],
      algorithms2: [],
      algorithms3: [],
      statuses: [
        { label: '待审核', value: '2' },
        { label: '待处理', value: '0' },
@@ -604,7 +542,6 @@
  },
  created() {
    this.inputMapShowDefaultCenter = null;
    this.loadAMapScripts();
    this.fetchDropdownData();
  },
@@ -691,134 +628,7 @@
        ? this.departmentUsers[this.dispatchForm.department] || []
        : [];
    },
    detailTableData() {
      return [
        {
          label: '工单名称',
          value: this.currentDetail.orderName,
          editable: this.currentDetail.status === 0,
          type: 'input',
        },
        {
          label: '关键任务',
          value: this.currentDetail.keyData,
          editable: false,
        },
        {
          label: '工单创建人',
          value: this.currentDetail.creator,
          editable: false,
        },
        {
          label: '当前状态',
          value: this.mapStatus(this.currentDetail.status),
          editable: false,
        },
        {
          label: '事件地址',
          value: this.currentDetail.address,
          editable: false,
        },
        {
          label: '工单类型',
          value: this.currentDetail.type,
          editable: this.currentDetail.status === 0,
          type: 'select',
          options: this.types,
        },
        {
          label: '关联算法',
          value: this.currentDetail.aiType,
          editable: false,
        },
        {
          label: '发起单位',
          value: this.currentDetail.department,
          editable: false,
        },
        {
          label: '发起任务时间',
          value: this.currentDetail.startTime,
          editable: false,
        },
        {
          label: '工单内容',
          value: this.currentDetail.content,
          editable: this.currentDetail.status === 0,
          type: 'textarea',
        },
      ];
    },
    detailFields() {
      return [
        {
          label: '工单名称',
          value: this.currentDetail.orderName,
          editable: this.currentDetail.status === 0,
          type: 'input',
        },
        { label: '关键任务', value: this.currentDetail.keyData, editable: false },
        { label: '工单创建人', value: this.currentDetail.creator, editable: false },
        { label: '当前状态', value: this.mapStatus(this.currentDetail.status), editable: false },
        { label: '事件地址', value: this.currentDetail.address, editable: false },
        {
          label: '工单类型',
          value: this.currentDetail.type,
          editable: this.currentDetail.status === 0,
          type: 'select',
          options: this.types,
        },
        { label: '关联算法', value: this.currentDetail.aiType, editable: false },
        { label: '发起单位', value: this.currentDetail.department, editable: false },
        { label: '发起任务时间', value: this.currentDetail.startTime, editable: false },
        {
          label: '工单内容',
          value: this.currentDetail.content,
          editable: this.currentDetail.status === 0,
          type: 'textarea',
        },
      ];
    },
    formattedDetailFields() {
      const fields = [
        { label: '工单名称', value: this.currentDetail.orderName },
        {
          label: '工单类型',
          // 修改这里:使用 types 数组查找对应的 label
          value:
            this.types.find(t => t.value === this.currentDetail.type)?.label ||
            this.currentDetail.type,
        },
        { label: '关联任务', value: this.currentDetail.job_name || '/' },
        { label: '工单创建人', value: this.currentDetail.creator },
        { label: '当前状态', value: this.mapStatus(this.currentDetail.status) },
        { label: '事件地址', value: this.currentDetail.address || this.currentDetail.latAndLon }, // 包含经纬度信息
        {
          label: '关联算法',
          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 < filteredFields.length; i += 2) {
        formattedFields.push({
          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;
    },
    dynamicFixedStatuses() {
      // 直接使用接口返回的 stepInfos
      return this.stepInfos.map(step => String(step.status));
@@ -962,7 +772,6 @@
    // 更新当前详情
    updateCurrentDetail() {  
      this.currentDetail = this.tableData[this.currentIndex];
      console.log('当前',this.currentDetail);
      this.currentDetail.mediaUrl = this.currentDetail.photo_url;
      this.currentDetail.updatePhotoUrl = this.currentDetail.update_photo_url;
      this.currentDetail.processingDetail = this.currentDetail.processingDetail;
@@ -981,9 +790,7 @@
      this.currentDetail = detail;
      this.dispatchDialogVisible = true;
    },
    async handleQRCode(val) {
      val.showQR = !val.showQR;
    },
    async loadAMapScripts() {
      try {
        const areaCode = this.userInfo.detail.areaCode;
@@ -1015,7 +822,6 @@
        this.$message.error('地图加载失败,请检查网络或API Key配置');
      }
    },
    async fetchDropdownData() {
      try {
        const response = await getTicketInfo();
@@ -1067,7 +873,7 @@
      }
    },
    // 获取表格数据
    async fetchTableData() {
      if (this.isFetching) return;
      this.isFetching = true;
@@ -1172,7 +978,41 @@
      }
    },
 handleTypeChange(typeValue) {
      // this.form.algorithm = [];
      if (!typeValue) {
        // 未选择类型时清空算法列表
        this.algorithms2 = [];
        this.algorithms3 = [];
        return;
      }
      const matchedCategory = this.allAlgorithms.find(category => category.dict_key === typeValue);
      if (
        !matchedCategory ||
        !matchedCategory.algorithms ||
        matchedCategory.algorithms.length === 0
      ) {
        // 无匹配的算法时清空
        this.algorithms2 = [];
         this.algorithms3 = [];
        this.$message.info('该工单类型暂无关联算法');
        return;
      }
      this.algorithms2 = matchedCategory.algorithms.map(algo => ({
        label: algo.dict_value,
        value: algo.dict_key,
        dict_key: algo.dict_key,
        dict_value: algo.dict_value,
      }));
        this.algorithms3 = matchedCategory.algorithms.map(algo => ({
        label: algo.dict_value,
        value: algo.dict_key,
        dict_key: algo.dict_key,
        dict_value: algo.dict_value,
      }));
    },
    formatDate(date) {
      if (!date) return undefined;
      const d = new Date(date);
@@ -1294,13 +1134,6 @@
    },
    formatLocation(location) {
      if (!Array.isArray(location)) {
        return '未知位置';
      }
      return `${location[0].toFixed(6)}, ${location[1].toFixed(6)}`;
    },
async getStepInfoData(val) {
  try {
  const orderNum = val
@@ -1331,8 +1164,6 @@
        };
      });
    }
    console.log('stepInfos',this.stepInfos);
    return true; // 表示成功获取数据
  } catch (error) {
    console.error('获取步骤信息失败:', error);
@@ -1367,10 +1198,8 @@
  // 获取步骤信息
  await this.getStepInfoData(row.orderNumber);
  this.currentDetail.status = row.status;
  console.log(detailData, 'detailDatadetailDatadetailData');
  this.currentDetail = {
    ...detailData,
    showQR: false,
@@ -1380,7 +1209,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) {
@@ -1397,41 +1226,7 @@
    handleDetailError(error) {
      console.error('工单操作失败:', error);
    },
    getStepHandler(status) {
      const step = this.stepInfos.find(step => step.status === status);
      return step ? step.name : '';
    },
    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;
    },
    getStepCreateTime(status) {
      const step = this.stepInfos.find(step => step.status === status);
      return step ? step.create_time : null;
    },
    getActiveStep() {
      // 步骤索引适配
      const arr = this.stepStatusList;
      const index = arr.indexOf(String(this.currentDetail.status));
      return index !== -1 ? index + 2 : 1;
    },
    openMap() {
      const areaCode = this.userInfo.detail.areaCode;
      const subAreaCode = areaCode ? areaCode.substring(0, 6) : '';
      getAdcodeObj(geoJson, 'adcode', subAreaCode);
      this.$message.info('地图选址功能暂未实现');
    },
    handlePreview(file) {
      this.$message.info(`预览图片:${file.name}`);
    },
    handleRemove(file) {
      this.$message.info(`移除图片:${file.name}`);
    },
    refreshChange() {
      if (this.isFetching) return;
@@ -1508,113 +1303,11 @@
        工单状态: 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('只有处理中状态的工单可以提交处理详情');
        return;
      }
      try {
        const data = {
          id: this.currentDetail.id, // 当前工单 ID
          status: this.currentDetail.status, // 当前工单状态
          processing_details: this.currentDetail.processingDetail, // 事件处理详情
        };
        // 如果有图片,添加 file 参数
        const file = this.currentDetail.photos?.[0]?.raw || 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) {
        console.error('处理详情提交失败:', error);
        this.$message.error(error.message || '提交失败,请稍后重试');
      }
    },
    markAsCompleted() {
      this.$message.success('工单已标记为完成');
    },
    async approveAndDispatch() {
      if (this.dispatchLoading) return;
      // 添加必填项检查
      if (!this.currentDetail.orderName || !this.currentDetail.orderName.trim()) {
        this.$message.warning('请填写工单名称');
        return;
      }
      if (!this.currentDetail.aiType) {
        this.$message.warning('请选择关联算法');
        return;
      }
      if (!this.currentDetail.content || !this.currentDetail.content.trim()) {
        this.$message.warning('请填写工单内容');
        return;
      }
      // 通过验证后,打开派发对话框
      this.dispatchDialogVisible = true;
      console.log('受理', this.currentDetail);
    },
      // 派发成功回调
    // 派发成功回调
    handleDispatchSuccess() {
      this.detailVisible = false;
      this.fetchTableData();
@@ -1625,21 +1318,7 @@
      // 可以在这里处理错误,或者保持空实现
      console.error('派发失败:', error);
    },
    hasProcessingBtnPermission() {
      // undefined 或 false 都返回 false,只有 true 返回 true
      //  console.log('权限检查:', this.permission.tickets_processing_btn)
      return this.permission && this.permission.tickets_processing_btn === true;
    },
    hasProcessedAndOverBtnPermission() {
      // undefined 或 false 都返回 false,只有 true 返回 true
      //console.log('权限检查:', this.permission)
      return this.permission && this.permission.tickets_view_processedAndOver === true;
    },
    hasReviewBtnPermission() {
      // undefined 或 false 都返回 false,只有 true 返回 true
      // console.log('权限检查:', this.permission)
      return this.permission && this.permission.tickets_review_btn === true;
    },
    async finalizeTicket() {
      if (this.finalizeLoading) return;
      this.finalizeLoading = true;
@@ -2032,14 +1711,6 @@
};
</script>
<style lang="scss">
.custom-dialog {
  max-height: 96vh;
  .el-dialog__body {
    border-top: 0.1rem solid #f0f0f0;
  }
}
.custom-qrcode-popover {
  min-width: 160px !important;
  min-height: 140px !important;
src/views/tickets/ticketComponent/CreateTicketDialog.vue
@@ -397,16 +397,13 @@
      this.dialogVisible = false;
      this.resetForm();
    },
    handleTypeChange(typeValue) {
      this.form.algorithm = [];
      if (!typeValue) {
        this.availableAlgorithms = [];
        return;
      }
      const matchedCategory = this.allAlgorithms.find(category => category.dict_key === typeValue);
      if (!matchedCategory || !matchedCategory.algorithms || matchedCategory.algorithms.length === 0) {
        this.availableAlgorithms = [];
        this.$message.info('该工单类型暂无关联算法');
@@ -421,7 +418,7 @@
      }));
    },
    handleDepartmentChange(deptId) {
    handleDepartmentChange() {
      this.form.handler = '';
    },
src/views/tickets/ticketComponent/TicketDetailDialog.vue
@@ -15,8 +15,7 @@
        </div>
        <div class="event-title-center">{{ currentDetail.orderName || '事件名称' }}</div>
      </div>
      <div v-if="totalTime" class="event-total-time">总耗时:{{ totalTime }}</div>
      <div v-if="totalTime" class="event-total-time">总耗时:{{ totalTime }}</div>
      <!-- 工单状态流程 -->
      <div class="custom-steps-container">
        <!-- 标题行 -->
@@ -380,392 +379,431 @@
  </el-dialog>
</template>
<script>
<script setup>
import { ref, computed, watch, nextTick } from 'vue';
import { flowEvent, getStepInfo } from '@/api/tickets/ticket';
import { getSmallImg, getShowImg } from '@/utils/util';
import CreateQRcode from '@/components/CreateQRcode/CreateQRcode.vue';
export default {
  name: 'TicketDetailDialog',
  components: {
    CreateQRcode
import { ElMessage} from 'element-plus';
// 定义props
const props = defineProps({
  modelValue: {
    type: Boolean,
    default: false
  },
  props: {
    modelValue: {
      type: Boolean,
      default: false
  currentDetail: {
    type: Object,
    default: () => ({})
  },
  currentIndex: {
    type: Number,
    default: 0
  },
  totalItems: {
    type: Number,
    default: 0
  },
  algorithms: {
    type: Array,
    default: () => []
  },
  types: {
    type: Array,
    default: () => []
  },
  permission: {
    type: Object,
    default: () => ({})
  },
  stepStatusList: {
    type: Array,
    default: () => []
  },
  workType: {
    type: Number,
    default: 0
  }
});
// 定义emits
const emit = defineEmits([
  'update:modelValue',
  'detail-success',
  'detail-error',
  'prev-item',
  'next-item',
  'update:currentDetail',
  'approve-and-dispatch'
]);
// 响应式数据
const stepInfos = ref([]);
const totalTime = ref('');
const approveLoading = ref(false);
const rejectLoading = ref(false);
const dispatchLoading = ref(false);
const completeLoading = ref(false);
const finalizeLoading = ref(false);
const uploadRef = ref(null);
const mapContainerRef = ref(null);
// 计算属性
const dialogVisible = computed({
  get() {
    return props.modelValue;
  },
  set(value) {
    emit('update:modelValue', value);
  }
});
const isEditMode = computed(() => {
  return props.currentDetail.status === -1;
});
const formattedDetailFields = computed(() => {
  const fields = [
    { label: '工单名称', value: props.currentDetail.orderName },
    {
      label: '工单类型',
      value: props.types.find(t => t.value === props.currentDetail.type)?.label || props.currentDetail.type,
    },
    currentDetail: {
      type: Object,
      default: () => ({})
    { label: '关联任务', value: props.currentDetail.job_name || '/' },
    { label: '工单创建人', value: props.currentDetail.creator },
    { label: '当前状态', value: mapStatus(props.currentDetail.status) },
    { label: '事件地址', value: props.currentDetail.address || props.currentDetail.latAndLon },
    {
      label: '关联算法',
      value: props.algorithms.find(t => t.value === props.currentDetail.aiType)?.label || props.currentDetail.aiType,
    },
    currentIndex: {
      type: Number,
      default: 0
    },
    totalItems: {
      type: Number,
      default: 0
    },
    algorithms: {
      type: Array,
      default: () => []
    },
    types: {
      type: Array,
      default: () => []
    },
    permission: {
      type: Object,
      default: () => ({})
    },
    stepStatusList: {
      type: Array,
      default: () => []
    },
    workType: {
      type: Number,
      default: 0
    { label: '发起单位', value: props.currentDetail.department },
    { label: '发起任务时间', value: props.currentDetail.startTime },
    { label: '工单内容', value: props.currentDetail.content },
  ];
  const filteredFields = fields.filter(field => field.value !== '/');
  const formattedFields = [];
  for (let i = 0; i < filteredFields.length; i += 2) {
    formattedFields.push({
      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;
});
// 监听
watch(
  () => props.modelValue,
  (newVal) => {
    if (newVal) {
      loadStepInfo();
      initMap();
    }
  }
);
watch(
  () => props.currentDetail,
  (newVal) => {
    if (newVal && dialogVisible.value) {
      loadStepInfo();
      initMap();
    }
  },
  emits: [
    'update:modelValue',
    'detail-success',
    'detail-error',
    'prev-item',
    'next-item',
    'update:currentDetail'
  ],
  data() {
    return {
      stepInfos: [],
      totalTime: '',
      approveLoading: false,
      rejectLoading: false,
      dispatchLoading: false,
      completeLoading: false,
      finalizeLoading: false
  { deep: true }
);
// 方法
const handleClose = () => {
  dialogVisible.value = false;
};
const handlePrev = () => {
  if (props.currentIndex > 0) {
    emit('prev-item');
  }
};
const handleNext = () => {
  if (props.currentIndex < props.totalItems - 1) {
    emit('next-item');
  }
};
const loadStepInfo = async () => {
  try {
    const stepResponse = await getStepInfo(props.currentDetail.orderNumber);
    const steps = Array.isArray(stepResponse.data.data)
      ? stepResponse.data.data
      : stepResponse.data.data?.steps || [];
    const finishedStep = steps.find(s => String(s.status) === '4');
    totalTime.value = finishedStep && finishedStep.total_time ? finishedStep.total_time : '';
    stepInfos.value = steps.map(step => ({
      status: String(step.status),
      name: step.name,
      time: step.time,
      create_time: step.create_time,
    }));
  } catch (error) {
    stepInfos.value = [];
  }
};
const initMap = () => {
  nextTick(() => {
    if (mapContainerRef.value && mapContainerRef.value.initAddEntity && props.currentDetail.location) {
      mapContainerRef.value.initAddEntity('point', props.currentDetail.location);
    }
  });
};
const mapStatus = (status) => {
  const statusTextMap = {
    '-1': '草稿',
    2: '待审核',
    0: '待处理',
    3: '处理中',
    4: '已完成',
  };
  return statusTextMap[status] || '未知状态';
};
const getStepHandler = (status) => {
  const step = stepInfos.value.find(step => step.status === status);
  return step ? step.name : '';
};
const getStepTime = (status) => {
  const step = stepInfos.value.find(step => step.status === status);
  return step && step.time && step.time !== '0' ? step.time : false;
};
const getStepCreateTime = (status) => {
  const step = stepInfos.value.find(step => step.status === status);
  return step ? step.create_time : null;
};
const getActiveStep = () => {
  const arr = props.stepStatusList;
  const index = arr.indexOf(String(props.currentDetail.status));
  return index !== -1 ? index + 2 : 1;
};
const handleQRCode = (val) => {
  emit('update:currentDetail', {
    ...props.currentDetail,
    showQR: !val.showQR
  });
};
const getThumbUrl = (url) => {
  if (!url) return '';
  return getSmallImg(url);
};
const getPreviewUrl = (url) => {
  if (!url) return '';
  return getShowImg(url);
};
const hasProcessingBtnPermission = () => {
  return props.permission && props.permission.tickets_processing_btn === true;
};
const hasProcessedAndOverBtnPermission = () => {
  return props.permission && props.permission.tickets_view_processedAndOver === true;
};
const hasReviewBtnPermission = () => {
  return props.permission && props.permission.tickets_review_btn === true;
};
// 文件操作方法
const handleFileChange = (file, fileList) => {
  emit('update:currentDetail', {
    ...props.currentDetail,
    photos: fileList
  });
};
const handleUploadRemove = (file, fileList) => {
  emit('update:currentDetail', {
    ...props.currentDetail,
    photos: fileList
  });
};
const beforeUpload = (file) => {
  const isImage = file.type.includes('image');
  const isLt5M = file.size / 1024 / 1024 < 5;
  if (!isImage) {
    ElMessage.error('只能上传图片文件!');
    return false;
  }
  if (!isLt5M) {
    ElMessage.error('图片大小不能超过5MB!');
    return false;
  }
  return true;
};
// 工单操作方法
const handleApprove = async () => {
  if (approveLoading.value) return;
  approveLoading.value = true;
  try {
    const data = {
      id: props.currentDetail.id,
      status: props.currentDetail.status,
      isPass: 0,
      eventNum: props.currentDetail.orderNumber,
    };
  },
  computed: {
    dialogVisible: {
      get() {
        return this.modelValue;
      },
      set(value) {
        this.$emit('update:modelValue', value);
      }
    },
    isEditMode() {
      return this.currentDetail.status === -1;
    },
    formattedDetailFields() {
      const fields = [
        { label: '工单名称', value: this.currentDetail.orderName },
        {
          label: '工单类型',
          value: this.types.find(t => t.value === this.currentDetail.type)?.label || this.currentDetail.type,
        },
        { label: '关联任务', value: this.currentDetail.job_name || '/' },
        { label: '工单创建人', value: this.currentDetail.creator },
        { label: '当前状态', value: this.mapStatus(this.currentDetail.status) },
        { label: '事件地址', value: this.currentDetail.address || this.currentDetail.latAndLon },
        {
          label: '关联算法',
          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 < filteredFields.length; i += 2) {
        formattedFields.push({
          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;
    const response = await flowEvent(data);
    if (response.data.code === 0) {
      ElMessage.success('工单已通过');
      emit('detail-success');
      handleClose();
    } else {
      throw new Error(response.data.msg || '操作失败');
    }
  },
  watch: {
    modelValue(newVal) {
      if (newVal) {
        this.loadStepInfo();
        this.initMap();
      }
    },
    currentDetail: {
      handler(newVal) {
        if (newVal && this.dialogVisible) {
          this.loadStepInfo();
          this.initMap();
        }
      },
      deep: true
  } catch (error) {
    ElMessage.error(error.message || '操作失败,请稍后重试');
    emit('detail-error', error);
  } finally {
    approveLoading.value = false;
  }
};
const handleReject = async () => {
  if (rejectLoading.value) return;
  rejectLoading.value = true;
  try {
    const data = {
      id: props.currentDetail.id,
      status: props.currentDetail.status,
      isPass: 1,
    };
    const response = await flowEvent(data);
    if (response.data.code === 0) {
      ElMessage.success('工单未通过');
      emit('detail-success');
      handleClose();
    } else {
      throw new Error(response.data.msg || '操作失败');
    }
  },
  methods: {
    handleClose() {
      this.dialogVisible = false;
    },
  } catch (error) {
    ElMessage.error(error.message || '操作失败,请稍后重试');
    emit('detail-error', error);
  } finally {
    rejectLoading.value = false;
  }
};
    handlePrev() {
      if (this.currentIndex > 0) {
        this.$emit('prev-item');
      }
    },
const handleApproveAndDispatch = () => {
  // 触发派发工单操作,由父组件处理
  emit('approve-and-dispatch', props.currentDetail);
};
    handleNext() {
      if (this.currentIndex < this.totalItems - 1) {
        this.$emit('next-item');
      }
    },
    async loadStepInfo() {
      try {
        const stepResponse = await getStepInfo(this.currentDetail.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 : '';
        this.stepInfos = steps.map(step => ({
          status: String(step.status),
          name: step.name,
          time: step.time,
          create_time: step.create_time,
        }));
      } catch (error) {
        console.error('加载步骤信息失败:', error);
        this.stepInfos = [];
      }
    },
    initMap() {
      this.$nextTick(() => {
        if (this.$refs.mapContainerRef && this.$refs.mapContainerRef.initAddEntity && this.currentDetail.location) {
          this.$refs.mapContainerRef.initAddEntity('point', this.currentDetail.location);
        }
      });
    },
    mapStatus(status) {
      const statusTextMap = {
        '-1': '草稿',
        2: '待审核',
        0: '待处理',
        3: '处理中',
        4: '已完成',
      };
      return statusTextMap[status] || '未知状态';
    },
    getStepHandler(status) {
      const step = this.stepInfos.find(step => step.status === status);
      return step ? step.name : '';
    },
    getStepTime(status) {
      const step = this.stepInfos.find(step => step.status === status);
      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;
    },
    getActiveStep() {
      const arr = this.stepStatusList;
      console.log('stepStatusList',this.stepStatusList);
      const index = arr.indexOf(String(this.currentDetail.status));
      return index !== -1 ? index + 2 : 1;
    },
    handleQRCode(val) {
      this.$emit('update:currentDetail', {
        ...this.currentDetail,
        showQR: !val.showQR
      });
    },
    getThumbUrl(url) {
      if (!url) return '';
      return getSmallImg(url);
    },
    getPreviewUrl(url) {
      if (!url) return '';
      return getShowImg(url);
    },
    hasProcessingBtnPermission() {
      return this.permission && this.permission.tickets_processing_btn === true;
    },
    hasProcessedAndOverBtnPermission() {
      return this.permission && this.permission.tickets_view_processedAndOver === true;
    },
    hasReviewBtnPermission() {
      return this.permission && this.permission.tickets_review_btn === true;
    },
    // 文件操作方法
    handleFileChange(file, fileList) {
      this.$emit('update:currentDetail', {
        ...this.currentDetail,
        photos: fileList
      });
    },
    handleUploadRemove(file, fileList) {
      this.$emit('update:currentDetail', {
        ...this.currentDetail,
        photos: fileList
      });
    },
    beforeUpload(file) {
      const isImage = file.type.includes('image');
      const isLt5M = file.size / 1024 / 1024 < 5;
      if (!isImage) {
        this.$message.error('只能上传图片文件!');
        return false;
      }
      if (!isLt5M) {
        this.$message.error('图片大小不能超过5MB!');
        return false;
      }
      return true;
    },
    // 工单操作方法
    async handleApprove() {
      if (this.approveLoading) return;
      this.approveLoading = true;
      try {
        const data = {
          id: this.currentDetail.id,
          status: this.currentDetail.status,
          isPass: 0,
          eventNum: this.currentDetail.orderNumber,
        };
        const response = await flowEvent(data);
        if (response.data.code === 0) {
          this.$message.success('工单已通过');
          this.$emit('detail-success');
          this.handleClose();
        } else {
          throw new Error(response.data.msg || '操作失败');
        }
      } catch (error) {
        this.$message.error(error.message || '操作失败,请稍后重试');
        this.$emit('detail-error', error);
      } finally {
        this.approveLoading = false;
      }
    },
    async handleReject() {
      if (this.rejectLoading) return;
      this.rejectLoading = true;
      try {
        const data = {
          id: this.currentDetail.id,
          status: this.currentDetail.status,
          isPass: 1,
        };
        const response = await flowEvent(data);
        if (response.data.code === 0) {
          this.$message.success('工单未通过');
          this.$emit('detail-success');
          this.handleClose();
        } else {
          throw new Error(response.data.msg || '操作失败');
        }
      } catch (error) {
        this.$message.error(error.message || '操作失败,请稍后重试');
        this.$emit('detail-error', error);
      } finally {
        this.rejectLoading = false;
      }
    },
    handleApproveAndDispatch() {
      // 触发派发工单操作,由父组件处理
      this.$emit('approve-and-dispatch', this.currentDetail);
    },
    async handleComplete() {
      if (this.completeLoading) return;
      this.completeLoading = true;
      try {
        if (!this.currentDetail.processingDetail) {
          this.$message.warning('请先填写事件处理详情');
          return;
        }
        if (!this.currentDetail.photos || this.currentDetail.photos.length === 0) {
          this.$message.warning('请选择上传图片');
          this.completeLoading = false;
          return;
        }
        const data = {
          id: this.currentDetail.id,
          status: this.currentDetail.status,
          processingDetails: this.currentDetail.processingDetail,
          eventNum: this.currentDetail.orderNumber,
        };
        const file = this.currentDetail.photos?.[0]?.raw || null;
        const response = await flowEvent(data, file);
        if (response.data.code === 0) {
          this.$message.success('工单已完成');
          this.$emit('detail-success');
          this.handleClose();
        } else {
          throw new Error(response.data.msg || '操作失败');
        }
      } catch (error) {
        console.error('完成工单失败:', error);
        this.$message.error(error.message || '操作失败,请稍后重试');
        this.$emit('detail-error', error);
      } finally {
        this.completeLoading = false;
      }
const handleComplete = async () => {
  if (completeLoading.value) return;
  completeLoading.value = true;
  try {
    if (!props.currentDetail.processingDetail) {
      ElMessage.warning('请先填写事件处理详情');
      return;
    }
    if (!props.currentDetail.photos || props.currentDetail.photos.length === 0) {
      ElMessage.warning('请选择上传图片');
      completeLoading.value = false;
      return;
    }
    const data = {
      id: props.currentDetail.id,
      status: props.currentDetail.status,
      processingDetails: props.currentDetail.processingDetail,
      eventNum: props.currentDetail.orderNumber,
    };
    const file = props.currentDetail.photos?.[0]?.raw || null;
    const response = await flowEvent(data, file);
    if (response.data.code === 0) {
      ElMessage.success('工单已完成');
      emit('detail-success');
      handleClose();
    } else {
      throw new Error(response.data.msg || '操作失败');
    }
      handleClose();
  } catch (error) {
    console.error('完成工单失败:', error);
    ElMessage.error(error.message || '操作失败,请稍后重试');
    emit('detail-error', error);
      handleClose();
  } finally {
    completeLoading.value = false;
      handleClose();
  }
};
</script>
<style lang="scss">
.custom-dialog {
  max-height: 95vh;
  :deep(.el-dialog__body) {
  max-height: 96vh;
 transition: max-height 0.3s ease-out;
  .el-dialog__body {
    border-top: 0.1rem solid #f0f0f0;
  }
}
.custom-qrcode-popover {
  min-width: 160px !important;
  min-height: 140px !important;
  position: relative;
  .qrcode-content {
    margin-top: 8px;
    .close-btn {
      position: absolute;
      top: 2px;
      right: 2px;
      width: 16px;
      height: 16px;
      line-height: 16px;
      text-align: center;
      cursor: pointer;
      color: #999;
      font-size: 16px;
      z-index: 10;
    }
  }
}
</style>
<style lang="scss" scoped>
.uploadImg {
  margin-bottom: 32px;
}
.tableCss {
  width: 100%;
  margin-bottom: 10px;
}
.step-timer {
  position: absolute;
@@ -777,6 +815,138 @@
  color: #666;
  font-size: 12px;
}
.event-total-time {
  text-align: center;
  color: #666;
  font-size: 15px;
  margin-bottom: 12px;
}
.el-dialog {
  .el-form-item {
    margin-bottom: 20px;
  }
}
.el-upload {
  width: 100%;
  display: flex;
  flex-wrap: wrap;
  :deep(.el-upload-list__item) {
    transition: all 0.3s ease;
  }
  :deep(.el-upload-list__item:hover) {
    background-color: #f5f7fa;
  }
}
.el-upload__tip {
  font-size: 12px;
  color: #909399;
  margin-top: 12px;
}
:deep(.el-dialog__body) {
  border-top: 0.1rem solid #f0f0f0;
}
.dialog-footer1-new {
  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;
    }
  }
}
.map-container {
  width: 100%;
  height: 400px;
  margin-bottom: 15px;
  border: 1px solid #dcdfe6;
  border-radius: 4px;
  overflow: hidden;
  :deep(.el-input-map) {
    height: 100%;
  }
}
.map-select {
  display: flex;
  align-items: flex-start;
  gap: 15px;
  .selected-location {
    flex: 1;
    padding: 5px 10px;
    background-color: #f5f7fa;
    border-radius: 4px;
    p {
      margin: 5px 0;
      color: #606266;
      font-size: 14px;
    }
  }
}
.preview-image {
  border-radius: 4px;
  overflow: hidden;
  background-color: #f5f7fa;
}
.image-placeholder,
.image-error,
.no-media {
  height: 200px;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  color: #909399;
  background-color: #f5f7fa;
  border-radius: 4px;
  i {
    font-size: 32px;
    margin-bottom: 8px;
  }
}
.no-media {
  border: 1px dashed #d9d9d9;
}
.detail-container {
  padding: 0 20px;
@@ -790,44 +960,7 @@
    }
  }
}
.event-title-center {
  text-align: center;
  font-size: 20px;
  font-weight: bold;
  margin-bottom: 5px;
  color: #333;
}
.event-total-time {
  text-align: center;
  color: #666;
  font-size: 15px;
  margin-bottom: 12px;
}
.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;
}
.status-flow {
  margin-bottom: 20px;
@@ -887,78 +1020,11 @@
    }
  }
}
.step-description {
  font-size: 14px;
  color: #666;
  line-height: 1.5;
  display: block;
  text-align: center;
  &:first-child {
    font-weight: bold;
    margin-bottom: 4px;
  }
.basic-info {
  margin-bottom: 20px;
}
.PopUpTableScrolls {
  max-height: 600px;
  overflow-y: scroll;
  overflow-x: hidden;
}
.tableCss {
  width: 100%;
  margin-bottom: 10px;
}
/* 必填项样式 */
.required-label {
  position: relative;
  display: inline-block;
  .required-star {
    color: #f56c6c;
    margin-right: 4px;
  }
}
/* 必填输入框样式 */
.required-input {
  width: 100%;
  :deep(.el-input__inner),
  :deep(.el-textarea__inner) {
    &:focus {
      border-color: #409eff;
    }
  }
}
/* 新建工单和处理工单的上传组件样式 */
.create-upload,
.detail-upload {
  :deep(.el-upload--picture-card) {
    width: 120px;
    height: 100px;
    line-height: 100px;
  }
  /* 隐藏额外的上传按钮 */
  :deep(.el-upload.el-upload--picture-card) {
    display: none;
  }
  /* 当没有图片时显示上传按钮 */
  :deep(.el-upload.el-upload--picture-card:first-child) {
    display: flex;
  }
  /* 上传组件的预览图片样式 */
  :deep(.el-upload-list--picture-card .el-upload-list__item) {
    width: 120px;
    height: 100px;
  }
}
.el-upload__tip {
  font-size: 12px;
  color: #909399;
  margin-top: 12px;
}
.media-section {
  // margin-bottom: 20px;
@@ -967,6 +1033,32 @@
    align-items: center;
  }
}
.leftBtn {
  width: 70px;
  height: 32px;
  background-color: #999;
  border-radius: 5px;
  text-align: center;
  line-height: 32px;
  color: #fff;
  cursor: pointer;
  opacity: 0.8;
}
.disableds {
  background: #999 !important;
  cursor: not-allowed !important;
  pointer-events: none;
  opacity: 0.3 !important;
}
.PopUpTableScrolls {
  max-height: 600px;
  overflow-y: scroll;
  overflow-x: hidden;
}
.media-box {
  width: 100%;
  border: 1px solid #dcdfe6;
@@ -1006,75 +1098,50 @@
    }
  }
}
.image-placeholder,
.image-error,
.no-media {
  height: 200px;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  height: 100%;
  color: #909399;
  background-color: #f5f7fa;
  border-radius: 4px;
}
  i {
    font-size: 32px;
    margin-bottom: 8px;
  }
.image-placeholder i,
.image-error i {
  font-size: 32px;
  margin-bottom: 8px;
}
.no-media {
  border: 1px dashed #d9d9d9;
}
.dialog-footer1-new {
  position: sticky;
  bottom: 28px;
  left: 0;
  right: 0;
  background: white;
  z-index: 10;
  padding: 16px 20px;
  border-top: 1px solid #ebeef5;
.info-table {
  margin-bottom: 20px;
}
.info-item {
  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;
    }
  }
}
.leftBtn {
  width: 70px;
  height: 32px;
  background-color: #999;
  border-radius: 5px;
  text-align: center;
  line-height: 32px;
  color: #fff;
  cursor: pointer;
  opacity: 0.8;
  margin-bottom: 10px;
}
.disableds {
  background: #999 !important;
  cursor: not-allowed !important;
  pointer-events: none;
  opacity: 0.3 !important;
.info-label {
  font-weight: bold;
  width: 120px;
  color: #606266;
}
.info-value {
  flex: 1;
  color: #303133;
  word-break: break-word;
}
.readonly-processing-detail {
  background-color: #f5f7fa;
  padding: 12px;
@@ -1091,4 +1158,144 @@
    margin-bottom: 4px;
  }
}
</style>
// 添加删除按钮样式
.danger-button {
  color: #f56c6c;
}
.danger-button:hover {
  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;
  :deep(.el-step__description) {
    margin-top: 8px;
    padding: 0 20px;
  }
}
.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 {
  .custom-steps {
    .el-step__description {
      position: relative;
      margin: 0;
      padding: 0;
    }
    // 移除之前的样式
    .init-step-info,
    .step-info {
      display: block;
      width: auto;
      text-align: center;
    }
  }
}
/* 新建工单和处理工单的上传组件样式 */
.create-upload,
.detail-upload {
  :deep(.el-upload--picture-card) {
    width: 120px;
    height: 100px;
    line-height: 100px;
  }
  /* 隐藏额外的上传按钮 */
  :deep(.el-upload.el-upload--picture-card) {
    display: none;
  }
  /* 当没有图片时显示上传按钮 */
  :deep(.el-upload.el-upload--picture-card:first-child) {
    display: flex;
  }
  /* 上传组件的预览图片样式 */
  :deep(.el-upload-list--picture-card .el-upload-list__item) {
    width: 120px;
    height: 100px;
  }
}
/* 原有图片预览的样式 */
.el-image-viewer__wrapper {
  :deep(.el-image-viewer__img) {
    max-width: 100%;
    max-height: 100%;
    object-fit: contain;
  }
}
/* 必填项样式 */
.required-label {
  position: relative;
  display: inline-block;
  .required-star {
    color: #f56c6c;
    margin-right: 4px;
  }
}
/* 必填输入框样式 */
.required-input {
  width: 100%;
  :deep(.el-input__inner),
  :deep(.el-textarea__inner) {
    &:focus {
      border-color: #409eff;
    }
  }
}
</style>