feat: 气象预警响应
This commit is contained in:
parent
285bc95b91
commit
7eef4cfd92
@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<div class="card-item">
|
||||
<slot name="header">
|
||||
<slot name="header" v-if="title">
|
||||
<div class="header" :style="{marginBottom: titleGap + 'px'}">
|
||||
<div class="header-title">{{ title }}</div>
|
||||
<div class="header-extra" v-if="$slots.headerExtra">
|
||||
|
||||
@ -52,7 +52,7 @@ const eventTypeOptions = [
|
||||
|
||||
// 返回上一页
|
||||
const handleClickBack = () => {
|
||||
router.replace('/disasterManagement')
|
||||
router.go(-1)
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
@ -1,20 +1,26 @@
|
||||
<template>
|
||||
<PageContainer title="气象预警" @click-back="handleClickBack">
|
||||
<SearchInput v-model="searchValue" />
|
||||
<SearchInput v-model="searchValue" @update:modelValue="handleSearch" />
|
||||
|
||||
<CurrentSite />
|
||||
|
||||
<div class="list-panel">
|
||||
<CardItem v-for="(item, index) in list" :key="index" :title="item.area" @click="handleClickItem(item)">
|
||||
<CardItem
|
||||
v-for="(item, index) in list"
|
||||
:key="index"
|
||||
:class="['card-item', item.status]"
|
||||
:title="item.title"
|
||||
@click="handleClickItem(item)"
|
||||
>
|
||||
<template #headerExtra>
|
||||
<span class="red-ball"></span>
|
||||
<span v-if="item.status === 'unread'" class="red-ball"></span>
|
||||
</template>
|
||||
|
||||
<!-- 发布时间 block -->
|
||||
<div class="time-block">
|
||||
<div class="time-box">
|
||||
<span class="time-label-text">发布时间:</span>
|
||||
<span class="time-value-text">{{ item.publishTime }}</span>
|
||||
<span class="time-value-text">{{ item.onset }}</span>
|
||||
</div>
|
||||
<van-icon class="jump-icon" name="arrow" />
|
||||
</div>
|
||||
@ -27,67 +33,89 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, onMounted, watch } from 'vue'
|
||||
import { useRouter, useRoute } from 'vue-router'
|
||||
import PageContainer from '@/components/PageContainer.vue'
|
||||
import SearchInput from '@/components/SearchInput.vue'
|
||||
import CurrentSite from '@/components/CurrentSite.vue'
|
||||
import CardItem from '@/components/CardItem.vue'
|
||||
import EmptyBox from '@/components/EmptyBox.vue'
|
||||
import { ref, onMounted } from 'vue';
|
||||
import { useRouter, useRoute } from 'vue-router';
|
||||
import PageContainer from '@/components/PageContainer.vue';
|
||||
import SearchInput from '@/components/SearchInput.vue';
|
||||
import CurrentSite from '@/components/CurrentSite.vue';
|
||||
import CardItem from '@/components/CardItem.vue';
|
||||
import EmptyBox from '@/components/EmptyBox.vue';
|
||||
import warningMessageMock from './mock.json';
|
||||
import { request } from '@shared/utils/request';
|
||||
import { showToast } from 'vant';
|
||||
|
||||
const router = useRouter()
|
||||
const router = useRouter();
|
||||
const route = useRoute();
|
||||
|
||||
onMounted(() => {
|
||||
getData()
|
||||
})
|
||||
getData();
|
||||
});
|
||||
|
||||
// 搜索关键词
|
||||
const searchValue = ref('')
|
||||
|
||||
watch(
|
||||
() => searchValue.value,
|
||||
(newVal, oldVal) => {
|
||||
if (newVal !== oldVal) {
|
||||
getData(newVal)
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
const searchValue = ref('');
|
||||
|
||||
const handleSearch = value => {
|
||||
getData(value);
|
||||
};
|
||||
|
||||
// 预警列表数据
|
||||
const list = ref([
|
||||
{
|
||||
area: '合川区',
|
||||
level: '红色气象预警',
|
||||
publishTime: '2026/01/10 20:29'
|
||||
},
|
||||
{
|
||||
area: '万州区',
|
||||
level: '红色气象预警',
|
||||
publishTime: '2025/10/10 20:29'
|
||||
},
|
||||
{
|
||||
area: '涪陵区',
|
||||
level: '红色气象预警',
|
||||
publishTime: '2025/10/10 20:29'
|
||||
const list = ref([]);
|
||||
|
||||
const getData = async (keyword = '') => {
|
||||
const normalizedKeyword = keyword.trim();
|
||||
|
||||
if (route.query.mock) {
|
||||
list.value = warningMessageMock.filter(item => {
|
||||
if (!normalizedKeyword) return true;
|
||||
return item.title.includes(normalizedKeyword);
|
||||
});
|
||||
return;
|
||||
}
|
||||
])
|
||||
|
||||
const getData = async () => {
|
||||
try {
|
||||
const result = await request({
|
||||
url: '/snow-ops-platform/weather-warning/notice',
|
||||
method: 'get',
|
||||
params: {
|
||||
pageSize: 999,
|
||||
keyword: normalizedKeyword,
|
||||
},
|
||||
});
|
||||
list.value = result.data.records;
|
||||
} catch (_error) {
|
||||
showToast('获取数据失败,请稍后重试');
|
||||
list.value = [];
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
const markNoticeRead = async id => {
|
||||
await request({
|
||||
url: `/snow-ops-platform/weather-warning/notice/${id}/_read`,
|
||||
method: 'put',
|
||||
});
|
||||
};
|
||||
|
||||
const handleClickItem = (item) => {
|
||||
const handleClickItem = async item => {
|
||||
try {
|
||||
await markNoticeRead(item.id);
|
||||
item.status = 'read';
|
||||
} catch (_error) {
|
||||
showToast('更新已读状态失败');
|
||||
}
|
||||
|
||||
window.warningMessageDetail = item;
|
||||
router.push({
|
||||
path: '/warningMessage-detail',
|
||||
|
||||
})
|
||||
}
|
||||
name: 'WarningMessageDetail',
|
||||
query: {
|
||||
id: item.id,
|
||||
mock: route.query.mock,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const handleClickBack = () => {
|
||||
router.push('/')
|
||||
}
|
||||
router.push('/');
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@ -98,10 +126,6 @@ const handleClickBack = () => {
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.card-item {
|
||||
padding-right: 15px;
|
||||
}
|
||||
|
||||
/* ==================== Block 层级 ==================== */
|
||||
.time-block {
|
||||
display: flex;
|
||||
@ -126,9 +150,13 @@ const handleClickBack = () => {
|
||||
border-radius: 100%;
|
||||
}
|
||||
|
||||
.card-item {
|
||||
padding-right: 15px;
|
||||
}
|
||||
|
||||
.jump-icon {
|
||||
font-size: 16px;
|
||||
color: rgb(102, 102, 102, .4);
|
||||
color: rgb(102, 102, 102, 0.4);
|
||||
}
|
||||
|
||||
.time-label-text {
|
||||
@ -142,4 +170,4 @@ const handleClickBack = () => {
|
||||
font-size: 14px;
|
||||
color: #666666;
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
|
||||
@ -1,95 +1,154 @@
|
||||
<template>
|
||||
<PageContainer class="page-container" title="预警详情" @click-back="handleClickBack">
|
||||
<CurrentSite />
|
||||
<CurrentSite />
|
||||
|
||||
<PanelHeader title="气象预警" />
|
||||
<div class="list-panel margin">
|
||||
<CardItem class="card-item" v-for="(item, index) in list" :key="index" :title="item.area" titleGap="10">
|
||||
<CardItem class="card-item" :title="detail?.title">
|
||||
<template #content>
|
||||
<div class="time-block">
|
||||
<div class="time-box">
|
||||
<span class="time-label-text">发布时间:</span>
|
||||
<span class="time-value-text">{{ item.publishTime }}</span>
|
||||
<span class="time-value-text">{{ detail?.onset }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="desc-block">
|
||||
<div class="desc-text">{{ item.content }}</div>
|
||||
<div class="desc-text">{{ detail?.warningContent }}</div>
|
||||
</div>
|
||||
|
||||
<van-icon class="jump-icon" name="arrow" />
|
||||
</template>
|
||||
</CardItem>
|
||||
|
||||
<!-- 空状态提示 -->
|
||||
<EmptyBox v-if="list.length === 0" placeholder="暂无相关预警信息" />
|
||||
<EmptyBox v-if="!detail?.warningContent" placeholder="暂无相关预警信息" />
|
||||
</div>
|
||||
|
||||
<PanelHeader title="防御措施" />
|
||||
<div class="list-panel">
|
||||
<CardItem v-for="(item, index) in list" :key="index" :title="item.area">
|
||||
<div class="method-block">
|
||||
<div class="method-text">{{ item.method }}</div>
|
||||
</div>
|
||||
<van-icon class="jump-icon" name="arrow" />
|
||||
<PanelHeader title="工作建议" />
|
||||
<div class="list-panel margin">
|
||||
<CardItem class="card-item">
|
||||
<template #content>{{ detail?.workSuggestion }}</template>
|
||||
</CardItem>
|
||||
|
||||
<!-- 空状态提示 -->
|
||||
<EmptyBox v-if="list.length === 0" placeholder="暂无相关防御措施" />
|
||||
<EmptyBox v-if="!detail?.workSuggestion" placeholder="暂无相关防御措施" />
|
||||
</div>
|
||||
|
||||
<PanelHeader title="影响范围" />
|
||||
<CardItem class="card-item impact-card">
|
||||
<template #content>
|
||||
<div class="impact-tab-block">
|
||||
<div
|
||||
class="impact-tab-item"
|
||||
:class="{ active: activeImpactTab === item.value }"
|
||||
v-for="item in impactTabs"
|
||||
:key="item.value"
|
||||
@click="activeImpactTab = item.value"
|
||||
>
|
||||
{{ item.label }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="impact-table" v-if="currentImpactList.length">
|
||||
<div class="impact-table-header" :class="`cols-${currentImpactColumns.length}`">
|
||||
<div class="impact-table-cell" v-for="column in currentImpactColumns" :key="column.key">
|
||||
{{ column.label }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="impact-table-body">
|
||||
<div class="impact-table-row" :class="`cols-${currentImpactColumns.length}`" v-for="(item, index) in currentImpactList" :key="index">
|
||||
<div class="impact-table-cell" v-for="column in currentImpactColumns" :key="column.key">
|
||||
{{ item[column.key] }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<EmptyBox v-else placeholder="暂无相关影响范围" />
|
||||
</template>
|
||||
</CardItem>
|
||||
|
||||
<div class="footer-panel">
|
||||
<van-button block type="primary" @click="handleClickBack()">我已知晓</van-button>
|
||||
<!-- <van-button block type="primary" @click="handleClickBack()">我已知晓</van-button> -->
|
||||
<van-button block type="primary" @click="goToHandle()">我要响应</van-button>
|
||||
</div>
|
||||
</PageContainer>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, onMounted } from 'vue'
|
||||
import { useRouter, useRoute } from 'vue-router'
|
||||
import PageContainer from '@/components/PageContainer.vue'
|
||||
import CurrentSite from '@/components/CurrentSite.vue'
|
||||
import PanelHeader from '@/components/PanelHeader.vue'
|
||||
import CardItem from '@/components/CardItem.vue'
|
||||
import EmptyBox from '@/components/EmptyBox.vue'
|
||||
import { ref, onMounted, computed } from 'vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
import PageContainer from '@/components/PageContainer.vue';
|
||||
import CurrentSite from '@/components/CurrentSite.vue';
|
||||
import PanelHeader from '@/components/PanelHeader.vue';
|
||||
import CardItem from '@/components/CardItem.vue';
|
||||
import EmptyBox from '@/components/EmptyBox.vue';
|
||||
|
||||
const router = useRouter()
|
||||
const router = useRouter();
|
||||
|
||||
const detail = ref({
|
||||
id: null,
|
||||
status: '',
|
||||
title: '',
|
||||
onset: '',
|
||||
warningContent: '',
|
||||
workSuggestion: '',
|
||||
affectedPoints: [],
|
||||
affectedProjects: [],
|
||||
});
|
||||
const activeImpactTab = ref('point');
|
||||
const impactTabs = [
|
||||
{ label: '影响点', value: 'point' },
|
||||
{ label: '影响项目', value: 'project' },
|
||||
];
|
||||
|
||||
onMounted(() => {
|
||||
getData()
|
||||
})
|
||||
getData();
|
||||
});
|
||||
|
||||
// 列表数据
|
||||
const list = ref([
|
||||
{
|
||||
area: '合川区',
|
||||
level: '红色气象预警',
|
||||
content: '今天,北京将进入大雪,请做好',
|
||||
publishTime: '2026/01/10 20:29',
|
||||
method: '立即启动防汛Ⅰ级应急响应,立即转移危险区'
|
||||
},
|
||||
{
|
||||
area: '万州区',
|
||||
level: '红色气象预警',
|
||||
publishTime: '2025/10/10 20:29',
|
||||
method: '立即启动防汛Ⅰ级应急响应,立即转移危险区'
|
||||
},
|
||||
{
|
||||
area: '涪陵区',
|
||||
level: '红色气象预警',
|
||||
publishTime: '2025/10/10 20:29',
|
||||
method: '立即启动防汛Ⅰ级应急响应,立即转移危险区'
|
||||
const impactColumnsMap = {
|
||||
point: [
|
||||
{ label: '序号', key: 'index' },
|
||||
{ label: '影响区域', key: 'affectedCounty' },
|
||||
{ label: '线路编号', key: 'roadCode' },
|
||||
{ label: '类型', key: 'type' },
|
||||
{ label: '桩号', key: 'station' },
|
||||
],
|
||||
project: [
|
||||
{ label: '序号', key: 'index' },
|
||||
{ label: '影响区域', key: 'affectedCounty' },
|
||||
{ label: '项目名称', key: 'projectName' },
|
||||
{ label: '驻地名称', key: 'siteName' },
|
||||
{ label: '线路编号', key: 'roadCode' },
|
||||
],
|
||||
};
|
||||
|
||||
const currentImpactColumns = computed(() => impactColumnsMap[activeImpactTab.value] || []);
|
||||
const impactListMap = computed(() => ({
|
||||
point: detail.value.affectedPoints,
|
||||
project: detail.value.affectedProjects,
|
||||
}));
|
||||
|
||||
const currentImpactList = computed(() => {
|
||||
return impactListMap.value[activeImpactTab.value].map((item, index) => ({
|
||||
index: index + 1,
|
||||
...item,
|
||||
}));
|
||||
});
|
||||
|
||||
const getData = async () => {
|
||||
if (!window.warningMessageDetail) {
|
||||
return;
|
||||
}
|
||||
])
|
||||
|
||||
const getData = async () => {}
|
||||
detail.value = window.warningMessageDetail;
|
||||
};
|
||||
|
||||
const handleClickBack = () => {
|
||||
router.go(-1)
|
||||
}
|
||||
router.go(-1);
|
||||
};
|
||||
const goToHandle = () => {
|
||||
router.push({path: '/warningMessageHandle'})
|
||||
}
|
||||
router.push({ path: '/disasterReport' });
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@ -164,7 +223,7 @@ const goToHandle = () => {
|
||||
font-weight: 400;
|
||||
font-size: 14px;
|
||||
color: #666666;
|
||||
line-height: 13px;
|
||||
line-height: 20px;
|
||||
}
|
||||
.method-text {
|
||||
font-weight: 500;
|
||||
@ -172,4 +231,75 @@ const goToHandle = () => {
|
||||
color: #4a4a4a;
|
||||
line-height: 20px;
|
||||
}
|
||||
|
||||
.impact-card {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.impact-tab-block {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
border: 1px solid #d9d9d9;
|
||||
overflow: hidden;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.impact-tab-item {
|
||||
height: 48px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-weight: 600;
|
||||
font-size: 16px;
|
||||
color: #4a4a4a;
|
||||
background: #ffffff;
|
||||
|
||||
& + .impact-tab-item {
|
||||
border-left: 1px solid #d9d9d9;
|
||||
}
|
||||
|
||||
&.active {
|
||||
color: #ffffff;
|
||||
background: linear-gradient(180deg, #2f79f6 0%, #1f66e0 100%);
|
||||
}
|
||||
}
|
||||
|
||||
.impact-table {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.impact-table-header,
|
||||
.impact-table-row {
|
||||
display: grid;
|
||||
}
|
||||
|
||||
.cols-5 {
|
||||
grid-template-columns: 52px 1fr 1fr 1fr 1.2fr;
|
||||
}
|
||||
|
||||
.impact-table-header {
|
||||
background: linear-gradient(180deg, #e5e5e5 0%, #d8d8d8 100%);
|
||||
}
|
||||
|
||||
.impact-table-row {
|
||||
background: #ffffff;
|
||||
|
||||
& + .impact-table-row {
|
||||
border-top: 1px solid #f0f0f0;
|
||||
}
|
||||
}
|
||||
|
||||
.impact-table-cell {
|
||||
min-height: 52px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 8px 6px;
|
||||
text-align: center;
|
||||
font-weight: 500;
|
||||
font-size: 14px;
|
||||
color: #4a4a4a;
|
||||
line-height: 20px;
|
||||
word-break: break-word;
|
||||
}
|
||||
</style>
|
||||
|
||||
62
packages/mobile/src/views/WarningMessage/mock.json
Normal file
62
packages/mobile/src/views/WarningMessage/mock.json
Normal file
@ -0,0 +1,62 @@
|
||||
[
|
||||
{
|
||||
"id": 1,
|
||||
"status": "unread",
|
||||
"title": "潼南区气象台发布暴雨蓝色预警[Ⅳ级/一般]",
|
||||
"onset": "2026/01/10 20:29",
|
||||
"warningContent": "区气象台发布暴雨红色预警,按照相关要求,启动I级防御响应,并请及时关注地质、水文等风险提示信息,落实主动封闭管控/“关停撤转”措施。",
|
||||
"workSuggestion": "立即启动防汛Ⅰ级应急响应,立即转移危险区,加强沿线桥梁、隧道及在建项目巡查。",
|
||||
"affectedPoints": [
|
||||
{
|
||||
"affectedCounty": "巫溪县",
|
||||
"roadCode": "G242",
|
||||
"type": "桥梁",
|
||||
"station": "336.800-336.850"
|
||||
},
|
||||
{
|
||||
"affectedCounty": "万州区",
|
||||
"roadCode": "G242",
|
||||
"type": "隧道",
|
||||
"station": "336.800-336.850"
|
||||
}
|
||||
],
|
||||
"affectedProjects": [
|
||||
{
|
||||
"affectedCounty": "巫溪县",
|
||||
"projectName": "G242灾害防治工程",
|
||||
"siteName": "巫溪驻地",
|
||||
"roadCode": "G242"
|
||||
},
|
||||
{
|
||||
"affectedCounty": "万州区",
|
||||
"projectName": "G242改扩建工程",
|
||||
"siteName": "万州驻地",
|
||||
"roadCode": "G242"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"status": "read",
|
||||
"title": "潼南区气象台发布暴雨蓝色预警[Ⅳ级/一般]",
|
||||
"onset": "2026/01/09 07:10",
|
||||
"warningContent": "预计未来12小时局地将出现能见度小于500米的大雾天气,请注意行车安全并做好道路巡查。",
|
||||
"workSuggestion": "加强重点路段值守,及时发布诱导信息,视情采取限速、分流等措施。",
|
||||
"affectedPoints": [
|
||||
{
|
||||
"affectedCounty": "涪陵区",
|
||||
"roadCode": "S105",
|
||||
"type": "高边坡",
|
||||
"station": "K12+300-K13+100"
|
||||
}
|
||||
],
|
||||
"affectedProjects": [
|
||||
{
|
||||
"affectedCounty": "涪陵区",
|
||||
"projectName": "S105养护提升工程",
|
||||
"siteName": "涪陵驻地",
|
||||
"roadCode": "S105"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
Loading…
x
Reference in New Issue
Block a user