日常巡检专题

main
zhufu 2025-12-12 16:30:25 +08:00
parent aabb073e48
commit a1eba4faf3
6 changed files with 695 additions and 1 deletions

View File

@ -0,0 +1,14 @@
import { defHttp } from '@/utils/http/axios';
enum Api {
LoadRcxjCaseinfo = '/api/DroneCaseInfoRcjg/LoadRcxjCaseinfo',
UpdateStatus = '/api/DroneCaseInfoRcjg/UpdateStatus',
}
export function LoadRcxjCaseinfo(params){
return defHttp.get({ url: Api.LoadRcxjCaseinfo, params });
}
export function UpdateStatus(id){
return defHttp.post({
url: `${Api.UpdateStatus}?id=${id}`,
});
}

View File

@ -1778,6 +1778,12 @@
createMessage.error('请传入操作类型!');
}
};
const handlerShowPoint = (lng, lat) => {
new mapboxgl.Marker({
color: 'red' //
}).setLngLat([lng, lat]).addTo(map);
handlerLocation([lng,lat])
}
const currentGeoJson = ref({});
const editGeoJson = ref({})
@ -2359,6 +2365,7 @@
handlerCancleDraw,
handlerLoadPictureAzimuth,
handlerCurrentImageChange,
handlerShowPoint,
});
</script>

View File

