Compare commits

...

2 Commits

6 changed files with 961 additions and 443 deletions

View File

@ -1,7 +1,7 @@
<template>
<base-dialog
v-model:visible="props.visible"
title="涉灾隐患点情况"
:title="props.title"
:table-data="tableData"
:table-columns="tableColumns"
:table-height="450"
@ -32,17 +32,26 @@
</div>
</div>
<div class="info-row">
<div class="info-item">
<span class="info-label">路线名称</span>
<span class="info-value">{{ hazardData.roadName }}</span>
</div>
<div class="info-item">
<span class="info-label">公路编号</span>
<span class="info-value">{{ hazardData.roadCode }}</span>
</div>
</div>
<div class="info-row">
<div class="info-item">
<span class="info-label">位置</span>
<span class="info-value">{{ hazardData.location }}</span>
</div>
<div class="info-item">
<span class="info-label">风险类型</span>
<span class="info-value">{{ hazardData.riskType }}</span>
</div>
</div>
</div>
<!-- 风险描述 -->
<div class="info-block">
<div class="block-title">风险描述</div>
@ -55,8 +64,14 @@
<div class="block-content">{{ hazardData.measures }}</div>
</div>
<!-- 专家意见 - 仅涉灾隐患点显示 -->
<div v-if="!isRoadData && hazardData.expertOpinion" class="info-block">
<div class="block-title">专家意见</div>
<div class="block-content">{{ hazardData.expertOpinion }}</div>
</div>
<!-- 三级包保责任人 -->
<div class="info-block">
<div class="info-block display jc_sb">
<div class="block-title">三级包保责任人</div>
<div class="responsibility-list">
<div class="responsibility-item">
@ -64,59 +79,122 @@
<span class="responsibility-name">{{ hazardData.trafficDept.name }}</span>
<span class="responsibility-phone">{{ hazardData.trafficDept.phone }}</span>
<span class="responsibility-frequency">{{ hazardData.trafficDept.frequency }}</span>
<div class="action-btns">
<div class="action-btn" @click="handleVideo(hazardData.trafficDept)" title="视频">
<el-icon><VideoCamera /></el-icon>
</div>
<div class="action-btn" @click="handleVoice(hazardData.trafficDept)" title="语音">
<el-icon><Microphone /></el-icon>
</div>
<div class="action-btn" @click="handleCall(hazardData.trafficDept)" title="电话">
<el-icon><Phone /></el-icon>
</div>
</div>
<span class="fs_12">半年巡查一次</span>
</div>
<div class="responsibility-item">
<span class="responsibility-label">公路机构责任人</span>
<span class="responsibility-name">{{ hazardData.roadOrg.name }}</span>
<span class="responsibility-phone">{{ hazardData.roadOrg.phone }}</span>
<span class="responsibility-frequency">{{ hazardData.roadOrg.frequency }}</span>
<div class="action-btns">
<div class="action-btn" @click="handleVideo(hazardData.roadOrg)" title="视频">
<el-icon><VideoCamera /></el-icon>
</div>
<div class="action-btn" @click="handleVoice(hazardData.roadOrg)" title="语音">
<el-icon><Microphone /></el-icon>
</div>
<div class="action-btn" @click="handleCall(hazardData.roadOrg)" title="电话">
<el-icon><Phone /></el-icon>
</div>
</div>
<span class="fs_12">每月巡查一次</span>
</div>
<div class="responsibility-item">
<span class="responsibility-label">养护站责任人</span>
<span class="responsibility-name">{{ hazardData.maintenance.name }}</span>
<span class="responsibility-phone">{{ hazardData.maintenance.phone }}</span>
<span class="responsibility-frequency">{{ hazardData.maintenance.frequency }}</span>
<div class="action-btns">
<div class="action-btn" @click="handleVideo(hazardData.maintenance)" title="视频">
<el-icon><VideoCamera /></el-icon>
</div>
<div class="action-btn" @click="handleVoice(hazardData.maintenance)" title="语音">
<el-icon><Microphone /></el-icon>
</div>
<div class="action-btn" @click="handleCall(hazardData.maintenance)" title="电话">
<el-icon><Phone /></el-icon>
</div>
</div>
<span class="fs_12">每周巡查一次</span>
</div>
</div>
</div>
<!-- 护路员 -->
<div class="info-row simple-row">
<span class="row-label">护路员</span>
<span class="row-value name">{{ hazardData.roadKeeper.name }}</span>
<span class="row-value phone">{{ hazardData.roadKeeper.phone }}</span>
<span class="row-value frequency">{{ hazardData.roadKeeper.frequency }}</span>
</div>
<div class="info-block display jc_sb">
<div class="block-title">护路员:</div>
<!-- 预警预报关 -->
<div class="info-row simple-row">
<div class="f1 display ai_center jc_end" style="gap: 8px">
<span class="responsibility-name">{{ hazardData.roadKeeper.name }}</span>
<span class="responsibility-name" style="width: 100px;">{{ hazardData.roadKeeper.phone }}</span>
<span class="responsibility-frequency">{{ hazardData.roadKeeper.frequency }}</span>
<div class="action-btns">
<div class="action-btn" @click="handleVideo(hazardData.roadKeeper)" title="视频">
<el-icon><VideoCamera /></el-icon>
</div>
<div class="action-btn" @click="handleVoice(hazardData.roadKeeper)" title="语音">
<el-icon><Microphone /></el-icon>
</div>
<div class="action-btn" @click="handleCall(hazardData.roadKeeper)" title="电话">
<el-icon><Phone /></el-icon>
</div>
</div>
<span class="fs_12">一周巡查两次</span>
</div>
</div>
<!-- 预警预报关 - 仅涉灾隐患点显示 -->
<div v-if="!isRoadData" class="info-row simple-row">
<span class="row-label">预警预报关</span>
<span class="row-value">{{ hazardData.earlyWarning }}</span>
<!-- <span class="row-value">{{ hazardData.earlyWarning }}</span> -->
</div>
<!-- 线下巡查关 -->
<div class="info-row simple-row">
<!-- 线下巡查关 - 仅涉灾隐患点显示 -->
<div v-if="!isRoadData" class="info-row simple-row">
<span class="row-label">线下巡查关</span>
<span class="row-value">{{ hazardData.offlinePatrol }}</span>
<!-- <span class="row-value">{{ hazardData.offlinePatrol }}</span> -->
</div>
<!-- 交通管控关 -->
<div class="info-row simple-row">
<!-- 交通管控关 - 仅涉灾隐患点显示 -->
<div v-if="!isRoadData" class="info-row simple-row">
<span class="row-label">交通管控关</span>
<span class="row-value">{{ hazardData.trafficControl }}</span>
<!-- <span class="row-value">{{ hazardData.trafficControl }}</span> -->
</div>
<!-- 力量预置关 -->
<div class="info-row simple-row">
<!-- 力量预置关 - 仅涉灾隐患点显示 -->
<div v-if="!isRoadData" class="info-row simple-row">
<span class="row-label">力量预置关</span>
<span class="row-value">{{ hazardData.forcePreposition }}</span>
<!-- <span class="row-value">{{ hazardData.forcePreposition }}</span> -->
<el-icon class="location-icon"><Location /></el-icon>
</div>
<!-- 告警阻拦关 -->
<div class="info-row simple-row">
<!-- 告警阻拦关 - 仅涉灾隐患点显示 -->
<div v-if="!isRoadData" class="info-row simple-row">
<span class="row-label">告警阻拦关</span>
<span class="row-value">{{ hazardData.alarmBlocking }}</span>
<!-- <span class="row-value">{{ hazardData.alarmBlocking }}</span> -->
</div>
<!-- 备注 - 仅涉灾隐患点显示 -->
<div v-if="!isRoadData && hazardData.remark" class="info-block display jc_sb">
<div class="block-title">备注</div>
<!-- <div class="block-content">{{ hazardData.remark }}</div> -->
</div>
<!-- 照片 - 路段数据展示 -->
<div v-if="isRoadData && hazardData.photos" class="info-block display jc_sb">
<div class="block-title">照片</div>
<div class="photo-list">
<img v-for="(photo, index) in hazardData.photos" :key="index" :src="photo" class="photo-item" @click="previewPhoto(photo)" />
</div>
</div>
</div>
</template>
@ -124,9 +202,10 @@
</template>
<script setup>
import { ref, watch } from 'vue';
import { Location } from '@element-plus/icons-vue';
import baseDialog from '../component/baseDialog.vue';
import { ref, watch, computed } from 'vue'
import { Location, VideoCamera, Microphone, Phone } from '@element-plus/icons-vue'
import baseDialog from '../component/baseDialog.vue'
import { openVideoConference } from '../component/index.js'
const props = defineProps({
visible: {
@ -137,16 +216,25 @@ const props = defineProps({
type: Object,
default: () => ({}),
},
});
title: {
type: String,
default: '涉灾隐患点情况',
},
})
const emit = defineEmits(['update:visible', 'close']);
const emit = defineEmits(['update:visible', 'close', 'video', 'voice', 'call'])
//
const isRoadData = computed(() => {
return props.data?.dataType === 'road'
})
// 使
const tableColumns = ref([]);
const tableData = ref([]);
const total = ref(0);
const currentPage = ref(1);
const pageSize = ref(10);
const tableColumns = ref([])
const tableData = ref([])
const total = ref(0)
const currentPage = ref(1)
const pageSize = ref(10)
//
const hazardData = ref({
@ -204,44 +292,73 @@ const hazardData = ref({
//
longitude: '', // GL1_LON
latitude: '', // GL1_LAT
});
//
photos: [], //
})
//
const handleClose = () => {
emit('update:visible', false);
emit('close');
};
emit('update:visible', false)
emit('close')
}
//
const handleSizeChange = val => {
pageSize.value = val;
};
const handleSizeChange = (val) => {
pageSize.value = val
}
const handleCurrentChange = val => {
currentPage.value = val;
};
const handleCurrentChange = (val) => {
currentPage.value = val
}
//
const previewPhoto = (photo) => {
// 使 Element Plus
const { preview } = require('element-plus')
preview(photo)
}
//
const handleVideo = (item) => {
console.log('视频通话:', item)
emit('video', {
...item,
id: hazardData.value.id,
})
}
//
const handleVoice = (item) => {
console.log('语音通话:', item)
emit('voice', item)
}
//
const handleCall = (item) => {
console.log('拨打电话:', item)
emit('call', item)
}
// visible
watch(
() => props.visible,
newVal => {
(newVal) => {
if (newVal && props.data) {
//
const data = props.data;
const data = props.data
hazardData.value = {
//
district: data.GL1_QXMC || data.district || '',
riskLevel: data.GL1_FXDJ || data.riskLevel || '',
roadCode: data.GL1_GLBH || data.roadCode || '',
roadName: data.GL1_GLMC || data.roadName || '',
location:
data.GL1_QDZH && data.GL1_ZDZH
? `${data.GL1_QDZH}${data.GL1_ZDZH}`
: data.location || '',
location: data.GL1_QDZH && data.GL1_ZDZH ? `${data.GL1_QDZH}${data.GL1_ZDZH}` : data.location || '',
riskDescription: data.GL1_FXMS || data.riskDescription || '',
riskType: data.GL1_FXLX || data.riskType || '',
measures: data.GL1_CQCS || data.measures || '',
isWithinRedLine: data.GL1_SFHXN || data.isWithinRedLine || '',
id: data.GL1_ID || '',
//
trafficDept: {
@ -286,17 +403,20 @@ watch(
//
longitude: data.GL1_LON || data.longitude || '',
latitude: data.GL1_LAT || data.latitude || '',
};
//
photos: data.photos || [],
}
}
},
{ immediate: true }
);
{ immediate: true },
)
</script>
<style lang="scss" scoped>
.hazard-info-panel {
height: 500px;
width: 600px;
width: 650px;
color: rgba(255, 255, 255, 0.9);
overflow-y: auto;
//
@ -342,7 +462,6 @@ watch(
}
.row-value {
flex: 1;
color: rgba(255, 255, 255, 0.9);
font-size: 14px;
@ -399,6 +518,7 @@ watch(
padding: 12px;
background: rgba(64, 169, 255, 0.05);
border-radius: 4px;
// align-items: center;
.block-title {
color: rgba(255, 255, 255, 0.6);
@ -411,6 +531,41 @@ watch(
font-size: 14px;
line-height: 1.6;
}
.responsibility-label {
color: rgba(255, 255, 255, 0.6);
font-size: 13px;
flex-shrink: 0;
}
.responsibility-name {
color: #40a9ff;
font-size: 14px;
flex-shrink: 0;
}
//
.action-btns {
display: flex;
flex-shrink: 0;
display: flex;
align-items: center;
.action-btn {
width: 24px;
height: 24px;
display: flex;
align-items: center;
justify-content: center;
color: #40a9ff;
cursor: pointer;
font-size: 14px;
transition: all 0.3s;
border-radius: 4px;
&:hover {
color: #40a9ff;
background: rgba(64, 169, 255, 0.1);
}
}
}
}
.responsibility-list {
@ -427,24 +582,101 @@ watch(
width: 140px;
color: rgba(255, 255, 255, 0.6);
font-size: 13px;
flex-shrink: 0;
}
.responsibility-name {
color: #40a9ff;
font-size: 14px;
width: 60px;
flex-shrink: 0;
}
.responsibility-phone {
color: #40a9ff;
font-size: 14px;
width: 120px;
width: 100px;
flex-shrink: 0;
}
.responsibility-frequency {
color: rgba(255, 255, 255, 0.6);
font-size: 13px;
flex: 1;
min-width: 0;
}
//
.action-btns {
display: flex;
flex-shrink: 0;
display: flex;
align-items: center;
.action-btn {
width: 24px;
height: 24px;
display: flex;
align-items: center;
justify-content: center;
color: #40a9ff;
cursor: pointer;
font-size: 14px;
transition: all 0.3s;
border-radius: 4px;
&:hover {
color: #40a9ff;
background: rgba(64, 169, 255, 0.1);
}
}
}
}
}
//
.simple-row {
.action-btns {
display: flex;
margin-left: auto;
.action-btn {
width: 24px;
height: 24px;
display: flex;
align-items: center;
justify-content: center;
color: #40a9ff;
cursor: pointer;
font-size: 14px;
transition: all 0.3s;
border-radius: 4px;
&:hover {
color: #40a9ff;
background: rgba(64, 169, 255, 0.1);
}
}
}
}
//
.photo-list {
display: flex;
flex-wrap: wrap;
gap: 10px;
.photo-item {
width: 100px;
height: 100px;
object-fit: cover;
border-radius: 4px;
cursor: pointer;
border: 1px solid rgba(64, 169, 255, 0.3);
transition: all 0.3s ease;
&:hover {
border-color: #40a9ff;
transform: scale(1.05);
}
}
}

View File

@ -68,6 +68,21 @@
<el-option v-for="option in regionOptions" :key="option.value" :label="option.label" :value="option.value" />
</el-select>
</div>
<div class="filter-item">
<el-select
:teleported="false"
v-model="filterForm.roadType"
size="small"
placeholder="公路类型"
class="filter-select"
clearable
@change="handleFilterChange"
>
<el-option v-for="option in roadTypeOptions" :key="option.value" :label="option.label" :value="option.value" />
</el-select>
</div>
<div class="filter-item">
<el-button type="primary" class="search-btn" @click="handleSearch">查询</el-button>
</div>
@ -179,7 +194,7 @@
import { ref, computed, watch, onMounted, nextTick } from 'vue'
import { Close, ArrowLeft, ArrowRight } from '@element-plus/icons-vue'
import { request } from '@/utils/request'
import { pointTypeOptions, pointLevelOptions, regionOptions } from '../component/index.js'
import { pointTypeOptions, pointLevelOptions, regionOptions, roadTypeOptions } from '../component/index.js'
import baseDialog from '../component/baseDialog.vue'
import respondedIcon from '../../../assets/xiangying/有回应@2x.png'
@ -213,6 +228,7 @@ const filterForm = ref({
pointType: '',
pointLevel: '',
region: '',
roadType: '',
})
// 使
@ -481,6 +497,7 @@ const getTimeParams = () => {
offset: (currentPage.value - 1) * pageSize.value,
countyName: countyName || '',
riskLevel: filterForm.value.pointLevel || '',
roadTypes: filterForm.value.roadType || '',
}
}
//
@ -791,6 +808,7 @@ watch(
district: '',
type: '',
roadConditionType: '',
roadType: '',
}
}
},

