From 7d03329274a6a3054f3959be43423bebe08aeb64 Mon Sep 17 00:00:00 2001
From: chenyao <1219716595@qq.com>
Date: Fri, 21 Nov 2025 16:15:37 +0800
Subject: [PATCH] feat:智飞工单
---
src/views/tickets/component/AddEditDetails.vue | 383 ++++++++++++++++++++++++++++++++++++++
src/views/tickets/orderLog.vue | 169 ++++++++++++++--
src/views/tickets/orderLog1.vue | 2
src/views/tickets/component/SearchBox.vue | 1
4 files changed, 530 insertions(+), 25 deletions(-)
diff --git a/src/views/tickets/component/AddEditDetails.vue b/src/views/tickets/component/AddEditDetails.vue
new file mode 100644
index 0000000..6cc025e
--- /dev/null
+++ b/src/views/tickets/component/AddEditDetails.vue
@@ -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>
\ No newline at end of file
diff --git a/src/views/tickets/SearchBox.vue b/src/views/tickets/component/SearchBox.vue
similarity index 99%
rename from src/views/tickets/SearchBox.vue
rename to src/views/tickets/component/SearchBox.vue
index b0a82fd..aec12e8 100644
--- a/src/views/tickets/SearchBox.vue
+++ b/src/views/tickets/component/SearchBox.vue
@@ -117,7 +117,6 @@
descs: 'update_time'
}
newGetWorkspacesPage(formData).then(res => {
- console.log(res, '5555');
lineList.value = res.data.data.records
})
}
diff --git a/src/views/tickets/orderLog.vue b/src/views/tickets/orderLog.vue
index c73e4ef..d531fcc 100644
--- a/src/views/tickets/orderLog.vue
+++ b/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()
diff --git a/src/views/tickets/orderLog1.vue b/src/views/tickets/orderLog1.vue
index 6954133..925b20b 100644
--- a/src/views/tickets/orderLog1.vue
+++ b/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,
--
Gitblit v1.9.3