@ -0,0 +1,395 @@
<template>
<div class="detail-container">
<div class="map-container" v-if="!props.hiddenInfoMap">
<MapboxMap
:caseno="props.showInfoData.case_no"
:countyname="props.showInfoData.countyname"
:imageList="imageList"
:geomsList="geomsList"
:mapConfig="mapConfig"
@handlerDrawComplete="handlerDrawComplete"
@mapOnLoad="onMapboxLoad"
ref="MapboxComponent"
/>
</div>
<div class="info-container" id="info-container">
<a-descriptions
:column="2"
bordered
:contentStyle="{
'text-align': 'center',
'min-width': '250px',
'word-break': 'break-all',
}"
>
<a-descriptions-item label="线索编号">
{{ props.showInfoData.case_no }}
</a-descriptions-item>
<a-descriptions-item label="区县">
{{ props.showInfoData.countyname }}
</a-descriptions-item>
<a-descriptions-item label="乡镇">
{{ props.showInfoData.streetname }}
</a-descriptions-item>
<a-descriptions-item label="社区/村">
{{ props.showInfoData.communityname }}
</a-descriptions-item>
<a-descriptions-item label="线索类型">
{{ getLabel('typename', props.showInfoData.typename) }}
</a-descriptions-item>
<a-descriptions-item label="线索来源">
{{ getLabel('tubanlaiyuan', props.showInfoData.tubanlaiyuan) }}
</a-descriptions-item>
<a-descriptions-item label="上报时间">
{{ props.showInfoData.createtime }}
</a-descriptions-item>
<a-descriptions-item label="经度">
{{ props.showInfoData.lng }}
</a-descriptions-item>
<a-descriptions-item label="纬度">
{{ props.showInfoData.lat }}
</a-descriptions-item>
<a-descriptions-item label="线索描述">
{{ props.showInfoData.case_description }}
</a-descriptions-item>
<a-descriptions-item label="线索状态">
{{ getLabel('biaozhu', props.showInfoData.biaozhu) }}
</a-descriptions-item>
<a-descriptions-item label="线索照片" :span="2">
<div class="image-div">
<a-image-preview-group
:preview="{
getContainer: getContainer,
onVisibleChange: handlerImageChange,
}"
>
<template v-for="(imageItem, imageIndex) in casepicList" :key="imageIndex">
<a-image
v-if="imageItem"
width="100px"
height="100px"
:src="`${VITE_GLOB_INFO_IMAGE_URL}/${imageItem}`"
@click="handlerPreviewImage(imageIndex, imageItem)"
:preview="{
getContainer,
}"
></a-image>
</template>
</a-image-preview-group>
</div>
</a-descriptions-item>
</a-descriptions>
<a-button type="primary" @click="confirm"></a-button>
</div>
<!-- File Preview && Download Start -->
<a-modal
v-model:open="previewFileModalVisible"
style="width: 100vw"
title="文件预览"
wrap-class-name="full-modal"
>
<FilePreview v-if="previewFileModalVisible" :fileUrl="previewFileUrl"></FilePreview>
<template #footer>
<a-button key="cancel" @click="handleCancelPreviewFile"></a-button>
<a-button key="confirm" type="primary" @click="handlerDownloadFle"></a-button>
</template>
</a-modal>
<!--File Preview && Download End -->
</div>
</template>
<script setup lang="ts">
import { defineProps, defineEmits, ref, computed, onBeforeMount, watch, onMounted } from 'vue';
import MapboxMap from '@/components/MapboxMaps/MapComponent.vue';
import { getConfig } from '@/api/sys/layerManagement';
import { getGeom } from '@/api/sys/layerManagement';
import { getLoadCaseImgList } from '@/api/facilityfarm';
import { UpdateStatus } from '@/api/dailycheck';
import { useMessage } from '@/hooks/web/useMessage';
import axios from 'axios';
const { createMessage } = useMessage();
import Icon from '@/components/Icon/Icon.vue';
import { getAppEnvConfig } from '@/utils/env';
const { VITE_GLOB_INFO_IMAGE_URL } = getAppEnvConfig();
import dayjs from 'dayjs';
const MapboxComponent = ref();
const mapConfig = ref({});
const props = defineProps(['showInfoData','hiddenInfoMap', 'typenameOptions', 'tubanlaiyuanOptions']);
const emits = defineEmits(['reload']);
const activeKey = ref('1');
const geomsList = ref();
const imageList = ref([]);
console.log('props', props.showInfoData);
watch(
() => props.showInfoData,
() => {
getConfig({ code: 'mapsetting' }).then((res) => {
mapConfig.value = JSON.parse(res.codeValue);
});
changeTask();
},
);
async function getCaseImgList() {
imageList.value = await getLoadCaseImgList({
caseid: props.showInfoData.id,
category: '日常巡检',
});
MapboxComponent.value.handlerLoadPictureAzimuth(imageList.value);
//
// let zhengshiImageList = [];
// imageList.value?.forEach((item,index)=>{
// let obj = anjianzhaopianList.value?.find((it,idx)=>{
// return item.filePath == it;
// })
// if(obj){
// zhengshiImageList.push(imageList.value[index]);
// }
// })
}
function handlerDealFileName(path) {
const regex = /([^/\\]+)(?=\.[^/\\]*$|$)/;
const matchStr = path.match(regex);
if (matchStr?.length) {
return matchStr[0];
}
}
function handlerPreviewImage(index, url) {
const regex = /([^/\\]+)(?=\.[^/\\]*$|$)/;
const match = url.match(regex);
if (match) {
MapboxComponent.value.handlerCurrentImageChange(match[1]);
}
}
const isInitImageLisener = ref<Boolean>(false);
//
function handlerImageChange(e): void {
isInitImageLisener.value = false;
if (e && !isInitImageLisener.value) {
setTimeout(function () {
const targetNode = document.getElementsByClassName('ant-image-preview-img');
targetNode?.forEach((node, index) => {
let imageObserver = new MutationObserver((mutationsList) => {
for (const mutation of mutationsList) {
if (node.getAttribute(mutation.attributeName).match('http')) {
handlerPreviewImage(0, node.getAttribute(mutation.attributeName));
}
}
});
const config = { attributes: true };
imageObserver.observe(node, config);
isInitImageLisener.value = true;
});
}, 250);
}
}
onBeforeMount(() => {
getCaseImgList()
});
onMounted(() => {
if(props.showInfoData.lng && props.showInfoData.lat){
MapboxComponent.value.handlerShowPoint(props.showInfoData.lng, props.showInfoData.lat);
}
})
const casepicList = computed(() => {
return props.showInfoData.casepic ? props.showInfoData.casepic.split(',') : [];
});
function onMapboxLoad() {
changeTask();
}
const getLabel = (type, value) => {
let result: any[] = [];
let label = '';
switch (type) {
case 'typename':
result = props.typenameOptions;
break;
case 'tubanlaiyuan':
result = props.tubanlaiyuanOptions;
break;
case 'biaozhu':
result = [
{ label: '未确认', value: 0},
{ label: '已确认', value: 1},
];
break;
}
result.forEach((item) => {
if (item.value == value) {
label = item.label;
}
});
return label;
};
async function changeTask() {
let getGeomPrams = {
TableName: 'drone_shp_data ',
FieldName: 'gid',
FieldValue: props.showInfoData.geomid?.split(','),
page: 1,
limit: 999,
key: null,
};
if (props.showInfoData.geomid) {
getGeom(getGeomPrams).then((res) => {
let geoms = [];
if (res) {
if (res.items?.length > 0) {
res.items.forEach((item, index) => {
let geom = {
key: item.gid,
mapgeom: item.geometry,
};
geoms.push(geom);
geomsList.value = geoms;
});
}
// MapboxComponent.value.handlerDraw(status,mapgemoList.value, false);
MapboxComponent.value.handlerDraw('Details', geoms, false);
} else {
geomsList.value = null;
createMessage.error('当前数据没有线索!');
}
});
} else {
createMessage.error('当前数据没有线索!');
}
}
import FilePreview from '@/components/Upload/src/components/FilePreview.vue';
import { message } from 'ant-design-vue';
const previewFileModalVisible = ref(false);
const previewFileUrl = ref('');
const hanlderPreViewFile = (url) => {
previewFileUrl.value = `${VITE_GLOB_INFO_IMAGE_URL}/${url}`;
previewFileModalVisible.value = true;
};
const handlerDownloadFle = () => {
window.open(previewFileUrl.value, 'mozillaTab');
};
const handleCancelPreviewFile = () => {
previewFileModalVisible.value = false;
};
///////
const getContainer = () => {
return document.getElementById('info-container');
};
const dataProcessing = (value) => {
if (!value) {
return '0';
}
if (value.indexOf('.') == -1) {
return value;
} else {
if (value.split('.')[1].length <= 2) {
return value;
}
let resultString = value.replace('㎡', '');
return Number(resultString).toFixed(2);
}
};
const showImage = (url) => {
if (url.indexOf('.png') !== -1 || url.indexOf('.jpg') !== -1 || url.indexOf('.jpeg') !== -1) {
return true;
} else {
return false;
}
};
const confirm = () => {
UpdateStatus(props.showInfoData.id).then(res => {
message.success('确认成功')
emits('reload')
})
}
</script>
<style lang="scss" scoped>
.image-div {
min-width: 340px;
max-height: 220px;
overflow: auto;
}
.detail-container {
width: 100%;
height: calc(100vh - 120px);
display: flex;
padding: 0px 20px;
}
.detail-container::after {
content: '';
display: block;
clear: both;
height: 0;
visibility: none;
}
.map-container {
float: left;
width: 45vw;
height: calc(100vh - 100px);
margin-right: 20px;
}
:deep(.ant-image) {
margin-right: 10px;
margin-bottom: 10px;
}
:deep(.ant-image-preview-switch-left) {
position: absolute;
}
:deep(.ant-image-preview-switch-right) {
position: absolute;
}
.info-container {
// float: left;
position: relative;
flex: 1;
display: flex;
flex-flow: column;
justify-content: space-between;
:deep(.ant-image-preview-wrap) {
position: absolute;
}
:deep(.ant-image-preview-mask) {
position: absolute;
}
:deep(.ant-image-preview-operations-wrapper) {
height: 100%;
position: absolute;
.ant-image-preview-operations {
position: absolute;
top: 0;
width: 100%;
.ant-image-preview-operations-operation {
// flex:1;
}
}
.ant-image-preview-operations-operation:nth-last-child(1) {
display: none;
}
.ant-image-preview-operations-operation:nth-last-child(2) {
display: none;
}
}
}
::v-deep .ant-tabs .ant-tabs-content-holder {
overflow: auto;
height: 80vh;
overflow: auto;
padding-right: 10px;
}
</style>