View File

@ -103,6 +103,7 @@ const emit = defineEmits([
'riskPointStatsChange',
'update:roadvalArr',
'openHazardPointSituation',
'openRoadSectionSituation',
]);
//
@ -846,6 +847,9 @@ const addProjectMarkers = (data, iconUrl, type = 'project') => {
if (type === 'riskPoint') {
//
emit('openHazardPointSituation', item);
} else if (type === 'road') {
//
emit('openRoadSectionSituation', item);
} else {
openMapInfoDialog(type, item);
}

View File

@ -1,9 +1,15 @@
// 弹窗组件统一数据配置
import { ref } from 'vue';
import { request } from '@/utils/request';
import { ref } from 'vue'
import { request } from '@/utils/request'
import { ElMessage } from 'element-plus'
// 公路类型选项
export const roadTypeOptions = ref([
{ label: '全部', value: '' },
{ label: '国省道', value: 'G,S' },
{ label: '农村公路', value: ' X, Y, C' },
])
// 影响区域选项
export const regionOptions = ref([]);
export const regionOptions = ref([])
// 获取影响区域选项
export const fetchDistrictOptions = async () => {
@ -11,25 +17,25 @@ export const fetchDistrictOptions = async () => {
const res = await request({
url: '/snow-ops-platform/sm-event/dashboard/district-options',
method: 'GET',
});
})
if (res && res.code === '00000' && Array.isArray(res.data)) {
// 将接口返回的数据转换为选项格式
const options = res.data.map(item => ({
const options = res.data.map((item) => ({
label: item.qxmc,
value: item.xzdm,
}));
}))
// 保留"全部"选项,并添加接口返回的数据
regionOptions.value = [{ label: '全部', value: '' }, ...options];
return options;
regionOptions.value = [{ label: '全部', value: '' }, ...options]
return options
}
} catch (error) {
console.error('获取影响区域选项失败:', error);
console.error('获取影响区域选项失败:', error)
}
return regionOptions.value;
};
return regionOptions.value
}
// 路况类型选项默认数据会被API数据替换
export const typeOptions = ref([]);
export const typeOptions = ref([])
// 管控措施选项默认数据会被API数据替换
export const controlMeasureOptions = ref([
@ -39,7 +45,7 @@ export const controlMeasureOptions = ref([
// { label: "限速限车", value: "限速限车" },
// { label: "限速", value: "限速" },
// { label: "告警阻拦", value: "告警阻拦" },
]);
])
// 获取路况类型选项
export const fetchRoadConditionOptions = async () => {
@ -47,22 +53,22 @@ export const fetchRoadConditionOptions = async () => {
const res = await request({
url: '/snow-ops-platform/sm-event/dashboard/processing-measure-options',
method: 'GET',
});
})
if (res && res.code === '00000' && Array.isArray(res.data)) {
// 将接口返回的字符串数组转换为选项格式
const options = res.data.map(item => ({
const options = res.data.map((item) => ({
label: item,
value: item,
}));
}))
// 保留"全部"选项,并添加接口返回的数据
typeOptions.value = [{ label: '全部', value: '' }, ...options];
return options;
typeOptions.value = [{ label: '全部', value: '' }, ...options]
return options
}
} catch (error) {
console.error('获取路况类型选项失败:', error);
console.error('获取路况类型选项失败:', error)
}
return typeOptions.value;
};
return typeOptions.value
}
// 获取管控措施选项
export const fetchControlMeasureOptions = async () => {
@ -70,34 +76,34 @@ export const fetchControlMeasureOptions = async () => {
const res = await request({
url: '/snow-ops-platform/sm-event/dashboard/processing-measure-options',
method: 'GET',
});
})
if (res && res.code === '00000' && Array.isArray(res.data)) {
const options = res.data
.map(item => {
.map((item) => {
if (typeof item === 'string') {
return { label: item, value: item };
return { label: item, value: item }
}
if (item && typeof item === 'object') {
const label = item.label ?? item.name ?? item.text ?? item.value ?? item.val;
const value = item.value ?? item.val ?? item.code ?? label;
if (label == null || value == null) return null;
return { label: String(label), value: String(value) };
const label = item.label ?? item.name ?? item.text ?? item.value ?? item.val
const value = item.value ?? item.val ?? item.code ?? label
if (label == null || value == null) return null
return { label: String(label), value: String(value) }
}
return null;
return null
})
.filter(Boolean);
.filter(Boolean)
if (options.length > 0) {
controlMeasureOptions.value = options;
return options;
controlMeasureOptions.value = options
return options
}
}
} catch (error) {
console.error('获取管控措施选项失败:', error);
console.error('获取管控措施选项失败:', error)
}
return controlMeasureOptions.value;
};
return controlMeasureOptions.value
}
// 风险等级选项
export const riskLevelOptions = [
@ -106,7 +112,7 @@ export const riskLevelOptions = [
{ label: '二级', value: '二级' },
{ label: '三级', value: '三级' },
{ label: '四级', value: '四级' },
];
]
// 影响点类型选项
export const pointTypeOptions = [
@ -115,7 +121,7 @@ export const pointTypeOptions = [
{ label: '桥梁', value: 'bridge' },
{ label: '隧道', value: 'tunnel' },
{ label: '路面', value: 'road' },
];
]
// 影响点等级选项
export const pointLevelOptions = [
@ -124,14 +130,14 @@ export const pointLevelOptions = [
{ label: '中风险', value: '中风险' },
{ label: '较高风险', value: '较高风险' },
{ label: '高风险', value: '高风险' },
];
]
// 是否回应选项
export const isRespondedOptions = [
{ label: '全部', value: '' },
{ label: '是', value: true },
{ label: '否', value: false },
];
]
// 预警等级选项
export const warningLevelOptions = [
@ -140,30 +146,277 @@ export const warningLevelOptions = [
{ label: '橙色预警', value: '橙色预警' },
{ label: '黄色预警', value: '黄色预警' },
{ label: '蓝色预警', value: '蓝色预警' },
];
]
// 是否结束选项
export const isEndedOptions = [
{ label: '全部', value: '' },
{ label: '是', value: true },
{ label: '否', value: false },
];
]
// 影响区域选项(带全部)
export const regionOptionsWithAll = ref([]);
export const regionOptionsWithAll = ref([])
// 格式化日期时间为接口所需格式
export const formatDateTime = date => {
if (!date) return '';
const d = new Date(date);
const year = d.getFullYear();
const month = String(d.getMonth() + 1).padStart(2, '0');
const day = String(d.getDate()).padStart(2, '0');
const hours = String(d.getHours()).padStart(2, '0');
const minutes = String(d.getMinutes()).padStart(2, '0');
const seconds = String(d.getSeconds()).padStart(2, '0');
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
};
export const formatDateTime = (date) => {
if (!date) return ''
const d = new Date(date)
const year = d.getFullYear()
const month = String(d.getMonth() + 1).padStart(2, '0')
const day = String(d.getDate()).padStart(2, '0')
const hours = String(d.getHours()).padStart(2, '0')
const minutes = String(d.getMinutes()).padStart(2, '0')
const seconds = String(d.getSeconds()).padStart(2, '0')
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`
}
// 记录用户操作日志(使用 fetch keepalive 确保页面切换后请求仍能完成)
export const logUserOperation = (type, command) => {
try {
const data = { type, command }
// 使用 fetch 的 keepalive 选项确保页面卸载后请求仍能完成
fetch('/snow-ops-platform/weather-warning/users/logs', {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(data),
keepalive: true, // 关键:确保页面切换后请求仍能完成
})
.then((response) => {
console.log('日志请求完成:', response.status)
})
.catch((error) => {
console.error('日志请求失败:', error)
})
console.log('日志已触发发送:', data)
return true
} catch (error) {
console.error('记录操作日志失败:', error)
return false
}
}
// 监听外部应用打开并记录日志的辅助函数
const listenForAppLaunch = (type, command) => {
let hasLogged = false
let hasLostFocus = false // 是否已经失去焦点
let focusTimer = null // 用于延迟检测的定时器
const startTime = Date.now()
console.log('开始监听外部应用打开...', { type, command, initialHidden: document.hidden, startTime })
// 确认打开外部应用
const confirmLaunch = () => {
if (hasLogged) return
hasLogged = true
console.log('确认用户点击了"打开"按钮,调用日志接口')
logUserOperation(type, command)
cleanup()
}
// 检测打开状态
const checkLaunchStatus = () => {
if (hasLogged) return
const now = Date.now()
const elapsed = now - startTime
const isHidden = document.hidden
const currentHasFocus = document.hasFocus()
console.log(`检测状态 [${elapsed}ms]:`, { isHidden, hasFocus: currentHasFocus, hasLostFocus, hasLogged })
// 情况1: 页面被隐藏 → 用户点击了"打开"
if (isHidden) {
confirmLaunch()
return
}
// 情况2: 失去焦点后重新获得焦点 → 用户点击了"取消"
if (hasLostFocus && currentHasFocus) {
console.log('用户点击了"取消"按钮,不记录日志')
cleanup()
return
}
// 记录当前焦点状态
if (!currentHasFocus && !hasLostFocus) {
hasLostFocus = true
console.log('页面失去焦点,开始等待用户选择...')
// 设置延迟检测1.5秒后如果仍未获得焦点,认为用户点击了"打开"
focusTimer = setTimeout(() => {
if (!hasLogged && hasLostFocus && !document.hasFocus()) {
console.log('1.5秒未重新获得焦点,认为用户点击了"打开"')
confirmLaunch()
}
}, 1500)
}
}
// 方法1: 监听 visibilitychange 事件
const handleVisibilityChange = () => {
console.log('visibilitychange 触发, document.hidden:', document.hidden)
checkLaunchStatus()
}
// 方法2: 监听 window blur 事件
const handleWindowBlur = () => {
console.log('window blur 触发')
hasLostFocus = true
// 设置延迟检测1.5秒后如果仍未获得焦点,认为用户点击了"打开"
focusTimer = setTimeout(() => {
if (!hasLogged && hasLostFocus && !document.hasFocus()) {
console.log('1.5秒未重新获得焦点,认为用户点击了"打开"')
confirmLaunch()
}
}, 1500)
}
// 方法3: 监听 window focus 事件(判断用户是否点击取消)
const handleWindowFocus = () => {
console.log('window focus 触发')
checkLaunchStatus()
}
// 方法4: 定时轮询检测
let checkInterval = setInterval(() => {
checkLaunchStatus()
const elapsed = Date.now() - startTime
// 10秒后停止轮询
if (elapsed > 10000 || hasLogged) {
clearInterval(checkInterval)
}
}, 200)
// 清理函数
const cleanup = () => {
console.log('清理监听器')
clearInterval(checkInterval)
clearTimeout(focusTimer)
document.removeEventListener('visibilitychange', handleVisibilityChange)
window.removeEventListener('blur', handleWindowBlur)
window.removeEventListener('focus', handleWindowFocus)
}
document.addEventListener('visibilitychange', handleVisibilityChange)
window.addEventListener('blur', handleWindowBlur)
window.addEventListener('focus', handleWindowFocus)
// 10秒后自动清理
setTimeout(() => {
if (!hasLogged) {
console.log('10秒超时未检测到打开操作清理监听器')
cleanup()
}
}, 10000)
}
// 打开视频会议
export const openVideoConference = async (item) => {
console.log('打开视频会议:', item)
if (!item || !item.name || !item.phone) {
console.warn('缺少姓名或电话信息')
return
}
try {
// 调用接口获取 user-id
const res = await request({
url: '/snow-ops-platform/weather-warning/user-id',
method: 'GET',
params: {
name: item.name,
phone: item.phone,
},
})
const userId = 1279134
if (res.code === '00000') {
// const userId = res.data
const url = `taurusykz://taurusclient/action/avmeeting/conferenceCreateByIds?title=重庆渝路畅行风险预警&isVideoConference=true&calleeStaffIds=${userId}`
window.location.href = url
// 监听用户点击"打开"按钮后记录日志
let jsobj = {
name: item.name,
phone: item.phone,
id: item.id,
userId: userId,
text: '打开视频会议',
}
logUserOperation('launch-video-conference', JSON.stringify(jsobj))
} else {
console.error('获取 user-id 失败:', res)
// 非系统用户, 操作无法执行
ElMessage.warning('非系统用户, 操作无法执行')
logUserOperation('launch-video-conference', '非系统用户, 操作无法执行')
}
} catch (error) {
console.error('获取 user-id 出错:', error)
}
}
// 打开语音通话
export const openVoiceConference = async (item) => {
console.log('打开语音通话:', item)
if (!item || !item.name || !item.phone) {
console.warn('缺少姓名或电话信息')
return
}
try {
// 调用接口获取 user-id
const res = await request({
url: '/snow-ops-platform/weather-warning/user-id',
method: 'GET',
params: {
name: item.name,
phone: item.phone,
},
})
if (res.code === '00000' && res.data != null) {
const userId = res.data
const url = `taurusykz://taurusclient/action/avmeeting/conferenceCreateByIds?title=重庆渝路畅行风险预警&isVideoConference=false&calleeStaffIds=${userId}`
window.location.href = url
// 监听用户点击"打开"按钮后记录日志
let jsobj = {
name: item.name,
phone: item.phone,
id: item.id,
userId: userId,
text: '打开语音通话',
}
logUserOperation('launch-audio-conference', JSON.stringify(jsobj))
} else {
console.error('获取 user-id 失败:', res)
// 非系统用户, 操作无法执行
ElMessage.warning('非系统用户, 操作无法执行')
logUserOperation('launch-audio-conference', '非系统用户, 操作无法执行')
}
} catch (error) {
console.error('获取 user-id 出错:', error)
}
}
// 打开拨打电话
export const opencallConference = async (item) => {
console.log('打开拨打电话:', item)
if (!item || !item.name || !item.phone) {
console.warn('缺少姓名或电话信息')
return
}
// 监听用户点击"打开"按钮后记录日志
let jsobj = {
name: item.name,
phone: item.phone,
id: item.id,
userId: '',
text: '拨打电话',
}
// listenForAppLaunch('launch-audio-conference', JSON.stringify(jsobj))
logUserOperation('phone-based-confirmation', JSON.stringify(jsobj))
}
// 默认导出所有选项
export default {
@ -181,4 +434,7 @@ export default {
fetchRoadConditionOptions,
fetchDistrictOptions,
fetchControlMeasureOptions,
};
openVideoConference,
openVoiceConference,
opencallConference,
}

