| applications/drone-command/src/views/areaManage/sceneManage/FormDiaLog.vue | ●●●●● patch | view | raw | blame | history | |
| applications/drone-command/src/views/areaManage/sceneManage/index.vue | ●●●●● patch | view | raw | blame | history | |
| applications/drone-command/src/views/areaManage/sceneManage/sceneConfigApi.js | ●●●●● patch | view | raw | blame | history | |
| uniapps/work-app/src/subPackages/workDetail/index.vue | ●●●●● patch | view | raw | blame | history |
applications/drone-command/src/views/areaManage/sceneManage/FormDiaLog.vue
New file @@ -0,0 +1,356 @@ <template> <el-dialog class="command-page-map-view-dialog" v-model="visible" :show-close="false" :close-on-click-modal="false"> <div class="dialog-container"> <div class="left-container"> <CommonCesiumMap ref="mapRef" class="leftMap command-cesium" :active="visible" :flat-mode="false" :terrain="true" :layer-mode="4" :boundary="false" /> </div> <div class="right-container"> <div class="header"> <span>{{ titleEnum[dialogMode] }}</span> <el-icon class="close-btn" @click.stop="visible = false"><Close /></el-icon> </div> <div class="content" v-if="readonly"> <div class="detail-title">场景详情</div> <el-row> <el-col :span="24"> <div class="label">场景名称</div> <div class="val">{{ formData.sceneName }}</div> </el-col> <el-col :span="24"> <div class="label">场景类型</div> <div class="val">{{ getDictLabel(formData.sceneType, dictObj.CommandSceneType) }}</div> </el-col> <el-col :span="24"> <div class="label">设备模式</div> <div class="val">{{ getDictLabel(formData.deviceMode, dictObj.deviceMode) }}</div> </el-col> </el-row> <div class="detail-title">关联区域</div> <div class="command-table-container"> <div class="command-table-content"> <el-table class="command-table" ref="areaTableRef" :data="areaList" row-key="id"> <el-table-column prop="areaName" label="区域名称" /> <el-table-column prop="areaType" label="区域类型"> <template v-slot="{ row }"> {{ getDictLabel(row.areaType, dictObj.areaType) }} </template> </el-table-column> </el-table> </div> </div> </div> <el-form class="dialog-form" v-else ref="formRef" :model="formData" :rules="rules" :disabled="readonly" label-width="96px" > <el-form-item label="场景名称" prop="sceneName"> <el-input class="command-input" v-model="formData.sceneName" maxlength="50" placeholder="请输入" clearable /> </el-form-item> <el-form-item label="场景类型" prop="sceneType"> <el-select class="command-select" popper-class="command-select-popper" v-model="formData.sceneType" placeholder="请选择" clearable > <el-option v-for="item in dictObj.CommandSceneType" :key="item.dictKey" :label="item.dictValue" :value="item.dictKey" /> </el-select> </el-form-item> <el-form-item label="设备模式" prop="deviceMode"> <el-select class="command-select" popper-class="command-select-popper" v-model="formData.deviceMode" placeholder="请选择" clearable > <el-option v-for="item in dictObj.deviceMode" :key="item.dictKey" :label="item.dictValue" :value="item.dictKey" /> </el-select> </el-form-item> <div class="search-table-container"> <div class="search-box"> <div class="label">关联区域</div> <el-input class="command-input" v-model="searchName" placeholder="请输入" clearable></el-input> </div> <el-form-item prop="areaDivideIds" label-width="0"> <el-table class="command-table" ref="areaTableRef" :data="areaList.filter(item => item.areaName.includes(searchName))" row-key="id" @selection-change="handleAreaSelectionChange" > <el-table-column type="selection" width="55" :reserve-selection="true" /> <el-table-column prop="areaName" show-overflow-tooltip label="区域名称" /> <el-table-column prop="areaType" show-overflow-tooltip label="区域类型"> <template v-slot="{ row }"> {{ getDictLabel(row.areaType, dictObj.areaType) }} </template> </el-table-column> </el-table> </el-form-item> </div> </el-form> <div class="footer"> <el-button v-if="dialogMode != 'view'" color="#2B2B4C" @click="handleCancel"> {{ readonly ? '关闭' : '取消' }} </el-button> <el-button color="#284FE3" v-if="!readonly" type="primary" :loading="submitting" :disabled="submitting" @click="handleSubmit" > 确定 </el-button> </div> </div> </div> </el-dialog> </template> <script setup> import { Close } from '@element-plus/icons-vue' import { computed, inject, nextTick, onMounted, ref, watch } from 'vue' import { ElMessage } from 'element-plus' import { fwDefenseSceneDetailApi, fwDefenseSceneSubmitApi } from './sceneConfigApi' import { fieldRules, geomAnalysis, getDictLabel } from '@ztzf/utils' import CommonCesiumMap from '@/components/map-container/common-cesium-map.vue' import * as Cesium from 'cesium' import { fwAreaDivideDetailApi, fwAreaDivideListApi } from '../partition/partitionApi' import { DrawPolygon } from '@/utils/cesium/DrawPolygon' import commandPost from '@/assets/images/dataCockpit/legend/command-post.png' import { saveOperationLog } from '@ztzf/apis' import { useRoute } from 'vue-router' const initForm = () => ({ sceneName: '', // 场景名称 sceneType: '', // 场景类型 deviceMode: '', // 设备模式 areaDivideIds: '', // 关联区域ID areaCount: 0, // 区域数量 }) const emit = defineEmits(['success']) const formRef = ref(null) // 表单实例 const formData = ref(initForm()) // 表单数据 const visible = defineModel() // 弹框显隐 const dialogMode = ref('add') // 弹框模式 const submitting = ref(false) // 提交中 const readonly = computed(() => dialogMode.value === 'view') const titleEnum = ref({ edit: '编辑', view: '查看', add: '新增' }) const areaTableRef = ref(null) const searchName = ref('') const areaList = ref([]) // 关联区域列表 const selectedAreaRows = ref([]) //选中列表 const dictObj = inject('dictObj') const mapRef = ref(null) const route = useRoute() let viewer let redPointEntity let leftClickBound = false const rules = { sceneName: fieldRules(true, 50), sceneType: fieldRules(true), deviceMode: fieldRules(true), areaDivideIds: fieldRules(false), } // 关闭弹框 function handleCancel() { visible.value = false } // 提交新增/编辑 async function handleSubmit() { const isValid = await formRef.value?.validate().catch(() => false) if (!isValid) return submitting.value = true try { const ids = selectedAreaRows.value.map(item => item.id) formData.value.areaDivideIds = ids.join(',') formData.value.areaCount = ids.length await fwDefenseSceneSubmitApi(formData.value) saveOperationLog({ requestUri: route.path, title: `${route.name || '场景配置管理'}-${dialogMode.value === 'add' ? '新增' : '编辑'}`, type: 1, }) ElMessage.success(dialogMode.value === 'add' ? '新增成功' : '更新成功') visible.value = false emit('success') } finally { submitting.value = false } } // 加载详情 async function loadDetail() { if (!formData.value.id) return const res = await fwDefenseSceneDetailApi({ id: formData.value.id }) formData.value = res?.data?.data ?? initForm() } // 获取区域列表 async function getAreaList() { if (areaList.value.length) return const res = await fwAreaDivideListApi({ filterSelected: 1, sceneId: formData.value.id }) areaList.value = res?.data?.data ?? [] } // 关联区域变更 async function handleAreaSelectionChange(rows) { const selectedRows = await ensureAreaGeom(rows) selectedAreaRows.value = selectedRows const ids = selectedRows.map(item => item.id) formData.value.areaDivideIds = ids.join(',') formData.value.areaCount = ids.length } // 渲染面 function renderingSurface() { if (!geometricSource) { initMap() } if (!geometricSource) return geometricSource && geometricSource.entities.removeAll() selectedAreaRows.value.forEach(item => { if (item.geom) { const pointList = geomAnalysis(item.geom) const result = pointList.map(item => [item.longitude, item.latitude]).flat() geometricSource.entities?.add({ customType: 'control_group', position: Cesium.Cartesian3.fromDegrees(result[0], result[1]), polyline: { positions: Cesium.Cartesian3.fromDegreesArray(result), clampToGround: true, width: 3, material: Cesium.Color.RED, }, }) } }) } watch(() => selectedAreaRows.value, renderingSurface) async function ensureAreaGeom(rows) { const missingRows = rows.filter(row => row?.id && !row.geom) if (!missingRows.length) return rows const detailList = await Promise.all( missingRows.map(row => fwAreaDivideDetailApi({ id: row.id }) .then(res => res?.data?.data) .catch(() => null) ) ) const detailMap = new Map(detailList.filter(Boolean).map(item => [String(item.id), item])) if (!detailMap.size) return rows areaList.value = areaList.value.map(item => { const detail = detailMap.get(String(item.id)) return detail ? { ...item, ...detail } : item }) return rows.map(item => { const detail = detailMap.get(String(item.id)) return detail ? { ...item, ...detail } : item }) } // 同步关联区域 function syncAreaSelection() { if (!areaTableRef.value) return areaTableRef.value.clearSelection() const rows = [] const areaDivideIds = formData.value.areaDivideIds const arr = Array.isArray(areaDivideIds) ? areaDivideIds.map(item => String(item)) : String(areaDivideIds || '') .split(',') .filter(Boolean) areaList.value.forEach(row => { if (arr.includes(String(row.id))) { areaTableRef.value.toggleRowSelection(row, true) rows.push(row) } }) selectedAreaRows.value = rows if (!rows.length) return formData.value.areaCount = rows.length } let geometricSource // 初始化地图实例 function initMap() { if (viewer) return const map = mapRef.value?.getMap() if (!map?.viewer) return viewer = map.viewer if (!geometricSource) { geometricSource = new Cesium.CustomDataSource('geometricSource') viewer.dataSources.add(geometricSource) } } // 打开弹框 async function open({ mode, row } = {}) { dialogMode.value = mode || 'add' formData.value = dialogMode.value === 'add' ? initForm() : row selectedAreaRows.value = [] redPointEntity = null await nextTick() initMap() await getAreaList() if (dialogMode.value !== 'add') { await loadDetail() } await nextTick() syncAreaSelection() } defineExpose({ open, }) </script> <style scoped lang="scss"></style> applications/drone-command/src/views/areaManage/sceneManage/index.vue
New file @@ -0,0 +1,195 @@ <template> <basic-container> <el-form ref="queryParamsRef" :model="searchParams" class="command-page-history-search"> <el-form-item label="场景名称" prop="sceneName"> <el-input class="command-input" v-model="searchParams.sceneName" placeholder="请输入" clearable @clear="handleSearch" /> </el-form-item> <el-form-item label="场景类型" prop="sceneType"> <el-select class="command-select" popper-class="command-select-popper" v-model="searchParams.sceneType" placeholder="请选择" clearable @change="handleSearch" > <el-option v-for="item in dictObj.CommandSceneType" :key="item.dictKey" :label="item.dictValue" :value="item.dictKey" /> </el-select> </el-form-item> <el-form-item class="history-search-actions"> <el-button :icon="RefreshRight" @click="resetForm"></el-button> <el-button class="search-btn" :icon="Search" @click="handleSearch"></el-button> </el-form-item> </el-form> <div class="command-table-toolbar"> <el-button :icon="Plus" color="#284FE3" type="primary" @click="openForm('add')">新增</el-button> <el-button :icon="Delete" color="#1A2652" :disabled="!selectedIds.length" @click="handleDelete()">删除</el-button> </div> <div class="command-table-container" v-loading="loading" element-loading-background="rgba(5, 5, 15, 0.6)"> <div class="command-table-content command-table-content-bg"> <el-table class="command-table" :data="list" @selection-change="handleSelectionChange"> <el-table-column type="selection" width="46" /> <el-table-column type="index" show-overflow-tooltip width="64" label="序号" /> <el-table-column prop="sceneName" show-overflow-tooltip label="场景名称" /> <el-table-column prop="sceneType" show-overflow-tooltip label="场景类型"> <template v-slot="{ row }"> {{ getDictLabel(row.sceneType, dictObj.CommandSceneType) }} </template> </el-table-column> <el-table-column prop="areaCount" show-overflow-tooltip label="区域数量" /> <el-table-column prop="counterDeviceCount" show-overflow-tooltip label="反制设备数量" /> <el-table-column prop="detectDeviceCount" show-overflow-tooltip label="探测设备数量" /> <el-table-column label="操作" class-name="operation-btns"> <template v-slot="{ row }"> <el-link @click="openForm('view', row)">查看</el-link> <el-link @click="openForm('edit', row)">编辑</el-link> <el-link @click="handleDelete(row)">删除</el-link> </template> </el-table-column> </el-table> </div> <div class="command-table-pagination"> <el-pagination popper-class="command-select-popper" v-model:current-page="searchParams.current" v-model:page-size="searchParams.size" layout="total, prev, pager, next, sizes" :total="total" @change="getList" /> </div> </div> <FormDiaLog v-if="dialogVisible" v-model="dialogVisible" ref="dialogRef" @success="getList" /> </basic-container> </template> <script setup> import { Search, RefreshRight, Plus, Delete } from '@element-plus/icons-vue' import { nextTick, onMounted, provide, ref } from 'vue' import { ElMessage, ElMessageBox } from 'element-plus' import { fwDefenseScenePageApi, fwDefenseSceneRemoveApi } from './sceneConfigApi' import FormDiaLog from './FormDiaLog.vue' import { getDictionaryByCode } from '@/api/system/dictbiz' import { getDictLabel } from '@ztzf/utils' import { saveOperationLog } from '@ztzf/apis' import { useRoute } from 'vue-router' const initSearchParams = () => ({ sceneName: '', // 场景名称 sceneType: '', // 场景类型 current: 1, // 当前页 size: 10, // 每页大小 }) const searchParams = ref(initSearchParams()) // 查询参数 const total = ref(0) // 总条数 const loading = ref(true) // 列表加载中 const list = ref([]) // 列表数据 const selectedIds = ref([]) // 勾选的ID列表 const queryParamsRef = ref(null) // 查询表单实例 const dialogRef = ref(null) // 弹框实例 const dialogVisible = ref(false) const route = useRoute() // 获取列表 async function getList() { loading.value = true try { const res = await fwDefenseScenePageApi(searchParams.value) list.value = res?.data?.data?.records ?? [] total.value = res?.data?.data?.total ?? 0 } finally { loading.value = false } } // 查询 function handleSearch() { searchParams.value.current = 1 getList() } // 重置查询 function resetForm() { queryParamsRef.value?.resetFields() searchParams.value.current = 1 getList() } // 删除 async function handleDelete(row) { const tips = row ? '该条' : '选中的项' await ElMessageBox.confirm(`确认删除${tips}吗?`, '提示', { type: 'warning', customClass: 'command-page-view-message-box', confirmButtonClass: 'command-message-box-confirm', cancelButtonClass: 'command-message-box-cancel', }) const ids = row ? row.id : selectedIds.value.join(',') await fwDefenseSceneRemoveApi({ ids }) saveOperationLog({ requestUri: route.path, title: `${route.name || '场景配置管理'}-删除`, type: 1, }) ElMessage.success('删除成功') selectedIds.value = [] getList() } // 勾选值设置 function handleSelectionChange(rows) { selectedIds.value = rows.map(item => item.id) } function formatLocation(row) { if (row?.longitude == null || row?.latitude == null) return '' return `${_.round(row.longitude, 6)}, ${_.round(row.latitude, 6)}` } const dictObj = ref({ sceneType: [], // 场景类型 deviceMode: [], // 设备模式 areaType: [], // 区域类型 }) provide('dictObj', dictObj) // 获取字典 function getDictList() { getDictionaryByCode('CommandSceneType,deviceMode,areaType').then(res => { dictObj.value = res.data.data }) } // 新增/编辑/查看 弹框 function openForm(mode, row) { dialogVisible.value = true nextTick(() => { dialogRef.value?.open({ mode, row }) }) } onMounted(() => { getList() getDictList() }) </script> <style scoped lang="scss"></style> applications/drone-command/src/views/areaManage/sceneManage/sceneConfigApi.js
New file @@ -0,0 +1,46 @@ import request from '@/axios' // 查page export const fwDefenseScenePageApi = params => { return request({ url: `/drone-fw/area/fwDefenseScene/page`, method: 'get', params: { descs: 'update_time', ...params }, }) } // 查list export const fwDefenseSceneListApi = params => { return request({ url: `/drone-fw/area/fwDefenseScene/list`, method: 'get', params, }) } // 增加或更新 export const fwDefenseSceneSubmitApi = data => { return request({ url: `/drone-fw/area/fwDefenseScene/submit`, method: 'post', data, }) } //删除 export const fwDefenseSceneRemoveApi = params => { return request({ url: `/drone-fw/area/fwDefenseScene/remove`, method: 'post', params, }) } //详情 export const fwDefenseSceneDetailApi = params => { return request({ url: `/drone-fw/area/fwDefenseScene/detail`, method: 'get', params, }) } uniapps/work-app/src/subPackages/workDetail/index.vue
@@ -4,7 +4,8 @@ <!-- 自定义导航栏 --> <u-navbar title="工单详情" :is-back="true" back-text="" :back-icon-size="40" @left-click="onBackClick"> <template #right> <div class="share-btn" @click="onShareClick"></div> <div v-if="isH5" class="tapShare-icon"><img src="/static/images/work/share.svg" @click="onShareClick"/></div> <div v-else class="share-btn" @click="onShareClick"></div> </template> </u-navbar> <div class="detailTop"> @@ -71,7 +72,7 @@ <up-button type="primary" color="#1D6FE9" text="确认" @click="confirmTheTicket"></up-button> </div> <div class="btngroups" v-if="workDetailData.eventStatus === 3"> <up-button type="primary" color="#1D6FE9" text="已确认" ></up-button> <up-button type="primary" color="#1D6FE9" text="已确认" @click="confirmedHandle"></up-button> </div> </div> <!-- 分享弹出层 --> @@ -102,7 +103,6 @@ <script setup> // import { getShowImg, getSmallImg } from '@/utils/util' import { ref, computed } from 'vue' import {getGddetailedData,backGdApi} from '/src/api/work/index.js' import dayjs from 'dayjs' const formatDate = dateString => { @@ -111,9 +111,12 @@ const eventNum = ref('') // 工单内容 const workDetailData = ref({}) // 环境判断 const isH5 = ref(false) onLoad(async options => { eventNum.value = options.id await getDataList(options.id) checkEnvironment() }) const getDataList = async val => { const params = { @@ -122,6 +125,19 @@ const res = await getGddetailedData(params) const response = res.data.data workDetailData.value = response } // 检查运行环境 const checkEnvironment = () => { const systemInfo = uni.getSystemInfoSync() // #ifdef APP-PLUS isH5.value = false // #endif // #ifdef H5 isH5.value = true // #endif console.log('当前环境:', isH5.value ? 'H5' : 'App') } // 图片预览 const previewImage = index => { @@ -180,6 +196,10 @@ uni.navigateBack() }) }) } // 已确认 const confirmedHandle = () => { uni.navigateBack() } // 分享模态框状态 const showShareModal = ref(false) @@ -312,12 +332,22 @@ } } // 分享按钮样式 .share-btn { // #ifdef APP-PLUS width: 40rpx; height: 40rpx; background-image: url('/static/images/work/share.svg'); background-size: contain; background-repeat: no-repeat; background-position: center; // #endif } .tapShare-icon { width: 40rpx; height: 40rpx; img { width: 40rpx; height: 40rpx; } } .detailTop { // #ifdef APP-PLUS