View File

@ -0,0 +1,122 @@
<template>
<PageWrapper dense contentFullHeight fixedHeight contentClass="flex">
<BasicTable @register="registerTable">
<template #bodyCell="{ column, record, text }">
<template v-if="column.key === 'action'">
<TableAction
:actions="[
{
label: '详情',
onClick: () => {
handleInfo(record);
},
}
]"
/>
</template>
<template v-if="['typename','tubanlaiyuan','biaozhu'].includes(column.dataIndex)">
{{ getLabel(column.dataIndex, text) }}
</template>
</template>
</BasicTable>
<a-modal
style="width: 100vw; top: 0px; left: 0px; margin: 0px; padding: 0px"
wrap-class-name="full-modal"
v-model:open="showInfoOpen"
title="详情"
:footer="null"
:maskClosable="true"
:destroyOnClose="true"
@cancel="showInfoOpen = false"
>
<ShowInfoModal
:showInfoData="showInfoData"
:typenameOptions="typenameOptions"
:tubanlaiyuanOptions="tubanlaiyuanOptions"
@reload="reloadList"
/>
</a-modal>
</PageWrapper>
</template>
<script setup lang="ts">
import { ref } from "vue"
import { BasicTable, useTable, TableAction } from '@/components/Table';
import { columns, searchFormSchema, typenameOptions, tubanlaiyuanOptions } from './utils';
import { LoadRcxjCaseinfo } from '@/api/dailycheck/index'
import ShowInfoModal from '@/views/demo/dailycheck/ShowInfoModal/index.vue'
const showInfoOpen = ref(false)
const showInfoData = ref();
const [registerTable, { getForm, reload }] = useTable({
title: '日常巡检',
api: LoadRcxjCaseinfo,
columns,
rowKey: 'id',
useSearchForm: true,
showTableSetting: true,
bordered: true,
formConfig: {
labelWidth: 120,
schemas: searchFormSchema,
showAdvancedButton: false, // /
alwaysShowLines: 0,
},
actionColumn: {
width: 80,
title: '操作',
dataIndex: 'action',
fixed: 'right',
},
});
function handleInfo(record) {
showInfoData.value = record
showInfoOpen.value = true
}
const getLabel = (type, value) => {
let result: any[] = [];
let label = '';
switch (type) {
case 'typename':
result = typenameOptions;
break;
case 'tubanlaiyuan':
result = tubanlaiyuanOptions;
break;
case 'biaozhu':
result = [
{ label: '未确认', value: 0},
{ label: '已确认', value: 1},
];
break;
}
result.forEach((item) => {
if (item.value == value) {
label = item.label;
}
});
return label;
};
const reloadList = () => {
reload()
}
</script>
<style lang="scss" scoped>
.full-modal {
.ant-modal {
min-width: 100vw;
top: 0px;
padding: 0px;
margin: 0px;
}
.ant-modal-content {
display: flex;
flex-direction: column;
}
.ant-modal-body {
flex: 1;
}
}
</style>