View File

@ -4,11 +4,7 @@
<img class="title_bg" src="../../assets/RiskWarning_img/一级标题栏bg@2x.png" alt="" />
<div class="title_img_box">
<img class="title_img1" src="../../assets/RiskWarning_img/位图@2x.png" alt="" />
<img
class="title_img2"
src="../../assets/RiskWarning_img/渝路畅行-风险预警一键响应@2x.png"
alt=""
/>
<img class="title_img2" src="../../assets/RiskWarning_img/渝路畅行-风险预警一键响应@2x.png" alt="" />
</div>
</div>
@ -20,12 +16,7 @@
<div class="corner corner-bottom-left"></div>
<div class="corner corner-bottom-right"></div>
<!-- 中心数据展示卡片 -->
<centerInfoCard
:type="showCenterCard.type"
:peopleCount="showCenterCard.value"
roadCount="117"
@click="handleCenterCardClick"
/>
<centerInfoCard :type="showCenterCard.type" :peopleCount="showCenterCard.value" roadCount="117" @click="handleCenterCardClick" />
<div class="left">
<left
@ -40,7 +31,7 @@
@openWarningSituation="openDialog('warningSituation')"
@openResponseStatus="openDialog('responseStatus')"
@openDispatchDistrict="openDialog('dispatchDistrict')"
@showCenterCard="item => handleCenterCardClick(item)"
@showCenterCard="(item) => handleCenterCardClick(item)"
@openOfflineHelp="openDialog('offlineHelp')"
@openImageInspection="openDialog('imageInspection')"
></left>
@ -70,6 +61,7 @@
@riskPointStatsChange="handleRiskPointStatsChange"
@update:roadvalArr="updateRoadvalArr"
@openHazardPointSituation="handleOpenHazardPointSituation"
@openRoadSectionSituation="handleOpenRoadSectionSituation"
/>
</div>
@ -127,15 +119,12 @@
:message="confirmConfig.message"
:confirm-text="confirmConfig.confirmText"
:cancel-text="confirmConfig.cancelText"
@confirm="closeDialog('confirm')"
@confirm="confirmCall"
@cancel="closeDialog('confirm')"
/>
<!-- 风险点详情对话框 -->
<riskPointDetailDialog
v-model:visible="dialogVisible.riskPointDetail"
@close="closeDialog('riskPointDetail')"
/>
<riskPointDetailDialog v-model:visible="dialogVisible.riskPointDetail" @close="closeDialog('riskPointDetail')" />
<!-- 影响点情况对话框 -->
<impactPointDialog
@ -154,16 +143,10 @@
/>
<!-- 响应点详情对话框 -->
<responsePointDetailDialog
v-model:visible="dialogVisible.responsePointDetail"
@close="closeDialog('responsePointDetail')"
/>
<responsePointDetailDialog v-model:visible="dialogVisible.responsePointDetail" @close="closeDialog('responsePointDetail')" />
<!-- 响应点信息对话框 -->
<responsePointInfoDialog
v-model:visible="dialogVisible.responsePointInfo"
@close="closeDialog('responsePointInfo')"
/>
<responsePointInfoDialog v-model:visible="dialogVisible.responsePointInfo" @close="closeDialog('responsePointInfo')" />
<!-- 响应状态对话框 -->
<responseStatusDialog
@ -174,10 +157,7 @@
/>
<!-- AI预警处理结果对话框 -->
<aiWarningResultDialog
v-model:visible="dialogVisible.aiWarningResult"
@close="closeDialog('aiWarningResult')"
/>
<aiWarningResultDialog v-model:visible="dialogVisible.aiWarningResult" @close="closeDialog('aiWarningResult')" />
<!-- 潼南基本信息对话框 -->
<tongnanInfoDialog
@ -213,16 +193,10 @@
/>
<!-- 管控情况对话框 -->
<controlSituationDialog
v-model:visible="dialogVisible.controlSituation"
@close="closeDialog('controlSituation')"
/>
<controlSituationDialog v-model:visible="dialogVisible.controlSituation" @close="closeDialog('controlSituation')" />
<!-- 调度详情对话框 -->
<dispatchDetailDialog
v-model:visible="dialogVisible.dispatchDetail"
@close="closeDialog('dispatchDetail')"
/>
<dispatchDetailDialog v-model:visible="dialogVisible.dispatchDetail" @close="closeDialog('dispatchDetail')" />
<!-- 调度区县情况对话框 -->
<dispatchDistrictDialog
@ -250,89 +224,81 @@
/>
<!-- 隧道信息对话框 -->
<tunnelInfoDialog
v-model:visible="dialogVisible.tunnelInfo"
@close="closeDialog('tunnelInfo')"
/>
<tunnelInfoDialog v-model:visible="dialogVisible.tunnelInfo" @close="closeDialog('tunnelInfo')" />
<hazardPointSituationDialog
v-model:visible="dialogVisible.hazardPointSituation"
:data="hazardPointData"
:title="hazardPointDialogTitle"
@close="closeDialog('hazardPointSituation')"
@voice="openVoiceConference"
@video="openVideoConference"
@call="handleCallClick"
/>
<offlineHelpDialog
v-model:visible="dialogVisible.offlineHelp"
@close="closeDialog('offlineHelp')"
/>
<offlineHelpDialog v-model:visible="dialogVisible.offlineHelp" @close="closeDialog('offlineHelp')" />
<!-- 抽查人次对话框 -->
<imageInspectionDialog
v-model:visible="dialogVisible.imageInspection"
@close="closeDialog('imageInspection')"
/>
<imageInspectionDialog v-model:visible="dialogVisible.imageInspection" @close="closeDialog('imageInspection')" />
<!-- 巡查里程对话框 -->
<patrolMileageDialog
v-model:visible="dialogVisible.patrolMileage"
@close="closeDialog('patrolMileage')"
/>
<patrolMileageDialog v-model:visible="dialogVisible.patrolMileage" @close="closeDialog('patrolMileage')" />
<!-- 巡查情况对话框 -->
<patrolSituationDialog
v-model:visible="dialogVisible.patrolSituation"
@close="closeDialog('patrolSituation')"
/>
<patrolSituationDialog v-model:visible="dialogVisible.patrolSituation" @close="closeDialog('patrolSituation')" />
</div>
</div>
</template>
<script setup>
import { ref, onMounted, provide } from 'vue';
import useMapStore from '@/map/stores/mapStore';
import { useMapBase } from '../cockpit/composables/useMapBase';
import left from './left.vue';
import right from './right.vue';
import bottom from './bottom.vue';
import top from './top.vue';
import ChongqingMap from './component/ChongqingMap.vue';
import { ref, onMounted, provide } from 'vue'
import useMapStore from '@/map/stores/mapStore'
import { useMapBase } from '../cockpit/composables/useMapBase'
import left from './left.vue'
import right from './right.vue'
import bottom from './bottom.vue'
import top from './top.vue'
import ChongqingMap from './component/ChongqingMap.vue'
import {
fetchRoadConditionOptions,
fetchDistrictOptions,
fetchControlMeasureOptions,
} from './component/index.js';
openVideoConference,
openVoiceConference,
opencallConference,
} from './component/index.js'
//
import responseSituationDiaLog from './Dialog/responseSituationDiaLog.vue';
import warningInfoDialog from './Dialog/warningInfoDialog.vue';
import eventDetailDialog from './Dialog/eventDetailDialog.vue';
import confirmDialog from './Dialog/confirmDialog.vue';
import riskPointDetailDialog from './Dialog/riskPointDetailDialog.vue';
import impactPointDialog from './Dialog/impactPointDialog.vue';
import impactPointDetailDialog from './Dialog/impactPointDetailDialog.vue';
import responsePointDetailDialog from './Dialog/responsePointDetailDialog.vue';
import responsePointInfoDialog from './Dialog/responsePointInfoDialog.vue';
import responseStatusDialog from './Dialog/responseStatusDialog.vue';
import aiWarningResultDialog from './Dialog/aiWarningResultDialog.vue';
import tongnanInfoDialog from './Dialog/tongnanInfoDialog.vue';
import tongnanResponsibleDialog from './Dialog/tongnanResponsibleDialog.vue';
import clearanceSituationDialog from './Dialog/clearanceSituationDialog.vue';
import controlSituationDialog from './Dialog/controlSituationDialog.vue';
import dispatchDetailDialog from './Dialog/dispatchDetailDialog.vue';
import dispatchDistrictDialog from './Dialog/dispatchDistrictDialog.vue';
import tongnanTeamDialog from './Dialog/tongnanTeamDialog.vue';
import warningSituationDialog from './Dialog/warningSituationDialog.vue';
import tunnelInfoDialog from './Dialog/tunnelInfoDialog.vue';
import centerInfoCard from './Dialog/centerInfoCard.vue';
import tongnanProjectPersonDialog from './Dialog/tongnanProjectPersonDialog.vue';
import hazardPointSituationDialog from './Dialog/hazardPointSituationDialog.vue';
import offlineHelpDialog from './Dialog/offlineHelpDialog.vue';
import imageInspectionDialog from './Dialog/imageInspectionDialog.vue';
import patrolMileageDialog from './Dialog/patrolMileageDialog.vue';
import patrolSituationDialog from './Dialog/patrolSituationDialog.vue';
import responseSituationDiaLog from './Dialog/responseSituationDiaLog.vue'
import warningInfoDialog from './Dialog/warningInfoDialog.vue'
import eventDetailDialog from './Dialog/eventDetailDialog.vue'
import confirmDialog from './Dialog/confirmDialog.vue'
import riskPointDetailDialog from './Dialog/riskPointDetailDialog.vue'
import impactPointDialog from './Dialog/impactPointDialog.vue'
import impactPointDetailDialog from './Dialog/impactPointDetailDialog.vue'
import responsePointDetailDialog from './Dialog/responsePointDetailDialog.vue'
import responsePointInfoDialog from './Dialog/responsePointInfoDialog.vue'
import responseStatusDialog from './Dialog/responseStatusDialog.vue'
import aiWarningResultDialog from './Dialog/aiWarningResultDialog.vue'
import tongnanInfoDialog from './Dialog/tongnanInfoDialog.vue'
import tongnanResponsibleDialog from './Dialog/tongnanResponsibleDialog.vue'
import clearanceSituationDialog from './Dialog/clearanceSituationDialog.vue'
import controlSituationDialog from './Dialog/controlSituationDialog.vue'
import dispatchDetailDialog from './Dialog/dispatchDetailDialog.vue'
import dispatchDistrictDialog from './Dialog/dispatchDistrictDialog.vue'
import tongnanTeamDialog from './Dialog/tongnanTeamDialog.vue'
import warningSituationDialog from './Dialog/warningSituationDialog.vue'
import tunnelInfoDialog from './Dialog/tunnelInfoDialog.vue'
import centerInfoCard from './Dialog/centerInfoCard.vue'
import tongnanProjectPersonDialog from './Dialog/tongnanProjectPersonDialog.vue'
import hazardPointSituationDialog from './Dialog/hazardPointSituationDialog.vue'
import offlineHelpDialog from './Dialog/offlineHelpDialog.vue'
import imageInspectionDialog from './Dialog/imageInspectionDialog.vue'
import patrolMileageDialog from './Dialog/patrolMileageDialog.vue'
import patrolSituationDialog from './Dialog/patrolSituationDialog.vue'
import './component/el-select.scss';
import './component/date-picker-theme.scss';
import './component/el-select.scss'
import './component/date-picker-theme.scss'
//
const dialogVisible = ref({
@ -362,8 +328,8 @@ const dialogVisible = ref({
imageInspection: false,
patrolMileage: false,
patrolSituation: false,
});
const activeitem = ref({});
})
const activeitem = ref({})
//
const riskPointStats = ref({
@ -374,169 +340,186 @@ const riskPointStats = ref({
一般路内隐患点: 0,
一般路外隐患点: 0,
风险点总数: 0,
});
})
//
const hazardPointData = ref({});
const hazardPointData = ref({})
//
const hazardPointDialogTitle = ref('涉灾隐患点情况')
//
const getdateRange = ref([]);
const getdateRange = ref([])
//
const handleDateRangeChange = val => {
console.log('日期范围变化:', val);
getdateRange.value = val;
};
const handleDateRangeChange = (val) => {
console.log('日期范围变化:', val)
getdateRange.value = val
}
//
const chongqingMapRef = ref(null);
const chongqingMapRef = ref(null)
//
const changeActiveIndex = index => {
activeitem.value = index;
};
const roadItem = ref({});
const showRoadStats = ref(false);
const roadItemClick = item => {
console.log('点击路段:', item);
roadItem.value = item;
showRoadStats.value = true;
};
const changeActiveIndex = (index) => {
activeitem.value = index
}
const roadItem = ref({})
const showRoadStats = ref(false)
const roadItemClick = (item) => {
console.log('点击路段:', item)
roadItem.value = item
showRoadStats.value = true
}
//
const handleHideRoadStats = () => {
console.log('隐藏路段统计');
showRoadStats.value = false;
};
console.log('隐藏路段统计')
showRoadStats.value = false
}
//
const handleHazardItemClick = item => {
console.log('点击隐患点:', item);
const handleHazardItemClick = (item) => {
console.log('点击隐患点:', item)
//
if (chongqingMapRef.value) {
chongqingMapRef.value.handleHazardItemClick(item);
chongqingMapRef.value.handleHazardItemClick(item)
}
};
const showHazardPopup = ref(false);
const showHazardPopupfn = val => {
showHazardPopup.value = val;
};
}
const showHazardPopup = ref(false)
const showHazardPopupfn = (val) => {
showHazardPopup.value = val
}
//
const handleRiskPointStatsChange = stats => {
console.log('风险点统计数据变化:', stats);
riskPointStats.value = stats;
};
const handleRiskPointStatsChange = (stats) => {
console.log('风险点统计数据变化:', stats)
riskPointStats.value = stats
}
//
const roadvalArrtrue = ref([]);
const updateRoadvalArr = roadvalArr => {
roadvalArrtrue.value = roadvalArr;
const roadvalArrtrue = ref([])
const updateRoadvalArr = (roadvalArr) => {
roadvalArrtrue.value = roadvalArr
// let num = 0;
// roadvalArrtrue.value.forEach(item => {
// num += item.value;
// });
// roadvalArrtrue.value.push({ label: '', value: num });
console.log('更新路段统计数据:', roadvalArr);
console.log('更新路段统计数据:', roadvalArrtrue);
};
console.log('更新路段统计数据:', roadvalArr)
console.log('更新路段统计数据:', roadvalArrtrue)
}
//
const openResourceDetail = item => {
console.log('打开资源详情:', item);
const openResourceDetail = (item) => {
console.log('打开资源详情:', item)
//
if (item.label === '全市普通公路抢险队伍' || item.label === '人员') {
//
if (chongqingMapRef.value) {
chongqingMapRef.value.getEmergencyForceData();
chongqingMapRef.value.getEmergencyForceData()
}
}
//
const key = item.label.toLowerCase().replace(/[^a-z]/g, '');
const key = item.label.toLowerCase().replace(/[^a-z]/g, '')
if (dialogVisible.value[key] !== undefined) {
dialogVisible.value[key] = true;
dialogVisible.value[key] = true
}
};
}
//
const clearMapMarkers = () => {
console.log('清除地图标记');
console.log('清除地图标记')
if (chongqingMapRef.value) {
chongqingMapRef.value.clearProjectMarkers();
chongqingMapRef.value.clearProjectMarkers()
}
};
}
//
const handleClearFilters = () => {
console.log('index.vue 处理清除筛选事件');
console.log('index.vue 处理清除筛选事件')
//
clearMapMarkers();
clearMapMarkers()
//
activeitem.value = {};
roadItem.value = {};
showHazardPopup.value = false;
};
activeitem.value = {}
roadItem.value = {}
showHazardPopup.value = false
}
//
const openDialog = dialogName => {
dialogVisible.value[dialogName] = true;
};
const impactPointDetailItem = ref({});
const openDialog = (dialogName) => {
dialogVisible.value[dialogName] = true
}
const impactPointDetailItem = ref({})
//
const handleImpactPointClick = item => {
console.log('影响点点击:', item);
impactPointDetailItem.value = item;
};
const handleImpactItem = ref({});
const handleImpactClickItem = item => {
console.log('影响点点击详情:', item);
handleImpactItem.value = item;
};
const tongnanInfoItemData = ref({});
const tongnanInfoItemDatafn = item => {
console.log('点击详情:', item);
tongnanInfoItemData.value = item;
};
const handleImpactPointClick = (item) => {
console.log('影响点点击:', item)
impactPointDetailItem.value = item
}
const handleImpactItem = ref({})
const handleImpactClickItem = (item) => {
console.log('影响点点击详情:', item)
handleImpactItem.value = item
}
const tongnanInfoItemData = ref({})
const tongnanInfoItemDatafn = (item) => {
console.log('点击详情:', item)
tongnanInfoItemData.value = item
}
//
const closeDialog = dialogName => {
const closeDialog = (dialogName) => {
//
console.log('关闭弹窗', dialogName);
dialogVisible.value[dialogName] = false;
};
console.log('关闭弹窗', dialogName)
dialogVisible.value[dialogName] = false
}
//
const handleOpenHazardPointSituation = item => {
console.log('打开涉灾隐患点情况弹窗:', item);
hazardPointData.value = item;
dialogVisible.value.hazardPointSituation = true;
};
const handleOpenHazardPointSituation = (item) => {
console.log('打开涉灾隐患点情况弹窗:', item)
hazardPointDialogTitle.value = '涉灾隐患点情况'
hazardPointData.value = item
dialogVisible.value.hazardPointSituation = true
}
//
const handleOpenRoadSectionSituation = (item) => {
console.log('打开路段情况弹窗:', item)
hazardPointDialogTitle.value = '路段情况'
//
hazardPointData.value = {
...item,
dataType: 'road',
}
dialogVisible.value.hazardPointSituation = true
}
//
const warningitem = ref({});
const handleWarningClick = item => {
console.log('气象预警点击:', item);
warningitem.value = item;
};
const warningitem = ref({})
const handleWarningClick = (item) => {
console.log('气象预警点击:', item)
warningitem.value = item
}
const clearanceSituationDialogItemData = ref({});
const handleItemData = item => {
console.log('点击详情:', item);
clearanceSituationDialogItemData.value = item;
};
const clearanceSituationDialogItemData = ref({})
const handleItemData = (item) => {
console.log('点击详情:', item)
clearanceSituationDialogItemData.value = item
}
const dispatchDateRange = ref([]);
const handleDispatchDateRange = range => {
dispatchDateRange.value = range;
};
const rightDateRange = ref([]);
const updateDateRange = range => {
console.log('更新日期范围:', range);
rightDateRange.value = range;
};
const filterForm = ref({});
const updateFilterForm = item => {
console.log('更新筛选表单:', item);
filterForm.value = item;
};
const dispatchDateRange = ref([])
const handleDispatchDateRange = (range) => {
dispatchDateRange.value = range
}
const rightDateRange = ref([])
const updateDateRange = (range) => {
console.log('更新日期范围:', range)
rightDateRange.value = range
}
const filterForm = ref({})
const updateFilterForm = (item) => {
console.log('更新筛选表单:', item)
filterForm.value = item
}
//
const confirmConfig = ref({
@ -544,63 +527,82 @@ const confirmConfig = ref({
message: '是否拨打电话?',
confirmText: '确定',
cancelText: '取消',
});
})
//
const currentCallItem = ref(null)
//
const handleCallClick = (item) => {
console.log('准备拨打电话:', item)
currentCallItem.value = item
openConfirm({
title: '拨打电话',
message: `是否拨打电话: ${item.phone || item.name || ''}?`,
})
}
//
const openConfirm = config => {
confirmConfig.value = { ...confirmConfig.value, ...config };
dialogVisible.value.confirm = true;
};
const openConfirm = (config) => {
confirmConfig.value = { ...confirmConfig.value, ...config }
dialogVisible.value.confirm = true
}
//
const confirmCall = () => {
if (currentCallItem.value) {
opencallConference(currentCallItem.value)
currentCallItem.value = null
}
closeDialog('confirm')
}
//
const showCenterCard = ref(false);
const allCountyData = ref({});
const showCenterCard = ref(false)
const allCountyData = ref({})
//
const handleDistrictClick = item => {
console.log('区县点击:', item);
allCountyData.value = item;
const handleDistrictClick = (item) => {
console.log('区县点击:', item)
allCountyData.value = item
if (item.data.roadType == 'national') {
//
openDialog('tongnanTeam');
openDialog('tongnanTeam')
} else if (item.data.roadType == 'rural') {
openDialog('responseSituation');
openDialog('responseSituation')
} else if (item.data.type == 'project' && item.data.roadType == '-') {
//
openDialog('tongnanResponsible');
openDialog('tongnanResponsible')
}
};
}
//
const handleCenterCardClick = item => {
console.log('中心卡片点击:', item);
const handleCenterCardClick = (item) => {
console.log('中心卡片点击:', item)
//
if (chongqingMapRef.value && item.data) {
const cardData = {
title: getCardTitleByType(item.type),
dataList: item.data,
};
chongqingMapRef.value.openCenterCard(cardData);
}
chongqingMapRef.value.openCenterCard(cardData)
//
if (item.data.length > 0 && (item.data[0].countyName || item.data[0].name)) {
const firstCounty = item.data[0].countyName || item.data[0].name;
chongqingMapRef.value.locateToDistrict(firstCounty);
const firstCounty = item.data[0].countyName || item.data[0].name
chongqingMapRef.value.locateToDistrict(firstCounty)
}
}
};
}
//
const getCardTitleByType = type => {
const getCardTitleByType = (type) => {
const titleMap = {
first: '国省道调度',
second: '农村公路调度',
third: '建设工程调度',
};
return titleMap[type] || '调度统计';
};
const handleCenterCardClickType = item => {
console.log(item.data);
showCenterCard.value = true;
}
return titleMap[type] || '调度统计'
}
const handleCenterCardClickType = (item) => {
console.log(item.data)
showCenterCard.value = true
// if (item.type === "second") {
// openDialog("tongnanTeam");
// } else if (item.type === "third") {
@ -608,47 +610,47 @@ const handleCenterCardClickType = item => {
// } else if (item.type === "first") {
// openDialog("warningSituation");
// }
};
}
//
const refreshLeftData = ref(null);
const refreshRightData = ref(null);
const refreshLeftData = ref(null)
const refreshRightData = ref(null)
//
const setRefreshLeftData = callback => {
refreshLeftData.value = callback;
};
const setRefreshLeftData = (callback) => {
refreshLeftData.value = callback
}
// right.vue
const setRefreshRightData = callback => {
refreshRightData.value = callback;
};
const setRefreshRightData = (callback) => {
refreshRightData.value = callback
}
//
const triggerRefreshLeftData = () => {
if (refreshLeftData.value) {
refreshLeftData.value();
refreshLeftData.value()
}
// right.vue
if (refreshRightData.value) {
refreshRightData.value();
refreshRightData.value()
}
};
}
//
provide('setRefreshLeftData', setRefreshLeftData);
provide('triggerRefreshLeftData', triggerRefreshLeftData);
provide('setRefreshRightData', setRefreshRightData);
provide('getdateRange', getdateRange);
provide('setRefreshLeftData', setRefreshLeftData)
provide('triggerRefreshLeftData', triggerRefreshLeftData)
provide('setRefreshRightData', setRefreshRightData)
provide('getdateRange', getdateRange)
// ==================== ====================
const mapStore = useMapStore();
const mapStore = useMapStore()
/**
* 加载地图的业务底图与聚焦中心点
*/
const mapBase = useMapBase(mapStore);
const mapBase = useMapBase(mapStore)
// ==================== ====================
@ -657,11 +659,11 @@ const mapBase = useMapBase(mapStore);
*/
onMounted(() => {
//
mapBase.loadBaseData();
fetchRoadConditionOptions(); //
fetchDistrictOptions(); //
fetchControlMeasureOptions(); //
});
mapBase.loadBaseData()
fetchRoadConditionOptions() //
fetchDistrictOptions() //
fetchControlMeasureOptions() //
})
</script>
<style lang="scss" scoped>

