784 lines
25 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<div class="detail-container">
<el-card class="basic-card">
<template #header>
<div class="card-header">
<span>基本信息</span>
</div>
</template>
<el-descriptions column="3">
<el-descriptions-item label="预警标题">{{ detailData.headline || '-' }}</el-descriptions-item>
<el-descriptions-item label="预警类型">{{ detailData.weatherWarningType || '-' }}</el-descriptions-item>
<el-descriptions-item label="发送时间">{{ detailData.createTime || '-' }}</el-descriptions-item>
<el-descriptions-item label="生效时间">{{ detailData.onset || '-' }}</el-descriptions-item>
<el-descriptions-item label="接收时间">{{ detailData.receiveTime || '-' }}</el-descriptions-item>
<el-descriptions-item label="预警转发时间">{{ detailData.forwardTime || '-' }}</el-descriptions-item>
<el-descriptions-item span="3" label="预警结束时间">{{ detailData.expires || '-' }}</el-descriptions-item>
<el-descriptions-item span="3" label="预警描述">{{ detailData.description || '-' }}</el-descriptions-item>
<el-descriptions-item span="3" label="响应措施">{{}}</el-descriptions-item>
</el-descriptions>
</el-card>
<el-card class="sites-card">
<template #header>
<div class="card-header">
<span>影响情况</span>
</div>
</template>
<div class="table">
<div class="stats-cards">
<div
v-for="(item, index) in impactData"
:key="index"
@click="handleClick(index, item)"
class="stat-card"
:style="{
backgroundImage: `url(${cardType == index ? selectedIcon : unselectedIcon})`,
backgroundSize: '100% 100%',
backgroundPosition: 'center',
}"
>
<div class="stat-icon"><img :src="item.icon" alt="" /></div>
<div class="stat-content">
<span class="stat-label">{{ item.name }}</span>
<span class="stat-value">{{ item.count }}</span>
</div>
</div>
</div>
<div class="search-form">
<el-select
class="search-item"
:teleported="false"
v-model="filterForm.pointLevel"
size="small"
placeholder="影响点等级"
clearable
@change="handleFilterChange"
>
<el-option v-for="option in pointLevelOptions" :key="option.value" :label="option.label" :value="option.value" />
</el-select>
<el-select
:teleported="false"
v-model="filterForm.region"
size="small"
placeholder="影响区域"
class="search-item"
clearable
@change="handleFilterChange"
placement="bottom-start"
>
<el-option v-for="option in regionOptions" :key="option.value" :label="option.label" :value="option.value" />
</el-select>
<el-select
:teleported="false"
v-model="filterForm.roadType"
size="small"
placeholder="公路类型"
class="search-item"
clearable
@change="handleFilterChange"
>
<el-option v-for="option in roadTypeOptions" :key="option.value" :label="option.label" :value="option.value" />
</el-select>
</div>
<DynamicTable :dataSource="tableData" :columns="columns" :autoHeight="true" :pagination="pagination"></DynamicTable>
</div>
</el-card>
</div>
</template>
<script setup>
import { ref, onMounted, watch, computed, h, reactive } from 'vue'
import { request } from '@/utils/request'
import DynamicTable from '../../../component/DynamicTable/index.js'
import selectedIcon from '@/assets/xiangying/选中bg@2x.png'
import unselectedIcon from '@/assets/xiangying/未选中bg@2x.png'
import Icon0 from '@/assets/xiangying/选中@2x.png'
import Icon1 from '@/assets/xiangying/未选中1@2x.png'
import Icon2 from '@/assets/xiangying/未选中2@2x.png'
import Icon3 from '@/assets/xiangying/未选中3@2x.png'
import Icon4 from '@/assets/xiangying/未选中4@2x.png'
const props = defineProps({
row: {
type: Object,
default: {},
},
})
const detailData = ref({})
const tableData = ref([])
const cardType = ref('0')
const cardTypeVal = ref('路段')
const filterForm = reactive({})
// 格式化时间函数
const formatDateTime = (dateStr) => {
if (!dateStr) return '-'
const date = new Date(dateStr)
const year = date.getFullYear()
const month = String(date.getMonth() + 1).padStart(2, '0')
const day = String(date.getDate()).padStart(2, '0')
const hours = String(date.getHours()).padStart(2, '0')
const minutes = String(date.getMinutes()).padStart(2, '0')
const seconds = String(date.getSeconds()).padStart(2, '0')
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`
}
// 分页
const pagination = reactive({
current: 1,
pageSize: 10,
total: 0,
pageSizes: [10, 20, 50],
layout: 'prev, pager, next, jumper',
onChange: (page, pageSize) => {
pagination.current = page
pagination.pageSize = pageSize
getAffectedSites()
},
})
// 根据预警ID获取预警详情
const getDetailData = async (id) => {
try {
const res = await request({
url: '/snow-ops-platform/weatherWarning/getById',
method: 'GET',
params: {
id: id,
},
})
if (res.code === '00000') {
detailData.value = res.data
} else {
throw new Error(res.message)
}
} catch (error) {
ElMessage.error(error.message)
console.log(error)
}
}
// 根据 cardType 获取对应的 API 路径
const getApiUrlByType = (type) => {
const urlMap = {
桥梁: '/snow-ops-platform/weather-warning/affected-object/bridge',
边坡: '/snow-ops-platform/weather-warning/affected-object/slope',
隧道: '/snow-ops-platform/weather-warning/affected-object/tunnel',
驻地: '/snow-ops-platform/weather-warning/affected-object/project',
路段: '/snow-ops-platform/weather-warning/affected-object/road-section',
}
return urlMap[type] || '/snow-ops-platform/weather-warning/affected-object/bridge'
}
// 根据类型处理数据(统一格式)
const processDataByType = (item, type) => {
return processUnifiedData(item, type)
}
// 处理数据为统一格式
const processUnifiedData = (item, type) => {
// 获取等级样式类
const getLevelClass = (level) => {
const levelMap = {
红色预警: 'level-red',
橙色预警: 'level-orange',
黄色预警: 'level-yellow',
蓝色预警: 'level-blue',
高风险: 'level-red',
中风险: 'level-orange',
低风险: 'level-blue',
}
return levelMap[level] || ''
}
// 获取影响点等级
const pointLevel = item.riskLevel || item.warningLevel || item.level || item.GL1_PDDJ || item.GL1_JSDJ || '-'
// 基础数据
const baseData = {
item,
id: item.id,
// 影响区域
region: item.GL1_QXMC || item.COUNTY || item.county || item.region || item.ADMINISTRATIVE_REGION || item.COUNTY_NAME || '-',
// 影响点类型
pointType: impactData.value[type].type || '-',
// 影响点位置(根据类型取不同字段)
pointLocation: item.GL1_QLMC || item.NAME || item.GL1_SDMC || item.PROJECT_NAME || item.GL1_LXBH || item.name || '-',
// 影响点等级
pointLevel: pointLevel,
levelClass: getLevelClass(pointLevel),
// 交通主管部门负责人
trafficDept: {
// name: item.trafficDeptName || item.trafficManager || "-",
phone: item.trafficDeptPhone || item.trafficManagerPhone || '-',
name: '-',
phone: '-',
},
// 公路机构责任人
roadOrg: {
// name: item.roadOrgName || item.roadManager || "-",
// phone: item.roadOrgPhone || item.roadManagerPhone || "-",
name: '-',
phone: '-',
},
// 养护站负责人
maintenance: {
// name: item.maintenanceName || item.maintenanceManager || "-",
// phone: item.maintenancePhone || item.maintenanceManagerPhone || "-",
name: '-',
phone: '-',
},
// 护路员
roadKeeper: {
// name: item.roadKeeperName || item.roadKeeper || "-",
// phone: item.roadKeeperPhone || item.roadKeeperPhone || "-",
name: '-',
phone: '-',
},
// 一般人员(路长履职)
generalStaff: {
// name: item.generalStaffName || item.roadChief || "-",
// phone: item.generalStaffPhone || item.roadChiefPhone || "-",
name: '-',
phone: '-',
},
// 保留原始数据供详情使用
rawData: item,
}
// 桥梁类型特殊处理根据BASE_GLQL桥梁信息表字典
if (cardTypeVal.value === '桥梁') {
return {
...baseData,
// 影响区域 - 使用区县名称
region: item.GL1_QXMC || '-',
// 影响点位置 - 使用桥梁名称
pointLocation: item.GL1_QLMC || '-',
// 影响点等级 - 使用技术状况评级等级
pointLevel: item.GL1_JSZKPJDJ || item.GL1_PDDJ || item.GL1_CSFXDJ || '-',
levelClass: getLevelClass(item.GL1_JSZKPJDJ || item.GL1_PDDJ || item.GL1_CSFXDJ),
// // 交通主管部门负责人 - 管理单位负责人GL1_DWFZR
// trafficDept: {
// name: item.GL1_DWFZR || "-",
// phone: item.GL1_GLDWDH || "-",
// },
// // 公路机构责任人 - 统计负责人GL1_TJFZR
// roadOrg: {
// name: item.GL1_TJFZR || "-",
// phone: item.GL1_TJFZRDH || "-",
// },
// // 养护站负责人 - 养护单位责任人GL1_YHDWDH
// maintenance: {
// name: item.GL1_GLDWMC || "-",
// phone: item.GL1_YHDWDH || "-",
// },
// // 护路员 - 填报人
// roadKeeper: {
// name: item.GL1_TBRXM || "-",
// phone: item.GL1_TBRDH || "-",
// },
// // 一般人员(路长履职)- 桥梁工程师GL1_QLGCS
// generalStaff: {
// name: item.GL1_QLGCS || "-",
// phone: item.GL1_QLGCSDH || "-",
// },
}
}
// 隧道类型特殊处理根据BASE_GLSD隧道信息表字典
if (cardTypeVal.value === '隧道') {
return {
...baseData,
// 影响区域 - 使用区县名称
region: item.GL1_QXMC || item.GL1_QXBM || '-',
// 影响点位置 - 使用隧道名称
pointLocation: item.GL1_SDMC || '-',
// 影响点等级 - 使用评定等级
pointLevel: item.GL1_PDDJ || item.GL1_TJJGPDDJ || item.GL1_SDYHDJ || '-',
levelClass: getLevelClass(item.GL1_PDDJ || item.GL1_TJJGPDDJ || item.GL1_SDYHDJ),
// // 交通主管部门负责人 - 管养单位行政领导GL1_GYDWXZLD
// trafficDept: {
// name: item.GL1_GYDWXZLD || "-",
// phone: item.GL1_GYDWXZLDDH || "-",
// },
// // 公路机构责任人 - 隧道路政员GL1_SDLZY
// roadOrg: {
// name: item.GL1_SDLZY || "-",
// phone: item.GL1_SDLZYDH || "-",
// },
// // 养护站负责人 - 隧道养护工GL1_SDYHG
// maintenance: {
// name: item.GL1_SDYHG || "-",
// phone: item.GL1_SDYHGDH || "-",
// },
// // 护路员 - 隧道信息填报人GL1_SDXXTBR
// roadKeeper: {
// name: item.GL1_SDXXTBR || "-",
// phone: item.GL1_SDXXTBRDH || "-",
// },
// // 一般人员(路长履职)- 管养单位养护工程师GL1_GYDWYHGCS
// generalStaff: {
// name: item.GL1_GYDWYHGCS || "-",
// phone: item.GL1_GYDWYHGSCSDH || "-",
// },
}
}
// 路段类型特殊处理根据BASE_XJLD路线信息表字典
if (cardTypeVal.value === '路段') {
return {
...baseData,
// 影响区域 - 使用区县名称
region: item.COUNTY_NAME || '-',
// 影响点位置 - 使用路线名称+起终点桩号
pointLocation:
item.GL1_QDZH && item.GL1_ZDZH
? item.GL1_LXMC + '(k' + (item.GL1_QDZH + '').replace('.', '+') + '至k' + (item.GL1_ZDZH + '').replace('.', '+') + ')'
: item.GL1_LXMC,
// 影响点等级 - 使用风险等级
pointLevel: item.GL1_FXDJ || '',
levelClass: getLevelClass(item.GL1_FXDJ),
// 交通主管部门负责人 - 使用GL1_JTXM和GL1_JTDH
trafficDept: {
name: item.GL1_JTXM || '-',
phone: item.GL1_JTDH || '-',
},
// 公路机构责任人 - 使用GL1_JGXM和GL1_JGDH
roadOrg: {
name: item.GL1_JGXM || '-',
phone: item.GL1_JGDH || '-',
},
// 养护站负责人 - 使用GL1_YHXM和GL1_YHDH
maintenance: {
name: item.GL1_YHXM || '-',
phone: item.GL1_YHDH || '-',
},
// 护路员 - 使用GL1_HLXM和GL1_HLDH
roadKeeper: {
name: item.GL1_HLXM || item.ROAD_PATROL_WORKER_NAME || '-',
phone: item.GL1_HLDH || item.ROAD_PATROL_WORKER_PHONE || '-',
},
// 一般人员(路长履职)
generalStaff: {
name: item.ROAD_CAPTAIN_NAME || '-',
phone: item.ROAD_CAPTAIN_PHONE || '-',
},
}
}
// 项目类型特殊处理根据SQL字段映射
if (cardTypeVal.value === '驻地') {
return {
...baseData,
// 影响区域 - 使用COUNTY字段
region: item.COUNTY || item.county || item.county_name || '-',
// 影响点位置 - 使用项目名称
pointLocation: item.PROJECT_NAME || item.projectName || item.name || '-',
// 驻地名称
siteName: item.SITE_NAME || item.siteName || item.PROJECT_NAME || item.projectName || '-',
// 吹哨人 - WHISTLEBLOWER_NAME / WHISTLEBLOWER_PHONE
whistleblower: {
name: item.WHISTLEBLOWER_NAME || item.whistleblowerName || '-',
phone: item.WHISTLEBLOWER_PHONE || item.whistleblowerPhone || '-',
},
// 建设单位包保责任人 - OWNER_RESPONSIBLE_PERSON / OWNER_RESPONSIBLE_PHONE
constructionUnit: {
name: item.OWNER_RESPONSIBLE_PERSON || item.ownerResponsiblePerson || '-',
phone: item.OWNER_RESPONSIBLE_PHONE || item.ownerResponsiblePhone || '-',
},
// 施工单位包保责任人 - CONSTRUCTOR_RESPONSIBLE_PERSON / CONSTRUCTOR_RESPONSIBLE_PHONE
constructionDept: {
name: item.CONSTRUCTOR_RESPONSIBLE_PERSON || item.constructorResponsiblePerson || '-',
phone: item.CONSTRUCTOR_RESPONSIBLE_PHONE || item.constructorResponsiblePhone || '-',
},
// 驻地包保责任人 - SITE_RESPONSIBLE_PERSON / SITE_RESPONSIBLE_PHONE
siteResponsible: {
name: item.SITE_RESPONSIBLE_PERSON || item.siteResponsiblePerson || '-',
phone: item.SITE_RESPONSIBLE_PHONE || item.siteResponsiblePhone || '-',
},
// 区县级包保责任人 - DISTRICT_RESPONSIBLE_PERSON / DISTRICT_RESPONSIBLE_PHONE
countyResponsible: {
name: item.DISTRICT_RESPONSIBLE_PERSON || item.districtResponsiblePerson || '-',
phone: item.DISTRICT_RESPONSIBLE_PHONE || item.districtResponsiblePhone || '-',
},
// 市级包保责任人 - CITY_RESPONSIBLE_PERSON / CITY_RESPONSIBLE_PHONE
cityResponsible: {
name: item.CITY_RESPONSIBLE_PERSON || item.cityResponsiblePerson || '-',
phone: item.CITY_RESPONSIBLE_PHONE || item.cityResponsiblePhone || '-',
},
}
}
return baseData
}
// 根据区县ID、预警生效时间和预警结束时间 查询受影响的驻地列表
const getAffectedSites = async () => {
try {
const res = await request({
url: getApiUrlByType(cardTypeVal.value),
method: 'GET',
params: {
start: formatDateTime(props.row.onset),
end: formatDateTime(props.row.expires),
limit: pagination.pageSize,
offset: (pagination.current - 1) * pagination.pageSize,
countyId: props.row.areaCode,
...filterForm,
},
})
// 路段存在分页功能了,需要处理返回数据
if (cardTypeVal.value == '路段') {
if (res.data) {
tableData.value = res.data.map((item, index) => ({
...processDataByType(item, cardType.value),
id: index + 1,
}))
pagination.total = res.total || 0
}
} else if (cardTypeVal.value == '驻地') {
if (res.data) {
tableData.value = res.data.map((item, index) => ({
...processDataByType(item, cardType.value),
id: index + 1,
}))
pagination.total = res.total || 0
}
} else if (cardTypeVal.value == '桥梁') {
if (res.data) {
tableData.value = res.data.map((item, index) => ({
...processDataByType(item, cardType.value),
id: index + 1,
}))
pagination.total = res.total || 0
}
} else {
if (res.code === '00000' && res.data) {
// 处理返回数据
const allData = res.data
pagination.total = allData.length || 0
// 客户端分页:计算当前页的数据范围
const startIndex = (currentPage.value - 1) * pageSize.value
const endIndex = startIndex + pageSize.value
const currentPageData = allData.slice(startIndex, endIndex)
tableData.value = currentPageData.map((item, index) => ({
...processDataByType(item, cardType.value),
id: startIndex + index + 1,
}))
} else {
tableData.value = []
pagination.total = 0
}
}
} catch (error) {
console.error('获取影响点数据失败:', error)
tableData.value = []
pagination.total = 0
}
}
// 影响点等级选项
const pointLevelOptions = [
{ label: '全部', value: '' },
{ label: '低风险', value: '低风险' },
{ label: '中风险', value: '中风险' },
{ label: '较高风险', value: '较高风险' },
{ label: '高风险', value: '高风险' },
]
// 影响区域选项
const regionOptions = ref([])
const fetchDistrictOptions = async () => {
try {
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) => ({
label: item.qxmc,
value: item.xzdm,
}))
// 保留"全部"选项,并添加接口返回的数据
regionOptions.value = [{ label: '全部', value: '' }, ...options]
return options
}
} catch (error) {
console.error('获取影响区域选项失败:', error)
}
return regionOptions.value
}
// 公路类型选项
const roadTypeOptions = ref([
{ label: '全部', value: '' },
{ label: '国省道', value: 'G,S' },
{ label: '农村公路', value: ' X, Y, C' },
])
// 筛选条件改变时触发
const handleFilterChange = () => {
pagination.current = 1
getAffectedSites()
}
// 影响点数据
const impactData = ref([
{ name: '影响路段', count: 0, icon: Icon4, type: '路段' },
{ name: '影响桥梁', count: 0, icon: Icon0, type: '桥梁' },
{ name: '影响隧道', count: 0, icon: Icon2, type: '隧道' },
{ name: '影响边坡', count: 0, icon: Icon1, type: '边坡' },
{ name: '影响驻地', count: 0, icon: Icon3, type: '驻地' },
])
// 顶部卡片数据
const loadBarChartData = async () => {
try {
const res = await request({
url: '/snow-ops-platform/weather-warning/affected-count',
method: 'GET',
params: {
start: formatDateTime(props.row.onset),
end: formatDateTime(props.row.expires),
},
})
if (res.code == '00000') {
const data = res.data
if (data && Array.isArray(data)) {
// 英文转中文映射
const nameMap = {
'road-section': '路段',
bridge: '桥梁',
tunnel: '隧道',
slope: '边坡',
project: '驻地',
Road: '路段',
Bridge: '桥梁',
Tunnel: '隧道',
Slope: '边坡',
Project: '驻地',
}
// 转换英文name为中文
const convertedData = data.map((item) => {
if (item.extension == '项目') {
item.extension = '驻地'
}
const name = nameMap[item.name] || item.name
return { ...item, name }
})
convertedData.forEach((item) => {
impactData.value.forEach((stat) => {
if (stat.name.includes(item.extension)) {
stat.count = item.count || 0
}
})
})
}
}
} catch (error) {
console.error('加载数据失败:', error)
}
}
// 点击卡片切换
const handleClick = (index, item) => {
tableData.value = []
cardType.value = index + ''
cardTypeVal.value = item.type
columns.value = getColumnsByType(item.type)
pagination.current = 1
getAffectedSites()
}
// 统一的表格列配置(桥梁、边坡、隧道、路段使用)
const unifiedColumns = [
{ prop: 'id', label: '序号', width: '' },
{ prop: 'region', label: '影响区域', width: '' },
{ prop: 'pointType', label: '影响点类型', width: '' },
{ prop: 'pointLocation', label: '影响点位置', width: '' },
{ prop: 'pointLevel', label: '影响点等级', width: '', slot: 'pointLevel' },
{
prop: 'trafficDept',
label: '交通主管部门负责人',
width: '',
slot: 'trafficDept',
},
{ prop: 'roadOrg', label: '公路机构责任人', width: '', slot: 'roadOrg' },
{
prop: 'maintenance',
label: '养护站负责人',
width: '',
slot: 'maintenance',
},
{ prop: 'roadKeeper', label: '护路员', width: '', slot: 'roadKeeper' },
{
prop: 'generalStaff',
label: '一般人员(路长履职)',
width: '',
slot: 'generalStaff',
},
{
prop: 'operation',
label: '操作',
width: '80',
slot: 'operation',
fixed: 'right',
},
]
// 项目类型专用表格列配置
const projectColumns = [
{ prop: 'id', label: '序号', width: '60' },
{ prop: 'region', label: '影响区域', width: '' },
{ prop: 'pointType', label: '影响点类型', width: '' },
{ prop: 'siteName', label: '驻地名称', width: '' },
{
prop: 'whistleblower',
label: '吹哨人',
width: '',
slot: 'whistleblower',
},
{
prop: 'constructionUnit',
label: '建设单位包保责任人',
width: '',
slot: 'constructionUnit',
},
{
prop: 'constructionDept',
label: '施工单位包保责任人',
width: '',
slot: 'constructionDept',
},
{
prop: 'siteResponsible',
label: '驻地包保责任人',
width: '',
slot: 'siteResponsible',
},
{
prop: 'countyResponsible',
label: '区县级包保责任人',
width: '',
slot: 'countyResponsible',
},
{
prop: 'cityResponsible',
label: '市级包保责任人',
width: '',
slot: 'cityResponsible',
},
]
const columns = ref(unifiedColumns)
// 根据 cardType 获取对应的表格列配置
const getColumnsByType = (type) => {
const typeMap = {
路段: unifiedColumns,
桥梁: unifiedColumns,
隧道: unifiedColumns,
边坡: unifiedColumns,
驻地: projectColumns,
}
return typeMap[type] || unifiedColumns
}
onMounted(async () => {
await loadBarChartData()
await fetchDistrictOptions()
await getDetailData(props.row.id)
await getAffectedSites()
})
</script>
<style scoped lang="scss">
.detail-container {
display: flex;
flex-direction: column;
gap: 20px;
}
.table {
display: flex;
flex-direction: column;
gap: 10px;
}
// 统计卡片
.stats-cards {
display: flex;
gap: 12px;
background-color: #16334e;
padding: 5px;
.stat-card {
display: flex;
align-items: center;
gap: 8px;
padding: 8px 16px;
transition: all 0.3s;
cursor: pointer;
flex: 1;
&:hover {
background: rgba(30, 70, 120, 0.9);
border-color: rgba(64, 169, 255, 0.6);
box-shadow: 0 0 15px rgba(64, 169, 255, 0.3);
}
.stat-icon {
font-size: 18px;
color: #40a9ff;
width: 34px;
height: 34px;
display: flex;
align-items: center;
justify-content: center;
img {
width: 100%;
height: 100%;
}
}
.stat-content {
display: flex;
align-items: center;
gap: 6px;
.stat-label {
font-size: 14px;
color: #fff;
font-weight: 500;
}
.stat-value {
font-size: 14px;
font-weight: bold;
color: #40a9ff;
}
}
}
}
.search-form {
display: flex;
flex-direction: row;
align-items: center;
flex-wrap: wrap;
gap: 12px;
.search-item {
width: 150px;
}
}
</style>