无人机管理后台前端(已迁走)
chenyao
2025-11-21 7d03329274a6a3054f3959be43423bebe08aeb64
feat:智飞工单
1 files renamed
2 files modified
1 files added
555 ■■■■■ changed files
src/views/tickets/component/AddEditDetails.vue 383 ●●●●● patch | view | raw | blame | history
src/views/tickets/component/SearchBox.vue 1 ●●●● patch | view | raw | blame | history
src/views/tickets/orderLog.vue 169 ●●●● patch | view | raw | blame | history
src/views/tickets/orderLog1.vue 2 ●●● patch | view | raw | blame | history
src/views/tickets/component/AddEditDetails.vue
New file
@@ -0,0 +1,383 @@
<template>
  <el-dialog
    class="ztzf-dialog-mange"
    modal-class="add-edit-details"
    v-model="isShowAddEditDetails"
    :title="txtTitle"
    width="70%"
    :close-on-click-modal="false"
    :destroy-on-close="true"
    @close="cancel"
  >
    <div class="add-edit-details">
      <div class="process">
        <div>{{ formParams.name }}}</div>
        <div class="custom-steps-container">
          <!-- 标题行 -->
          <div class="steps-titles" v-if="filters.status !== '0'">
            <div v-for="(record, index) in formParams.record_list" :class="{ active: record.user_id >= 0 }" :key="index"
                 class="step-title">
              {{ record.status_str }}
            </div>
          </div>
          <!-- Element Steps 组件 -->
          <el-steps :active="formParams.active" align-center class="custom-steps" v-if="filters.status !== '0'">
            <el-step v-for="(record, index) in formParams.record_list" :key="index">
              <template #description>
                <span class="step-description" style="position: relative; display: inline-block">
                  {{ record.user_name }}
                </span>
                <span class="step-timer">
                  {{ record.interval_time_str }}
                </span>
                <div class="step-description">
                  {{ record.create_time_str }}
                </div>
              </template>
            </el-step>
          </el-steps>
        </div>
      </div>
      <el-form ref="orderFormRef" :model="formParams" :rules="rules" label-width="100px">
        <el-row :gutter="20">
          <el-col :span="12">
            <el-form-item label="工单名称" prop="name">
              <el-input v-model="formParams.name" placeholder="请输入工单名称" maxlength="100" show-word-limit></el-input>
            </el-form-item>
          </el-col>
          <el-col :span="6">
            <el-form-item label="关联航线" prop="file_id">
              <el-select v-model="formParams.file_id" placeholder="请选择航线" filterable @change="getFlyingNest">
                <el-option v-for="item in lineList" :key="item.wayline_id" :label="item.name"
                           :value="item.wayline_id" />
              </el-select>
            </el-form-item>
          </el-col>
          <el-col :span="6">
            <el-form-item label="安全返航真高" prop="rth_altitude" class="safe-height">
              <el-input-number v-model="formParams.rth_altitude" :min="50" :max="500"></el-input-number>
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="20">
          <el-col :span="12">
            <el-form-item label="关联机巢" prop="device_sns">
              <el-select v-model="formParams.device_sns" placeholder="请选择机巢" multiple :disabled="!drone_sns.length">
                <el-option v-for="item in drone_sns" :key="item.device_sn" :label="item.nickname" :value="item.device_sn" />
              </el-select>
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="关联算法" prop="ai_types">
              <el-select v-model="formParams.ai_types" placeholder="请选择关联算法" multiple>
                <el-option v-for="item in aiTypeList" :key="item.dictKey" :label="item.dictValue" :value="item.dictKey" />
              </el-select>
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="20">
          <el-col :span="12">
            <el-form-item label="工单内容" prop="content">
              <el-input type="textarea" v-model="formParams.content" rows="4" placeholder="请输入工单内容" maxlength="255" show-word-limit></el-input>
            </el-form-item>
          </el-col>
          <el-col :span="8">
            <el-form-item label="周期频次" prop="date_range">
              <el-date-picker
                v-model="formParams.date_range"
                type="daterange"
                range-separator="至"
                start-placeholder="开始日期"
                end-placeholder="结束日期"
                :disabled-date="disabledDate" />
            </el-form-item>
          </el-col>
          <el-col :span="4">
            <div class="week_pc">
              <div class="week_pc_one week-r">
                <el-select v-model="formParams.rep_fre_type" placeholder="请选择频次">
                  <el-option v-for="item in weeks" :key="item" :label="item" :value="item" />
                </el-select>
              </div>
              <div class="week_pc_one">
                <el-time-picker style="width: 100px" v-model="formParams.deal_time" prop="deal_time" value-format="HH:mm"
                                :picker-options="{
                    selectableRange: '00:00 - 23:59',
                  }" />
              </div>
            </div>
          </el-col>
        </el-row>
        <el-row :gutter="20" style="height: 400px">
          <el-col :span="24">
            <map-container v-if="isShowAddEditDetails" ref="mapContainerRef"></map-container>
          </el-col>
        </el-row>
      </el-form>
    </div>
    <template #footer v-if="txtTitle === '新建工单'">
      <div class="dialog-footer">
        <el-button type="danger" @click="submitForm(1)" icon="el-icon-position">发起</el-button>
        <el-button type="primary" @click="submitForm(0)" icon="el-icon-document-add">存草稿</el-button>
        <el-button @click="cancel" icon="el-icon-circle-close">取消</el-button>
      </div>
    </template>
    <template #footer v-if="txtTitle === '工单审核'">
      <div class="dialog-footer">
        <el-button type="primary"
                   v-if="formParams.status == 1 && permission.order_log_review"
                   @click="orderPass(formParams.id)">通过
        </el-button>
        <el-button type="danger"
                   v-if="formParams.status == 1 && permission.rejection_btn"
                   @click="orderReject(formParams.id)">驳回
        </el-button>
        <el-button @click="cancel" icon="el-icon-circle-close">取消</el-button>
      </div>
    </template>
  </el-dialog>