View File

@ -1,7 +1,9 @@
<template>
<div class="filter-header">
<div class="filter-container">
<span class="filter-item active" @click="handleDateRangeClick()">本轮</span>
<!-- <span class="filter-item active" @click="handleDateRangeClick()">本轮</span> -->
<span class="filter-item active" @click="handleDateRangeClick('total')">累计</span>
<span class="filter-item active" @click="handleDateRangeClick('today')">今日</span>
<div class="date-range-wrapper">
<el-date-picker
v-model="dateRange"
@ -17,12 +19,7 @@
</div>
</div>
<div class="hazard-stats" v-if="showHazardPopup">
<div
v-for="(item, index) in hazardStatsShowArr"
:key="index"
class="stat-item"
:class="item.class"
>
<div v-for="(item, index) in hazardStatsShowArr" :key="index" class="stat-item" :class="item.class">
<span class="stat-label">{{ item.label }}</span>
<span class="stat-value-container display ai_center">
<span class="stat-value">{{ item.value }}</span>
@ -43,9 +40,9 @@
</template>
<script setup>
import { ref, watch, inject, defineProps, nextTick, provide, onMounted } from 'vue';
import { Calendar } from '@element-plus/icons-vue';
import { request } from '@/utils/request';
import { ref, watch, inject, defineProps, nextTick, provide, onMounted } from 'vue'
import { Calendar } from '@element-plus/icons-vue'
import { request } from '@/utils/request'
const props = defineProps({
riskPointStats: {
@ -68,26 +65,26 @@ const props = defineProps({
type: Boolean,
default: false,
},
});
})
watch(
() => props.showHazardPopup,
newShow => {
hazardStatsShowArr.value = JSON.parse(JSON.stringify([]));
hazardStats.value.forEach(item => {
item.value = 0;
item.show = false;
});
}
);
(newShow) => {
hazardStatsShowArr.value = JSON.parse(JSON.stringify([]))
hazardStats.value.forEach((item) => {
item.value = 0
item.show = false
})
},
)
const emit = defineEmits(['openAIResult', 'dateRangeChange']);
const emit = defineEmits(['openAIResult', 'dateRangeChange'])
//
const triggerRefreshLeftData = inject('triggerRefreshLeftData');
const dateRange = ref([]);
const triggerRefreshLeftData = inject('triggerRefreshLeftData')
const dateRange = ref([])
//
const hazardStatsShowArr = ref([]);
const hazardStatsShowArr = ref([])
const hazardStats = ref([
{ label: '重大路内隐患点', value: 0, show: false, type: '重大路内隐患点', isWithinRedLine: '是' },
{ label: '重大路外隐患点', value: 0, show: false, type: '重大路外隐患点', isWithinRedLine: '否' },
@ -96,7 +93,7 @@ const hazardStats = ref([
{ label: '一般路内隐患点', value: 0, show: false, type: '一般路内隐患点', isWithinRedLine: '是' },
{ label: '一般路外隐患点', value: 0, show: false, type: '一般路外隐患点', isWithinRedLine: '否' },
{ label: '风险点总数', value: 0, show: false, type: '风险点总数', isWithinRedLine: '' },
]);
])
//
const roadStats = ref([
@ -105,28 +102,24 @@ const roadStats = ref([
{ label: '中风险路段', value: 0, type: '中风险路段' },
{ label: '低风险路段', value: 0, type: '低风险路段' },
{ label: '风险点总数', value: 0, type: '风险点总数' },
]);
])
watch(
() => props.riskPointStats,
newStats => {
console.log('top.vue 收到风险点统计数据:', newStats);
(newStats) => {
console.log('top.vue 收到风险点统计数据:', newStats)
if (newStats) {
hazardStatsShowArr.value = [];
hazardStats.value.forEach(item => {
if (
item.label.includes(newStats.riskLevel) &&
newStats.isWithinRedLine == item.isWithinRedLine &&
newStats.value >= 0
) {
item.value = newStats.value;
item.show = true;
hazardStatsShowArr.value = []
hazardStats.value.forEach((item) => {
if (item.label.includes(newStats.riskLevel) && newStats.isWithinRedLine == item.isWithinRedLine && newStats.value >= 0) {
item.value = newStats.value
item.show = true
}
if (item.show) {
hazardStatsShowArr.value.push(item);
hazardStats.value[6].show = true;
hazardStatsShowArr.value.push(item)
hazardStats.value[6].show = true
}
});
})
hazardStats.value[6].value =
hazardStats.value[0].value +
@ -134,51 +127,64 @@ watch(
hazardStats.value[2].value +
hazardStats.value[3].value +
hazardStats.value[4].value +
hazardStats.value[5].value;
hazardStats.value[5].value
}
},
{ immediate: true, deep: true }
);
{ immediate: true, deep: true },
)
// 23:59:59
const setEndOfDay = date => {
if (!date) return date;
const d = new Date(date);
d.setHours(23, 59, 59, 999);
return d;
};
const setEndOfDay = (date) => {
if (!date) return date
const d = new Date(date)
d.setHours(23, 59, 59, 999)
return d
}
//
const handleDateRangeClick = () => {
dateRange.value = [];
const handleDateRangeClick = (type) => {
const now = new Date()
if (type === 'total') {
// 11 00:00:00 23:59:59
const startOfYear = new Date(now.getFullYear(), 0, 1, 0, 0, 0, 0)
const endOfYear = new Date(now.getFullYear(), 11, 31, 23, 59, 59, 999)
dateRange.value = [startOfYear, endOfYear]
} else if (type === 'today') {
// 00:00:00 23:59:59
const startOfDay = new Date(now.getFullYear(), now.getMonth(), now.getDate(), 0, 0, 0, 0)
const endOfDay = new Date(now.getFullYear(), now.getMonth(), now.getDate(), 23, 59, 59, 999)
dateRange.value = [startOfDay, endOfDay]
} else {
dateRange.value = []
}
// triggerRefreshLeftData();
// emitwatch dateRange emit
};
}
// dateRange
watch(
dateRange,
(newVal, oldVal) => {
//
console.log('dateRange 变化:', newVal, oldVal);
console.log('dateRange 变化:', newVal, oldVal)
if (JSON.stringify(newVal) !== JSON.stringify(oldVal)) {
console.log('dateRange 发生变化:', newVal);
console.log('dateRange 发生变化:', newVal)
// 23:59:59
if (newVal && newVal.length === 2 && newVal[1]) {
newVal[1] = setEndOfDay(newVal[1]);
newVal[1] = setEndOfDay(newVal[1])
}
//
// if (triggerRefreshLeftData) {
// triggerRefreshLeftData();
// }
//
emit('dateRangeChange', newVal);
emit('dateRangeChange', newVal)
}
},
{ deep: true }
);
{ deep: true },
)
const handleAIClick = () => {
emit('openAIResult');
};
emit('openAIResult')
}
//
const fetchRiskLevelCount = async () => {
@ -186,35 +192,35 @@ const fetchRiskLevelCount = async () => {
const res = await request({
url: '/snow-ops-platform/risk-point/risk-level-count',
method: 'GET',
});
console.log('风险等级统计数据:', res);
})
console.log('风险等级统计数据:', res)
if (res.code === '00000' && res.data) {
//
const data = res.data;
let roadTotal = 0;
roadStats.value.forEach(item => {
const data = res.data
let roadTotal = 0
roadStats.value.forEach((item) => {
// 0
item.value = 0;
item.value = 0
//
const matchedData = data.find(d => item.label.includes(d.level));
const matchedData = data.find((d) => item.label.includes(d.level))
if (matchedData) {
item.value = Number(matchedData.count);
roadTotal += Number(matchedData.count);
item.value = Number(matchedData.count)
roadTotal += Number(matchedData.count)
}
});
})
//
roadStats.value[4].value = roadTotal;
console.log('更新后的roadStats:', roadStats.value);
roadStats.value[4].value = roadTotal
console.log('更新后的roadStats:', roadStats.value)
}
} catch (error) {
console.error('获取风险等级统计数据失败:', error);
console.error('获取风险等级统计数据失败:', error)
}
};
}
//
onMounted(() => {
fetchRiskLevelCount();
});
fetchRiskLevelCount()
})
</script>
<style lang="scss" scoped>