View File

@ -0,0 +1,156 @@
import { BasicColumn, FormSchema } from '@/components/Table';
import { getChildrenTree } from '@/api/demo/system';
import { getLoad } from '@/api/sys/sysDataItemDetail';
import { asyncGetOptions } from '@/utils/global'
import dayjs from 'dayjs';
export const typenameOptions = await asyncGetOptions('rcxjxslx');
export const tubanlaiyuanOptions = await asyncGetOptions('rcxjxsly');
export const columns: BasicColumn[] = [
{
title: '线索编号',
dataIndex: 'case_no',
width: 240
},
{
title: '县区',
dataIndex: 'countyname',
width: 100
},
{
title: '乡镇',
dataIndex: 'streetname',
width: 100
},
{
title: '社区/村',
dataIndex: 'communityname',
},
{
title: '线索类型',
dataIndex: 'typename',
},
{
title: '线索来源',
dataIndex: 'tubanlaiyuan',
},
{
title: '上报时间',
dataIndex: 'createtime',
},
{
title: '经度',
dataIndex: 'lng',
},
{
title: '纬度',
dataIndex: 'lat',
},
{
title: '线索描述',
dataIndex: 'case_description',
},
{
title: '线索状态',
dataIndex: 'biaozhu',
},
];
export const searchFormSchema: FormSchema[] = [
{
field: 'typename',
component: 'ApiSelect',
colProps: { span: 5 },
label: '线索类型',
componentProps: ({ formModel }) => {
return {
api: getLoad,
params: { code: 'rcxjxslx' },
resultField: 'result',
labelField: 'itemName',
valueField: 'itemValue',
};
},
},
{
field: 'tubanlaiyuan',
component: 'ApiSelect',
colProps: { span: 5 },
label: '线索来源',
componentProps: ({ formModel }) => {
return {
api: getLoad,
params: { code: 'rcxjxsly' },
resultField: 'result',
labelField: 'itemName',
valueField: 'itemValue',
};
},
},
{
field: 'countyid',
label: '区县',
component: 'ApiSelect',
colProps: { span: 5 },
componentProps: ({ formModel }) => {
return {
api: getChildrenTree,
params: { parentId: 371300 },
// 接口参数
resultField: 'result',
labelField: 'name',
valueField: 'id',
onChange: () => {
formModel.streetid = '';
},
};
},
},
{
field: 'streetid',
label: '乡镇',
component: 'ApiSelect',
colProps: { span: 5 },
componentProps: ({ formModel }) => {
return {
api: formModel.countyid && getChildrenTree,
params: { parentId: formModel.countyid },
// 接口参数
resultField: 'result',
labelField: 'name',
valueField: 'id',
placeholder: '请先选择区县',
};
},
},
{
field: 'biaozhu',
label: '线索状态',
component: 'Select',
colProps: { span: 5 },
componentProps: {
options: [
{ label: '未确认', value: 0},
{ label: '已确认', value: 1},
],
},
},
{
field: '[synchronoustimebegin, synchronoustimeend]',
label: '上报时间',
component: 'RangePicker',
colProps: { span: 5 },
componentProps: {
format: 'YYYY-MM-DD',
placeholder: ['开始日期', '结束日期'],
},
},
{
field: 'case_no',
label: '线索编号',
component: 'Input',
colProps: { span: 5 },
},
];

View File

@ -103,7 +103,7 @@
lng: string;
lat: string;
}
const LocationShow = ref<Boolean>(true);
const LocationShow = ref<Boolean>(false);
const locationArrays = ref<LocationItem[]>([{ lng: '',lat: '', }]);
const locationDrawArrays = ref<LocationItem[]>();
const locationGeoJson = reactive({