</template>
<script setup>
import { ref, shallowRef, onMounted, computed } from 'vue';
import { newGetWorkspacesPage } from '@/api/resource/wayline'
import { getDictionaryByCode } from '@/api/system/dictbiz'
import { getFlyingNestBy } from '@/api/device/device'
import {
  saveUpdateOrderLog,
  orderLogDetails,
  orderLogPass,
  orderLogReject,
  orderLogRecall
} from '@/api/tickets/orderLog';
import dayjs from 'dayjs'
import 'dayjs/locale/zh-cn' // 导入中文语言包
import _ from 'lodash'
import { ElMessage, ElMessageBox } from 'element-plus';
import weekday from 'dayjs/plugin/weekday'
dayjs.extend(weekday)
dayjs.locale('zh-cn')
import { useStore } from 'vuex'
const emit = defineEmits(['refresh']);
const store = useStore()
const userInfo = computed(() => store.state.user.userInfo)
const permission = computed(() => store.state.user.permission);
const txtTitle = inject('txtTitle')
const rowObj = inject('rowObj')
const isShowAddEditDetails = defineModel('show')
const mapContainerRef = ref(null)
const orderFormRef = ref(null)
const formParams = ref({})
const rules = ref({
  name: [
    { required: true, message: '请输入工单名称', trigger: 'blur' },
    { max: 100, message: '工单名称不能超过100个字', trigger: 'blur' }
  ],
    file_id: [{ required: true, message: '需要选择航线', trigger: 'change' }],
    device_sns: [{ required: true, message: '请选择机巢', trigger: 'change' }],
    content: [
    { required: true, message: '请输入工单内容', trigger: 'blur' },
    { max: 255, message: '工单内容不能超过255个字', trigger: 'blur' }
  ],
    date_range: [{ required: true, message: '请选择时间不能为空', trigger: 'blur' }],
    rep_fre_type: [{ required: true, message: '请选择周期', trigger: 'blur' }],
    deal_time: [{ required: true, message: '请选择执行时间', trigger: 'blur' }]
})
//周期
const weeks = ref(['每天', '周一', '周二', '周三', '周四', '周五', '周六', '周末', '周天', '工作日'])
// 获取航线
let lineList = ref([])
function getLines() {
  const formData = {
    name: '',
    waylineType: undefined,
    current: 1,
    size: 99999999,
    descs: 'update_time'
  }
  newGetWorkspacesPage(formData).then(res => {
    lineList.value = res.data.data.records
  })
}
// 关联机巢
const drone_sns = ref([])
function getFlyingNest (waylineId) {
  // 选中航线对象
  initMapLine({}, polygon => {
    const currentLine = lineList.value.find(item => item.wayline_id === waylineId)
    const params = {
      type: ['2', '4', '5'].includes(currentLine.wayline_type) ? 2 : 0,
      wayline_id: waylineId,
      polygon
    }
    getFlyingNestBy(params).then(res => {
      drone_sns.value = res.data.data
      initMapLine()
    })
  })
}
// 获取算法
let aiTypeList = ref([])
function aitypeList() {
  getDictionaryByCode('SF').then(res => {
    aiTypeList.value = res.data.data['SF']
  })
}
// initMapLine 方法
const initMapLine = async (infos = {}, cb = () => {}) => {
  const currentLine = lineList.value.find(item => item.wayline_id == formParams.value.file_id)
  if (!currentLine) return
  await nextTick()
  if (mapContainerRef.value?.initAddEntity) {
    mapContainerRef.value.initAddEntity('polyline', {
      url: `${import.meta.env.VITE_APP_AIRLINE_URL + currentLine.object_key}?_t=${new Date().getTime()}`,
      type: currentLine.wayline_type,
      cb,
      infos
    })
  }
}
function formatDate (date) {
  if (!date) return undefined
  const d = new Date(date)
  return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(
    d.getDate()
  ).padStart(2, '0')} 00:00:00`
}
function submitForm(status) {
  orderFormRef.value?.validate(async (valid, fields) => {
    if (valid) {
      console.log('表单验证通过')
      formParams.value.begin_time = formatDate(formParams.value.date_range[0])
      formParams.value.end_time = formatDate(formParams.value.date_range[1])
      // 如果选中日期包含当前天,那么选中的时间点不能小于当前时间
      if (formParams.value.deal_time) {
        const selectedDate = dayjs(formParams.value.date_range[0]).format('YYYY-MM-DD')
        const selectedTime = dayjs(selectedDate + ' ' + formParams.value.deal_time).toDate()
        const now = new Date()
        if (selectedDate === dayjs().format('YYYY-MM-DD') && selectedTime < now) {
          return ElMessage({message: '任务时间不能小于当前时间', type: 'warning'})
        }
      }
      const submitData = {
        ...formParams.value,
        status: status,
        ai_types: formParams.value.ai_types?.length ? formParams.value.ai_types : []
      }
      await saveUpdateOrderLog(submitData)
      if (txtTitle === '新建工单') {
        ElMessage({message: '工单创建成功', type: 'success'})
      } else {
        ElMessage({message: '工单发布成功', type: 'success'})
      }
      cancel()
    } else {
      console.log('表单验证失败', fields)
    }
  })
}
// 审核通过
function orderPass(id) {
  orderLogPass(id).then(res => {
    ElMessage.success('审核通过')
    cancel()
  })
}
// 驳回
const orderReject = async (id) => {
  try {
    const { value } = await ElMessageBox.prompt('', '驳回原因', {
      confirmButtonText: '确定',
      cancelButtonText: '取消',
      inputPlaceholder: '请输入驳回原因',
      inputValidator: (value) => {
        if (!value) {
          return '驳回原因不能为空'
        }
      }
    })
    // 用户输入了内容并点击确定
    await orderLogReject(id, value)
    ElMessage.success('驳回成功')
    cancel()
  } catch (error) {
    // 用户点击取消或关闭弹窗
    if (error !== 'cancel' && error !== 'close') {
      ElMessage.error('驳回失败')
      console.error('驳回错误:', error)
    }
  }
}
// 监听txtTitle
watch(txtTitle, async (newVal, oldVal) => {
  console.log('txtTitle 发生变化:', newVal, oldVal)
  if (newVal === '工单审核') {
    const response = await orderLogDetails(rowObj.value.id)
    const data = response.data.data
    formParams.value = {
      ...data
    }
    drone_sns.value = data.device_list
    initMapLine(data.device_map_infos)
  }
}, {
  immediate: true // 立即执行一次
})
function cancel() {
  isShowAddEditDetails.value = false
  emit('refresh');
}
onMounted(async () => {
  aitypeList()
  getLines()
});
</script>
<style lang="scss" scoped>
.add-edit-details {
  .safe-height {
    :deep(.el-form-item__label) {
      width: 120px !important;
    }
    :deep(.el-input-number) {
      width: 180px !important;
    }
  }
  .week_pc {
    display: flex;
  }
  .week_pc_one {
    flex: 1;
    .week-r {
      margin-right: 4px;
    }
    &> ::v-deep(div) {
      width: 100% !important;
    }
  }
}
</style>
src/views/tickets/component/SearchBox.vue
File was renamed from src/views/tickets/SearchBox.vue
@@ -117,7 +117,6 @@
    descs: 'update_time'
  }
  newGetWorkspacesPage(formData).then(res => {
    console.log(res, '5555');
    lineList.value = res.data.data.records
  })
}
src/views/tickets/orderLog.vue
@@ -4,7 +4,7 @@
    <el-tabs v-model="activeTab" @tab-click="handleTabChange">
      <el-tab-pane v-for="tab in tabsList" :key="tab.name" :label="`${tab.label} (${tab.count})`" :name="tab.name">
        <SearchBox @search="searchClick"></SearchBox>
        <el-button v-if="hasAddBtnPermission() && activeTab != 'WAIT_AUDIT'" type="primary" icon="el-icon-plus" @click="handleAddOrder">新建工单
        <el-button v-if="permission?.order_log_add && activeTab != 'WAIT_AUDIT'" type="primary" icon="el-icon-plus" @click="handleAddOrder">新建工单
        </el-button>
        <el-button type="success" plain icon="el-icon-download" @click="exportData">导出
        </el-button>
@@ -41,27 +41,27 @@
            <el-table-column prop="cycle_time_value" label="工单周期频次" align="center" show-overflow-tooltip />
            <el-table-column label="操作" width="200" align="center">
              <template #default="scope">
                <template v-if="scope.row.status == 1">
                  <el-button v-if="hasPaddingBtnPermission()" type="text" icon="el-icon-view" @click="handleCheckDetail(row)">审核</el-button>
                <template v-if="scope.row.status === 1">
                  <el-button type="text" icon="el-icon-view" v-if="permission.order_log_review" @click="handleCheckDetail(scope.row, '工单审核')">审核</el-button>
                  <!--待审核状态-->
                  <el-button type="text" icon="el-icon-warning" v-if="hasRecallPaddingBtnPermission()" @click="orderLogRecall(scope.row.id)">撤回
                  <el-button type="text" icon="el-icon-warning" v-if="permission.order_log_recall" @click="revokeOrder(scope.row.id)">撤回
                  </el-button>
                </template>
                <!--已驳回-->
                <template v-if="scope.row.status == 2">
                  <el-button type="text" icon="el-icon-warning" @click="rejectDetail(scope.row.id)">驳回原因</el-button>
                  <el-button v-if="userInfo.user_id == row.create_user" type="text" icon="el-icon-view" @click="handleViewDetail(scope.row)">编辑</el-button>
                  <el-button v-else type="text" icon="el-icon-view" @click="handleViewDetail(scope.row)">详情</el-button>
                <template v-if="scope.row.status === 2">
                  <el-button type="text" icon="el-icon-warning" @click="rejectReason(scope.row.id)">驳回原因</el-button>
                  <el-button type="text" icon="el-icon-view" v-if="userInfo.user_id === scope.row.create_user" @click="handleViewDetail(scope.row, '工单编辑')">编辑</el-button>
                  <el-button type="text" icon="el-icon-view" v-else @click="handleViewDetail(scope.row, '工单详情')">详情</el-button>
                </template>
                <!-- 已通过 -->
                <template v-if="scope.row.status == 3">
                  <el-button type="text" icon="el-icon-view" @click="handleViewDetail(scope.row)">详情</el-button>
                <template v-if="scope.row.status === 3">
                  <el-button type="text" icon="el-icon-view" @click="handleViewDetail(scope.row, '工单详情')">详情</el-button>
                </template>
                <!--草稿-->
                <template v-if="scope.row.status == 0">
                  <el-button type="text" icon="el-icon-edit" @click="handleViewDetail(scope.row)">编辑</el-button>
                <template v-if="scope.row.status === 0">
                  <el-button type="text" icon="el-icon-edit" @click="handleViewDetail(scope.row, '工单编辑')">编辑</el-button>
                  <el-button type="text" icon="el-icon-position" @click="userPublishPush(scope.row.id)">发布</el-button>
                  <el-button type="text" icon="el-icon-delete" @click="deleteOrderLog(scope.row.id)">删除</el-button>
                  <el-button type="text" icon="el-icon-delete" @click="deleteOrder(scope.row.id)">删除</el-button>
                </template>
              </template>
            </el-table-column>
@@ -76,11 +76,15 @@
      </el-tab-pane>
    </el-tabs>
  </div>
  <AddEditDetails v-model:show="isShowAddEditDetails" @refresh="refreshGetOrderList" />
</template>
<script setup>
import SearchBox from './SearchBox.vue'
import SearchBox from './component/SearchBox.vue'
import AddEditDetails from './component/AddEditDetails.vue'
import NProgress from 'nprogress'
import { useStore } from 'vuex'
import { downloadXls } from '@/utils/util'
import {
  getList,
  saveUpdateOrderLog,
@@ -96,12 +100,16 @@
} from '@/api/tickets/orderLog'
import { cloneDeep } from 'lodash';
import { computed, onMounted } from 'vue';
import { ElMessage, ElMessageBox } from 'element-plus'
const store = useStore()
const userInfo = computed(() => store.state.user.userInfo)
const permission = computed(() => store.state.user.permission);
console.log(permission, '8888')
console.log(userInfo,permission, '8888')
const isShowAddEditDetails = ref(false)
const activeTab = ref('all')
// tab切换
@@ -132,7 +140,6 @@
    },
  }
  getList(apiParams).then(res => {
    console.log(res.data.data.records,'99999999')
    orderListTable.value = res.data.data.records
    total.value = res.data.data.total
  })
@@ -158,20 +165,136 @@
  getTableList()
}
const refreshGetJobList = () => {
const refreshGetOrderList = () => {
  orderListParams.current = 1
  orderListParams.size = 10
  getTableList()
}
// 新增按钮 和 导出按钮
function hasAddBtnPermission () {
  // undefined 或 false 都返回 false,只有 true 返回 true
  console.log(permission?.value.order_log_add, '66666')
  return permission && permission?.value.order_log_add
// function hasAddBtnPermission () {
//   return permission && permission?.value.order_log_add
// }
// 新建工单、编辑、详情
const txtTitle = ref('新建工单')
const rowObj = ref({})
provide('txtTitle', txtTitle)
provide('rowObj', rowObj)
function handleAddOrder() {
  isShowAddEditDetails.value = true
}
// 新建工单
function handleAddOrder() {}
// 详情、编辑
function handleViewDetail(row, txt) {
  txtTitle.value = txt
  isShowAddEditDetails.value = true
}
// 审核
function handleCheckDetail(row, txt) {
  rowObj.value = row
  txtTitle.value = txt
  isShowAddEditDetails.value = true
}
// 导出
async function exportData() {
  try {
    await ElMessageBox.confirm('是否导出智飞工单数据?', '提示', {
      confirmButtonText: '确定',
      cancelButtonText: '取消',
      type: 'warning',
    })
    NProgress.start()
    const apiParams = {
      ...cloneDeep(orderListParams),
      searchParams: {
        ...cloneDeep(orderListParams.searchParams)
      },
    }
    const res = await orderLogExport(apiParams)
    downloadXls(res.data, `智飞工单${dayjs().format('YYYY-MM-DD')}.xlsx`)
    ElMessage.success('导出成功')
  } catch (error) {
    if (error !== 'cancel') {
      ElMessage.error('导出失败')
      console.error('导出错误:', error)
    }
  } finally {
    NProgress.done()
  }
}
// 撤回工单
async function revokeOrder(id) {
  try {
    await ElMessageBox.confirm('确定撤回到草稿箱?', '是否撤回', {
      confirmButtonText: '确定',
      cancelButtonText: '取消',
      type: 'warning'
    })
    // 用户点击确定,执行撤回操作
    await orderLogRecall(id)
    // 显示成功消息
    ElMessage.success('撤回成功')
    // 触发搜索刷新
    refreshGetOrderList()
  } catch (error) {
    // 用户点击取消,不执行任何操作
    if (error !== 'cancel') {
      ElMessage.error('撤回失败')
      console.error('撤回错误:', error)
    }
  }
}
// 驳回原因
async function rejectReason(id) {
  try {
    const res = await orderLogDetails(id)
    const data = res.data.data
    await ElMessageBox.confirm(data.remark, '驳回原因', {
      confirmButtonText: '确定',
      cancelButtonText: '取消',
      type: 'warning'
    })
    // 用户点击确定后的逻辑 TODO
    // form.value = { ...res.data.data }
    // handleViewDetail(data)
  } catch (error) {
    // 用户点击取消,不执行任何操作
    if (error !== 'cancel') {
      ElMessage.error('操作失败')
      console.error('错误:', error)
    }
  }
}
// 删除
async function deleteOrder(id) {
  try {
    await ElMessageBox.confirm('确定删除吗?', '提示', {
      confirmButtonText: '确定',
      cancelButtonText: '取消',
      type: 'warning'
    })
    deleteOrderLog(id)
    ElMessage.success('删除成功')
    // 触发搜索刷新
    refreshGetOrderList()
  } catch (error) {
    if (error !== 'cancel') {
      ElMessage.error('删除失败')
      console.error('删除错误:', error)
    }
  }
}
onMounted(() => {
  getTableList()
src/views/tickets/orderLog1.vue
@@ -56,7 +56,7 @@
</template>
<script setup>
import SearchBox from './SearchBox.vue'
import SearchBox from './component/SearchBox.vue'
import {
  getList,
  saveUpdateOrderLog,