渝路畅行大屏页面,法规处相关管理页面完成,预警信息h5页面

This commit is contained in:
fanjia 2026-03-30 10:57:32 +08:00
parent aed67a2cc8
commit 9220282ff1
37 changed files with 14892 additions and 254 deletions

View File

@ -66,6 +66,16 @@ const routes = [
name: 'IceEventDetail', name: 'IceEventDetail',
component: () => import('../views/IceEvent/IceEventDetails.vue') component: () => import('../views/IceEvent/IceEventDetails.vue')
}, },
{
path: '/warningMessage/:data',
name: 'WarningMessage',
component: () => import('../views/WarningMessage/WarningMessage.vue')
},
{
path: '/warningMessage-detail/:data',
name: 'WarningMessageDetail',
component: () => import('../views/WarningMessage/WarningMessageDetail.vue')
},
] ]
const router = createRouter({ const router = createRouter({

View File

@ -40,6 +40,14 @@
params: { data: encodeURIComponent(JSON.stringify(yhzinfo)) }, params: { data: encodeURIComponent(JSON.stringify(yhzinfo)) },
}" }"
/> />
<van-grid-item
icon="warning-o"
text="气象预警"
:to="{
name: 'WarningMessage',
params: { data: encodeURIComponent(JSON.stringify(yhzinfo)) },
}"
/>
</van-grid> </van-grid>
</div> </div>
<van-popup <van-popup

View File

@ -0,0 +1,362 @@
<template>
<div class="warning-message-page">
<!-- 顶部导航栏 -->
<div class="nav-bar">
<div class="back-btn" @click="goBack">
<van-icon name="arrow-left" />
</div>
<div class="title">气象预警</div>
<div class="placeholder"></div>
</div>
<!-- 搜索栏 -->
<div class="search-section">
<div class="search-box">
<span class="search-text">关键词</span>
<van-icon name="search" class="search-icon" />
</div>
</div>
<!-- 当前站点 -->
<div class="current-site">
<span class="site-label">当前站点</span>
<span class="site-name">{{ currentSite }}</span>
</div>
<!-- 预警列表 -->
<div class="warning-list">
<div
v-for="(item, index) in warningList"
:key="index"
class="warning-item"
:class="{ unread: !item.isRead }"
@click="viewDetail(item)"
>
<div class="item-content">
<div class="warning-title">{{ item.title }}</div>
<div class="warning-time">
<span class="time-label">发布时间</span>
<span class="time-value">{{ item.publishTime }}</span>
</div>
</div>
<div class="item-right">
<div v-if="!item.isRead" class="unread-dot"></div>
<van-icon name="arrow" class="arrow-icon" />
</div>
</div>
</div>
<!-- 空状态 -->
<div v-if="warningList.length === 0" class="empty-state">
<van-icon name="warning-o" class="empty-icon" />
<div class="empty-text">暂无预警消息</div>
</div>
</div>
</template>
<script setup>
import { ref } from "vue";
import { useRouter, useRoute } from "vue-router";
const router = useRouter();
const route = useRoute();
//
const currentSite = ref("李家坝仓库");
//
const initSiteInfo = () => {
const { data } = route.params;
if (data) {
try {
const yhzinfo = JSON.parse(decodeURIComponent(data));
currentSite.value = yhzinfo.mc || "李家坝仓库";
} catch (e) {
console.error("解析站点信息失败", e);
}
}
};
initSiteInfo();
//
const warningList = ref([
{
id: 1,
title: "合川区暴雨红色预警",
publishTime: "2025/10/10 20:29",
isRead: false,
level: "red",
type: "rain",
},
{
id: 2,
title: "合川区红色气象预警",
publishTime: "2025/10/09 20:29",
isRead: true,
level: "red",
type: "weather",
},
]);
//
const goBack = () => {
console.log("返回上一页");
// 使
// router.back()
};
//
const viewDetail = (item) => {
//
item.isRead = true;
//
router.push({
name: "WarningMessageDetail",
params: {
id: item.id,
data: route.params.data,
},
});
};
</script>
<style lang="scss" scoped>
//
$primary-color: #409eff;
$text-color: #333;
$text-secondary: #666;
$text-tertiary: #999;
$bg-color: #f5f5f5;
$border-color: #e0e0e0;
$unread-color: #f56c6c;
.warning-message-page {
min-height: 100vh;
background-color: $bg-color;
padding-bottom: 20px;
//
.nav-bar {
display: flex;
align-items: center;
justify-content: space-between;
height: 44px;
background-color: #fff;
padding: 0 15px;
position: sticky;
top: 0;
z-index: 100;
.back-btn {
width: 40px;
height: 40px;
display: flex;
align-items: center;
justify-content: flex-start;
color: $text-color;
font-size: 20px;
}
.title {
flex: 1;
text-align: center;
font-size: 17px;
font-weight: 500;
color: $text-color;
}
.placeholder {
width: 40px;
}
}
//
.search-section {
padding: 12px 15px;
background-color: #fff;
.search-box {
display: flex;
align-items: center;
justify-content: space-between;
height: 36px;
background-color: #e8e8e8;
border-radius: 18px;
padding: 0 15px;
.search-text {
font-size: 14px;
color: $text-tertiary;
}
.search-icon {
font-size: 18px;
color: $text-tertiary;
}
}
}
//
.current-site {
padding: 10px 15px;
background-color: #fff;
text-align: center;
font-size: 13px;
border-bottom: 1px solid $border-color;
.site-label {
color: $text-secondary;
}
.site-name {
color: $primary-color;
}
}
//
.warning-list {
padding: 10px 15px;
.warning-item {
display: flex;
align-items: center;
justify-content: space-between;
background-color: #fff;
border-radius: 8px;
padding: 15px;
margin-bottom: 10px;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.05);
&:active {
background-color: #f9f9f9;
}
.item-content {
flex: 1;
min-width: 0;
.warning-title {
font-size: 15px;
font-weight: 500;
color: $text-color;
margin-bottom: 8px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.warning-time {
font-size: 12px;
color: $text-tertiary;
.time-label {
color: $text-secondary;
}
}
}
.item-right {
display: flex;
align-items: center;
gap: 8px;
margin-left: 10px;
.unread-dot {
width: 8px;
height: 8px;
border-radius: 50%;
background-color: $unread-color;
flex-shrink: 0;
}
.arrow-icon {
font-size: 16px;
color: $text-tertiary;
flex-shrink: 0;
}
}
//
&.unread {
.warning-title {
font-weight: 600;
}
}
}
}
//
.empty-state {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 60px 20px;
.empty-icon {
font-size: 48px;
color: $text-tertiary;
margin-bottom: 12px;
}
.empty-text {
font-size: 14px;
color: $text-secondary;
}
}
}
// iPhone
@supports (padding-top: env(safe-area-inset-top)) {
.warning-message-page {
.nav-bar {
padding-top: env(safe-area-inset-top);
height: calc(44px + env(safe-area-inset-top));
}
}
}
//
@media screen and (max-width: 320px) {
.warning-message-page {
.warning-list {
.warning-item {
.item-content {
.warning-title {
font-size: 14px;
}
}
}
}
}
}
@media screen and (min-width: 414px) {
.warning-message-page {
.nav-bar {
height: 48px;
.title {
font-size: 18px;
}
}
.warning-list {
.warning-item {
padding: 18px;
.item-content {
.warning-title {
font-size: 16px;
}
.warning-time {
font-size: 13px;
}
}
}
}
}
}
</style>

View File

@ -0,0 +1,395 @@
<template>
<div class="warning-detail-page">
<!-- 顶部导航栏 -->
<div class="nav-bar">
<div class="back-btn" @click="goBack">
<van-icon name="arrow-left" />
</div>
<div class="title">气象预警</div>
<div class="placeholder"></div>
</div>
<!-- 当前站点 -->
<div class="current-site">
<span class="site-label">当前站点</span>
<span class="site-name">{{ currentSite }}</span>
</div>
<!-- 预警详情内容 -->
<div class="detail-content" v-if="warningDetail">
<!-- 预警标题区域 -->
<div class="warning-header">
<div class="header-title">气象预警</div>
</div>
<!-- 预警信息卡片 -->
<div class="info-card">
<div class="warning-name">{{ warningDetail.title }}</div>
<div class="publish-time">
<span class="time-label">发布时间</span>
<span class="time-value">{{ warningDetail.publishTime }}</span>
</div>
</div>
<!-- 预警描述 -->
<div class="description-card">
<div class="desc-text">{{ warningDetail.description }}</div>
</div>
<!-- 防御措施 -->
<div class="measures-section">
<div class="section-title">防御措施</div>
<div class="measures-card">
<div class="measures-text">{{ warningDetail.measures }}</div>
</div>
</div>
</div>
<!-- 底部按钮 -->
<div class="bottom-action">
<van-button type="primary" block round @click="handleResponse">
立即响应
</van-button>
</div>
</div>
</template>
<script setup>
import { ref, onMounted } from "vue";
import { useRoute, useRouter } from "vue-router";
import { showToast } from "vant";
const route = useRoute();
const router = useRouter();
//
const currentSite = ref("李家坝仓库");
//
const warningDetail = ref({
id: 1,
title: "合川区暴雨红色预警",
publishTime: "2025/10/10 20:29",
description:
"区气象台发布暴雨红色预警按照相关要求启动I级防御响应并请及时关注地质、水文等风险提示信息落实主动封闭管控/“关停撤转”措施",
measures:
"请立即按照2小时一次频率对你管养的重点路段/重点部位进行巡查,重点巡查较高及以上风险路段、涉灾隐患点、地质条件复杂路段、临河临崖路段/两区三厂、大型设施设备、取弃土(渣)场、砂石料场、涉水桥梁、富水隧道、围堰、支架脚手架、高切坡、滑坡处置等部位,重点关注涉水桥梁基础及墩台、不良地质隧道、隧道洞口边仰坡及侧切结构、高陡边坡支挡防护以及防排水设施,发现异常情况,立即向上报告,采取紧急排危、告警阻拦、吹哨撤转等措施,并及时报送工作开展情况。",
level: "red",
type: "rain",
});
//
const goBack = () => {
router.back();
};
//
const handleResponse = () => {
showToast({
message: "响应成功",
type: "success",
});
// API
console.log("立即响应", warningDetail.value);
};
onMounted(() => {
// ID
const { id } = route.params;
if (id) {
console.log("获取预警详情 ID:", id);
// API
// fetchWarningDetail(id);
}
//
const { data } = route.params;
if (data) {
try {
const yhzinfo = JSON.parse(decodeURIComponent(data));
currentSite.value = yhzinfo.mc || "李家坝仓库";
} catch (e) {
console.error("解析站点信息失败", e);
}
}
});
</script>
<style lang="scss" scoped>
//
$primary-color: #1989fa;
$text-color: #333;
$text-secondary: #666;
$text-tertiary: #999;
$bg-color: #f5f5f5;
$border-color: #e0e0e0;
$card-bg: #fff;
$section-title-color: #666;
.warning-detail-page {
min-height: 100vh;
background-color: $bg-color;
padding-bottom: 80px;
//
.nav-bar {
display: flex;
align-items: center;
justify-content: space-between;
height: 44px;
background-color: #fff;
padding: 0 15px;
position: sticky;
top: 0;
z-index: 100;
.back-btn {
width: 40px;
height: 40px;
display: flex;
align-items: center;
justify-content: flex-start;
color: $text-color;
font-size: 20px;
}
.title {
flex: 1;
text-align: center;
font-size: 17px;
font-weight: 500;
color: $text-color;
}
.placeholder {
width: 40px;
}
}
//
.current-site {
padding: 10px 15px;
background-color: #fff;
text-align: center;
font-size: 13px;
border-bottom: 1px solid $border-color;
.site-label {
color: $text-secondary;
}
.site-name {
color: $primary-color;
}
}
//
.detail-content {
padding: 15px;
//
.warning-header {
margin-bottom: 12px;
.header-title {
font-size: 16px;
font-weight: 600;
color: $text-color;
position: relative;
padding-left: 12px;
&::before {
content: "";
position: absolute;
left: 0;
top: 50%;
transform: translateY(-50%);
width: 4px;
height: 16px;
background-color: $primary-color;
border-radius: 2px;
}
}
}
//
.info-card {
background-color: $card-bg;
border-radius: 8px;
padding: 15px;
margin-bottom: 12px;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.05);
.warning-name {
font-size: 16px;
font-weight: 600;
color: $text-color;
margin-bottom: 10px;
line-height: 1.4;
}
.publish-time {
font-size: 13px;
color: $text-tertiary;
.time-label {
color: $text-secondary;
}
}
}
//
.description-card {
background-color: $card-bg;
border-radius: 8px;
padding: 15px;
margin-bottom: 12px;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.05);
.desc-text {
font-size: 14px;
color: $text-secondary;
line-height: 1.8;
text-align: justify;
}
}
//
.measures-section {
.section-title {
font-size: 16px;
font-weight: 600;
color: $text-color;
margin-bottom: 12px;
position: relative;
padding-left: 12px;
&::before {
content: "";
position: absolute;
left: 0;
top: 50%;
transform: translateY(-50%);
width: 4px;
height: 16px;
background-color: $primary-color;
border-radius: 2px;
}
}
.measures-card {
background-color: $card-bg;
border-radius: 8px;
padding: 15px;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.05);
.measures-text {
font-size: 14px;
color: $text-secondary;
line-height: 1.8;
text-align: justify;
}
}
}
}
//
.bottom-action {
position: fixed;
left: 0;
right: 0;
bottom: 0;
padding: 10px 15px 20px;
background-color: #fff;
border-top: 1px solid $border-color;
.van-button {
height: 44px;
font-size: 16px;
}
}
}
// iPhone
@supports (padding-top: env(safe-area-inset-top)) {
.warning-detail-page {
.nav-bar {
padding-top: env(safe-area-inset-top);
height: calc(44px + env(safe-area-inset-top));
}
.bottom-action {
padding-bottom: calc(20px + env(safe-area-inset-bottom));
}
}
}
//
@media screen and (max-width: 320px) {
.warning-detail-page {
.detail-content {
.info-card {
.warning-name {
font-size: 15px;
}
}
.description-card,
.measures-section {
.desc-text,
.measures-text {
font-size: 13px;
}
}
}
}
}
@media screen and (min-width: 414px) {
.warning-detail-page {
.nav-bar {
height: 48px;
.title {
font-size: 18px;
}
}
.detail-content {
.warning-header {
.header-title {
font-size: 17px;
}
}
.info-card {
padding: 18px;
.warning-name {
font-size: 17px;
}
.publish-time {
font-size: 14px;
}
}
.description-card,
.measures-card {
padding: 18px;
.desc-text,
.measures-text {
font-size: 15px;
}
}
.measures-section {
.section-title {
font-size: 17px;
}
}
}
}
}
</style>

Binary file not shown.

After

Width:  |  Height:  |  Size: 655 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 112 KiB

View File

@ -61,6 +61,30 @@ const routes = [
}, },
component: () => import('../views/RiskWarning/index.vue') component: () => import('../views/RiskWarning/index.vue')
}, },
{
path: '/regulationsDivision',
name: 'RegulationsDivision',
meta: {
screen: true
},
component: () => import('../views/RegulationsDivision/RegulationsDivision.vue')
},
{
path: '/warningCounty',
name: 'WarningCounty',
meta: {
screen: true
},
component: () => import('../views/warningCounty/warningCounty.vue')
},
{
path: '/constructionDepartment',
name: 'ConstructionDepartment',
meta: {
screen: true
},
component: () => import('../views/ConstructionDepartment/ConstructionDepartment.vue')
},
] ]
const router = createRouter({ const router = createRouter({

View File

@ -0,0 +1,548 @@
<template>
<div class="construction-department-page">
<!-- 左侧树形菜单 -->
<div class="left-sidebar">
<el-tree
:data="treeData"
:props="defaultProps"
node-key="id"
default-expand-all
highlight-current
@node-click="handleNodeClick"
class="department-tree"
>
<template #default="{ node, data }">
<span class="tree-node">
<el-icon v-if="data.children && data.children.length" class="tree-icon">
<FolderOpened v-if="node.expanded" />
<Folder v-else />
</el-icon>
<el-icon v-else class="tree-icon"><Document /></el-icon>
<span class="tree-label">{{ node.label }}</span>
</span>
</template>
</el-tree>
</div>
<!-- 右侧内容区域 -->
<div class="right-content">
<!-- 搜索筛选区域 -->
<div class="search-area">
<div class="search-row">
<div class="search-item">
<span class="search-label">项目名称</span>
<el-input v-model="searchForm.projectName" placeholder="" clearable style="width: 140px" />
</div>
<div class="search-item">
<span class="search-label">驻地名称</span>
<el-input v-model="searchForm.stationName" placeholder="" clearable style="width: 140px" />
</div>
<div class="search-item">
<span class="search-label">建设单位</span>
<el-input v-model="searchForm.constructionUnit" placeholder="" clearable style="width: 140px" />
</div>
<div class="search-item">
<span class="search-label">施工单位</span>
<el-input v-model="searchForm.builderUnit" placeholder="" clearable style="width: 140px" />
</div>
<div class="search-item">
<span class="search-label">项目类型</span>
<el-select v-model="searchForm.projectType" placeholder="全部" clearable style="width: 100px">
<el-option label="全部" value="" />
<el-option label="新建" value="new" />
<el-option label="改扩建" value="rebuild" />
</el-select>
</div>
</div>
<div class="search-row">
<div class="search-item">
<span class="search-label">驻地类型</span>
<el-select v-model="searchForm.stationType" placeholder="全部" clearable style="width: 100px">
<el-option label="全部" value="" />
<el-option label="类型1" value="type1" />
<el-option label="类型2" value="type2" />
</el-select>
</div>
<div class="search-item">
<span class="search-label">驻地风险等级</span>
<el-select v-model="searchForm.riskLevel" placeholder="全部" clearable style="width: 100px">
<el-option label="全部" value="" />
<el-option label="高" value="high" />
<el-option label="中" value="medium" />
<el-option label="低" value="low" />
</el-select>
</div>
<div class="search-item">
<span class="search-label">所属区县</span>
<el-select v-model="searchForm.district" placeholder="全部" clearable style="width: 100px">
<el-option label="全部" value="" />
<el-option label="万州区" value="wanzhou" />
<el-option label="沙坪坝区" value="shapingba" />
<el-option label="涪陵区" value="fuling" />
</el-select>
</div>
<div class="search-item">
<span class="search-label">吹哨人姓名</span>
<el-input v-model="searchForm.whistleblowerName" placeholder="" clearable style="width: 100px" />
</div>
<div class="search-item">
<span class="search-label">建设单位责任人姓名</span>
<el-input v-model="searchForm.constructorResponsible" placeholder="" clearable style="width: 100px" />
</div>
</div>
<div class="search-row">
<div class="search-item">
<span class="search-label">区县级责任人姓名</span>
<el-input v-model="searchForm.countyResponsible" placeholder="" clearable style="width: 100px" />
</div>
<div class="search-item">
<span class="search-label">市级责任人姓名</span>
<el-input v-model="searchForm.cityResponsible" placeholder="" clearable style="width: 100px" />
</div>
<div class="search-item">
<span class="search-label">施工单位责任人姓名</span>
<el-input v-model="searchForm.builderResponsible" placeholder="" clearable style="width: 100px" />
</div>
<div class="search-item">
<span class="search-label">驻地责任人姓名</span>
<el-input v-model="searchForm.stationResponsible" placeholder="" clearable style="width: 100px" />
</div>
<div class="search-item search-buttons">
<el-button type="primary" @click="handleSearch">查询</el-button>
<!-- <el-button link type="primary" @click="handleReset">收起</el-button> -->
</div>
</div>
</div>
<!-- 操作按钮 -->
<div class="action-area">
<el-button type="primary" class="export-btn" @click="handleExport">
<el-icon><Download /></el-icon>
导出
</el-button>
<el-button type="primary" class="import-btn" @click="handleImport">
<el-icon><Upload /></el-icon>
导入
</el-button>
</div>
<!-- 数据表格 -->
<div class="table-area">
<el-table
:data="filteredTableData"
stripe
style="width: 100%"
:header-cell-style="headerCellStyle"
:row-class-name="rowClassName"
>
<el-table-column type="index" label="序号" width="60" align="center" />
<el-table-column prop="district" label="所属区县" width="100" align="center" />
<el-table-column prop="projectName" label="项目名称" min-width="200" show-overflow-tooltip />
<el-table-column prop="stationName" label="驻地名称" min-width="180" show-overflow-tooltip />
<el-table-column prop="stationType" label="驻地类型" width="100" align="center" />
<el-table-column prop="coordinate" label="坐标点位" width="150" align="center" show-overflow-tooltip />
<el-table-column prop="parentProject" label="所属项目名称" min-width="180" show-overflow-tooltip />
<el-table-column prop="projectType" label="项目类型" width="120" align="center" />
<el-table-column prop="constructionUnit" label="建设单位" min-width="150" show-overflow-tooltip />
<el-table-column prop="builderUnit" label="施工单位" min-width="150" show-overflow-tooltip />
<el-table-column prop="stationAddress" label="驻地地址" min-width="200" show-overflow-tooltip />
<el-table-column prop="adminArea" label="行政区域" width="100" align="center" />
<el-table-column prop="stationPeople" label="驻地人数" width="90" align="center" />
<el-table-column prop="riskLevel" label="驻地风险等级" width="110" align="center" />
<el-table-column prop="buildingType" label="房建类型" width="100" align="center" />
<el-table-column prop="relocationStatus" label="搬迁状态" width="90" align="center" />
<el-table-column label="操作" width="120" align="center" fixed="right">
<template #default="{ row }">
<el-button link type="primary" size="small" @click="handleEdit(row)">编辑</el-button>
<el-button link type="danger" size="small" @click="handleDelete(row)">删除</el-button>
</template>
</el-table-column>
</el-table>
</div>
<!-- 分页 -->
<div class="pagination-area">
<el-pagination
v-model:current-page="currentPage"
v-model:page-size="pageSize"
:total="total"
:page-sizes="[10, 20, 50, 100]"
layout="prev, pager, next, jumper"
prev-text="上一个"
next-text="下一个"
@current-change="handleCurrentChange"
@size-change="handleSizeChange"
/>
</div>
</div>
</div>
</template>
<script setup>
import { ref, reactive, computed } from "vue";
import { ElMessage, ElMessageBox } from "element-plus";
import { Folder, FolderOpened, Document, Download, Upload } from "@element-plus/icons-vue";
//
const treeData = ref([
{
id: 1,
label: "2026",
children: [
{ id: 11, label: "2026.3" },
{ id: 12, label: "2026.2" },
{ id: 13, label: "2026.1" },
],
},
{
id: 2,
label: "2025",
children: [
{ id: 21, label: "2025.12" },
{ id: 22, label: "2025.11" },
{ id: 23, label: "2025.10" },
],
},
]);
const defaultProps = {
children: "children",
label: "label",
};
//
const searchForm = reactive({
projectName: "",
stationName: "",
constructionUnit: "",
builderUnit: "",
projectType: "",
stationType: "",
riskLevel: "",
district: "",
whistleblowerName: "",
constructorResponsible: "",
countyResponsible: "",
cityResponsible: "",
builderResponsible: "",
stationResponsible: "",
});
//
const tableData = ref([
{
district: "沙坪坝区",
projectName: "沙坪坝区S545茅山峡公路桥新建工程渝黔铁路扩能改造工程",
stationName: "沙坪坝区S545茅山峡公路桥新建工程渝黔铁路扩能改造工程项目经理部",
stationType: "项目部驻地",
coordinate: "经度106.44E纬度29.62N",
parentProject: "中铁十七局集团第五工程有限公司",
projectType: "普通国省干线(新建/改扩建)",
constructionUnit: "成渝铁路客运专线有限公司",
builderUnit: "中铁十七局集团",
stationAddress: "沙坪坝区井口街道南溪村茅山峡公路跨线桥处",
adminArea: "沙坪坝区",
stationPeople: "16",
riskLevel: "高",
buildingType: "板房",
relocationStatus: "否",
},
{
district: "沙坪坝区",
projectName: "沙坪坝区S545茅山峡公路桥新建工程渝黔铁路扩能改造工程",
stationName: "沙坪坝区S545茅山峡公路桥新建工程渝黔铁路扩能改造工程项目经理部",
stationType: "项目部驻地",
coordinate: "经度106.44E纬度29.62N",
parentProject: "中铁十七局集团第五工程有限公司",
projectType: "普通国省干线(新建/改扩建)",
constructionUnit: "成渝铁路客运专线有限公司",
builderUnit: "中铁十七局集团",
stationAddress: "沙坪坝区井口街道南溪村茅山峡公路跨线桥处",
adminArea: "沙坪坝区",
stationPeople: "16",
riskLevel: "高",
buildingType: "板房",
relocationStatus: "否",
},
]);
//
const filteredTableData = computed(() => {
return tableData.value.filter((item) => {
const matchProjectName = !searchForm.projectName || item.projectName.includes(searchForm.projectName);
const matchStationName = !searchForm.stationName || item.stationName.includes(searchForm.stationName);
const matchConstructionUnit = !searchForm.constructionUnit || item.constructionUnit.includes(searchForm.constructionUnit);
const matchBuilderUnit = !searchForm.builderUnit || item.builderUnit.includes(searchForm.builderUnit);
const matchDistrict = !searchForm.district || item.district.includes(searchForm.district === 'wanzhou' ? '万州' : searchForm.district === 'shapingba' ? '沙坪坝' : '涪陵');
return matchProjectName && matchStationName && matchConstructionUnit && matchBuilderUnit && matchDistrict;
});
});
//
const currentPage = ref(1);
const pageSize = ref(10);
const total = ref(2);
//
const headerCellStyle = () => ({
background: "#e6f0ff",
color: "#303133",
fontWeight: "bold",
fontSize: "13px",
padding: "10px 0",
});
//
const rowClassName = ({ rowIndex }) => {
return rowIndex % 2 === 0 ? "even-row" : "odd-row";
};
//
const handleNodeClick = (data) => {
console.log("点击节点:", data);
};
//
const handleSearch = () => {
currentPage.value = 1;
ElMessage.success("查询成功");
};
//
const handleReset = () => {
Object.keys(searchForm).forEach((key) => {
searchForm[key] = "";
});
currentPage.value = 1;
ElMessage.success("已重置");
};
//
const handleExport = () => {
ElMessage.success("导出成功");
};
//
const handleImport = () => {
ElMessage.info("请选择要导入的文件");
};
//
const handleEdit = (row) => {
ElMessage.info(`编辑 ${row.projectName}`);
};
//
const handleDelete = (row) => {
ElMessageBox.confirm(`确定删除 ${row.projectName} 吗?`, "提示", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning",
})
.then(() => {
ElMessage.success("删除成功");
})
.catch(() => {});
};
//
const handleCurrentChange = (val) => {
currentPage.value = val;
};
const handleSizeChange = (val) => {
pageSize.value = val;
};
</script>
<style lang="scss" scoped>
.construction-department-page {
display: flex;
height: 100vh;
background: #f5f7fa;
//
.left-sidebar {
width: 200px;
background: #fff;
border-right: 1px solid #e4e7ed;
padding: 15px;
overflow-y: auto;
.department-tree {
.tree-node {
display: flex;
align-items: center;
font-size: 14px;
.tree-icon {
margin-right: 8px;
font-size: 16px;
color: #909399;
}
.tree-label {
color: #606266;
}
}
:deep(.el-tree-node__content) {
height: 32px;
padding-left: 8px !important;
}
:deep(.el-tree-node__children) {
.el-tree-node__content {
padding-left: 24px !important;
}
}
}
}
//
.right-content {
flex: 1;
padding: 20px;
overflow-y: auto;
//
.search-area {
background: #fff;
padding: 15px 20px;
border-radius: 4px;
margin-bottom: 15px;
.search-row {
display: flex;
gap: 15px;
margin-bottom: 12px;
flex-wrap: wrap;
&:last-child {
margin-bottom: 0;
}
.search-item {
display: flex;
align-items: center;
gap: 8px;
.search-label {
font-size: 13px;
color: #606266;
white-space: nowrap;
}
}
.search-buttons {
margin-left: auto;
}
}
}
//
.action-area {
display: flex;
gap: 10px;
margin-bottom: 15px;
.export-btn,
.import-btn {
display: flex;
align-items: center;
gap: 5px;
}
}
//
.table-area {
background: #fff;
padding: 15px;
border-radius: 4px;
margin-bottom: 15px;
:deep(.el-table) {
border: 1px solid #ebeef5;
border-radius: 4px;
.el-table__header-wrapper {
th {
background: #e6f0ff !important;
}
}
.even-row {
background: #f5f9ff;
}
.odd-row {
background: #fff;
}
}
}
//
.pagination-area {
display: flex;
justify-content: flex-end;
background: #fff;
padding: 15px;
border-radius: 4px;
:deep(.el-pagination) {
.btn-prev,
.btn-next {
padding: 0 15px;
border: 1px solid #dcdfe6;
border-radius: 4px;
background: #fff;
color: #606266;
&:hover {
color: #409eff;
border-color: #409eff;
}
&.disabled {
color: #c0c4cc;
border-color: #e4e7ed;
}
}
.el-pager {
li {
min-width: 32px;
height: 32px;
line-height: 32px;
border: 1px solid #dcdfe6;
border-radius: 4px;
margin: 0 4px;
background: #fff;
color: #606266;
&.active {
background: #409eff;
border-color: #409eff;
color: #fff;
}
&:hover {
color: #409eff;
border-color: #409eff;
}
&.active:hover {
color: #fff;
}
}
}
.el-pagination__jump {
margin-left: 15px;
color: #606266;
}
}
}
}
}
</style>

File diff suppressed because it is too large Load Diff

View File

@ -1,5 +1,5 @@
<template> <template>
<div class="main-box display p_10" style="box-sizing: border-box"> <div class="bottom-panel">
<div class="nav-menu"> <div class="nav-menu">
<div <div
v-for="(item, index) in menuItems" v-for="(item, index) in menuItems"
@ -8,7 +8,7 @@
:class="{ active: activeIndex === index }" :class="{ active: activeIndex === index }"
@click="activeIndex = index" @click="activeIndex = index"
> >
<div class="nav-icon"> <div class="nav-icon-box">
<!-- <i :class="item.icon"></i> --> <!-- <i :class="item.icon"></i> -->
<img :src="activeIndex === index ? item.icon1 : item.icon" alt="" /> <img :src="activeIndex === index ? item.icon1 : item.icon" alt="" />
</div> </div>
@ -86,7 +86,7 @@
</template> </template>
<script setup> <script setup>
import { ref, computed } from "vue"; import { ref, computed, onMounted, onUnmounted, watch, nextTick } from "vue";
import { ElTable } from "element-plus"; import { ElTable } from "element-plus";
import orangeIcon from "../../assets/RiskWarning_img/橙色@2x.png"; import orangeIcon from "../../assets/RiskWarning_img/橙色@2x.png";
import yellowIcon from "../../assets/RiskWarning_img/黄色@2x.png"; import yellowIcon from "../../assets/RiskWarning_img/黄色@2x.png";
@ -216,25 +216,145 @@ const cellStyle = () => ({
background: "transparent", background: "transparent",
color: "rgba(255, 255, 255, 0.9)", color: "rgba(255, 255, 255, 0.9)",
borderBottom: "1px solid rgba(64, 169, 255, 0.1)", borderBottom: "1px solid rgba(64, 169, 255, 0.1)",
padding: "12px 8px", padding: "5px 5px",
}); });
const rowClassName = ({ rowIndex }) => { const rowClassName = ({ rowIndex }) => {
return rowIndex % 2 === 0 ? "even-row" : "odd-row"; return rowIndex % 2 === 0 ? "even-row" : "odd-row";
}; };
//
const tableRef = ref(null);
const isScrolling = ref(true);
const scrollTimer = ref(null);
const scrollSpeed = 50; //
const scrollStep = 1; //
//
const hasFilter = computed(() => {
return filters.value.red || filters.value.blue || filters.value.orange || filters.value.yellow;
});
//
const startAutoScroll = () => {
if (scrollTimer.value) {
clearInterval(scrollTimer.value);
}
if (!isScrolling.value || hasFilter.value) return;
const tableBody = document.querySelector('.weather-warning-panel .el-table__body-wrapper .el-scrollbar__wrap');
if (!tableBody) return;
scrollTimer.value = setInterval(() => {
if (tableBody) {
tableBody.scrollTop += scrollStep;
// 使 >=
const maxScroll = tableBody.scrollHeight - tableBody.clientHeight;
if (tableBody.scrollTop >= maxScroll - 2) {
tableBody.scrollTop = 0;
}
}
}, scrollSpeed);
};
//
const stopAutoScroll = () => {
if (scrollTimer.value) {
clearInterval(scrollTimer.value);
scrollTimer.value = null;
}
};
//
const resetScrollToTop = () => {
const tableBody = document.querySelector('.weather-warning-panel .el-table__body-wrapper .el-scrollbar__wrap');
if (tableBody) {
tableBody.scrollTop = 0;
}
};
//
const handleMouseEnter = () => {
stopAutoScroll();
};
//
const handleMouseLeave = () => {
if (!hasFilter.value) {
isScrolling.value = true;
startAutoScroll();
}
};
//
watch(hasFilter, (newVal) => {
if (newVal) {
//
stopAutoScroll();
resetScrollToTop();
} else {
//
isScrolling.value = true;
nextTick(() => {
startAutoScroll();
});
}
});
onMounted(() => {
nextTick(() => {
startAutoScroll();
//
const tableContainer = document.querySelector('.weather-warning-panel .table-container');
if (tableContainer) {
tableContainer.addEventListener('mouseenter', handleMouseEnter);
tableContainer.addEventListener('mouseleave', handleMouseLeave);
}
});
});
onUnmounted(() => {
stopAutoScroll();
const tableContainer = document.querySelector('.weather-warning-panel .table-container');
if (tableContainer) {
tableContainer.removeEventListener('mouseenter', handleMouseEnter);
tableContainer.removeEventListener('mouseleave', handleMouseLeave);
}
});
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.main-box { // -
// 1920px使 vw
@function vw($px) {
@return calc($px / 1920 * 100vw);
}
.bottom-panel {
position: relative; position: relative;
width: 100%;
height: 100%;
padding: vw(10);
box-sizing: border-box;
display: flex;
//
@media (max-width: 1366px) {
padding: 8px;
}
@media (max-width: 1024px) {
padding: 6px;
}
} }
.nav-menu { .nav-menu {
width: 50px; width: vw(50);
min-width: 40px;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
justify-content: end; justify-content: end;
align-items: center; align-items: center;
gap: 5px; gap: vw(5);
.nav-item { .nav-item {
display: flex; display: flex;
@ -245,7 +365,7 @@ const rowClassName = ({ rowIndex }) => {
&:hover, &:hover,
&.active { &.active {
.nav-icon { .nav-icon-box {
background: rgba(64, 169, 255, 0.3); background: rgba(64, 169, 255, 0.3);
border-color: rgba(64, 169, 255, 0.6); border-color: rgba(64, 169, 255, 0.6);
} }
@ -255,10 +375,12 @@ const rowClassName = ({ rowIndex }) => {
} }
} }
.nav-icon { .nav-icon-box {
width: 50px; width: vw(50);
height: 50px; height: vw(50);
margin-bottom: 5px; min-width: 36px;
min-height: 36px;
margin-bottom: vw(5);
background: rgba(64, 169, 255, 0.1); background: rgba(64, 169, 255, 0.1);
border: 1px solid rgba(64, 169, 255, 0.3); border: 1px solid rgba(64, 169, 255, 0.3);
border-radius: 8px; border-radius: 8px;
@ -267,39 +389,41 @@ const rowClassName = ({ rowIndex }) => {
justify-content: center; justify-content: center;
transition: all 0.3s ease; transition: all 0.3s ease;
img { img {
width: 50px; width: vw(50);
width: 50px; height: vw(50);
min-width: 32px;
min-height: 32px;
} }
i { i {
font-size: 24px; font-size: vw(24);
color: #40a9ff; color: #40a9ff;
} }
// 使 emoji // 使 emoji
&.warning::before { &.warning::before {
content: "📍"; content: "📍";
font-size: 24px; font-size: vw(24);
} }
&.tunnel::before { &.tunnel::before {
content: "🚇"; content: "🚇";
font-size: 24px; font-size: vw(24);
} }
&.slope::before { &.slope::before {
content: "⛰️"; content: "⛰️";
font-size: 24px; font-size: vw(24);
} }
&.bridge::before { &.bridge::before {
content: "🌉"; content: "🌉";
font-size: 24px; font-size: vw(24);
} }
&.road::before { &.road::before {
content: "🛣️"; content: "🛣️";
font-size: 24px; font-size: vw(24);
} }
} }
@ -307,7 +431,7 @@ const rowClassName = ({ rowIndex }) => {
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
font-size: 12px; font-size: vw(12);
color: #fff; color: #fff;
transition: all 0.3s ease; transition: all 0.3s ease;
} }
@ -325,38 +449,36 @@ const rowClassName = ({ rowIndex }) => {
// //
.weather-warning-panel { .weather-warning-panel {
height: 60%; height: 50%;
background: linear-gradient( background: rgba(64, 169, 255, 0.1);
180deg,
rgba(21, 41, 59, 0.95) 0%,
rgba(13, 28, 42, 0.95) 100%
);
border: 1px solid rgba(64, 169, 255, 0.3); border: 1px solid rgba(64, 169, 255, 0.3);
border-radius: 8px; border-radius: 8px;
padding: 15px; padding: vw(15);
display: flex; display: flex;
flex-direction: column; flex-direction: column;
position: relative; position: relative;
.clear-icon { .clear-icon {
position: absolute; position: absolute;
top: -50px; top: vw(-50);
right: 0px; right: 0px;
width: 40px !important; width: vw(40) !important;
height: 40px !important; height: vw(40) !important;
min-width: 28px;
min-height: 28px;
} }
.panel-header { .panel-header {
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;
margin-bottom: 15px; margin-bottom: vw(15);
.header-title { .header-title {
font-size: 18px; font-size: vw(18);
font-weight: bold; font-weight: bold;
color: #40a9ff; color: #40a9ff;
position: relative; position: relative;
padding-left: 12px; padding-left: vw(12);
&::before { &::before {
content: ""; content: "";
@ -364,8 +486,8 @@ const rowClassName = ({ rowIndex }) => {
left: 0; left: 0;
top: 50%; top: 50%;
transform: translateY(-50%); transform: translateY(-50%);
width: 4px; width: vw(4);
height: 16px; height: vw(16);
background: linear-gradient(180deg, #40a9ff 0%, #1890ff 100%); background: linear-gradient(180deg, #40a9ff 0%, #1890ff 100%);
border-radius: 2px; border-radius: 2px;
} }
@ -373,22 +495,24 @@ const rowClassName = ({ rowIndex }) => {
.filter-tags { .filter-tags {
display: flex; display: flex;
gap: 15px; gap: vw(15);
.tag { .tag {
display: flex; display: flex;
align-items: center; align-items: center;
gap: 5px; gap: vw(5);
cursor: pointer; cursor: pointer;
input { input {
width: 14px; width: vw(14);
height: 14px; height: vw(14);
min-width: 12px;
min-height: 12px;
accent-color: #40a9ff; accent-color: #40a9ff;
} }
span { span {
font-size: 12px; font-size: vw(12);
color: rgba(255, 255, 255, 0.8); color: rgba(255, 255, 255, 0.8);
&.tag-red { &.tag-red {
@ -478,8 +602,10 @@ const rowClassName = ({ rowIndex }) => {
align-items: center; align-items: center;
justify-content: center; justify-content: center;
img { img {
width: 15px; width: vw(15);
height: 15px; height: vw(15);
min-width: 10px;
min-height: 10px;
} }
// .level-icon { // .level-icon {

View File

@ -0,0 +1,370 @@
<template>
<div v-if="visible" class="ai-dialog-overlay" @click="handleOverlayClick">
<div class="ai-dialog" @click.stop>
<!-- 标题栏 -->
<div class="dialog-header">
<div class="header-title">AI预警处理结果</div>
<div class="close-btn" @click="handleClose">
<el-icon><Close /></el-icon>
</div>
</div>
<!-- 内容区域 -->
<div class="content-wrapper">
<!-- AI处理前预警 -->
<div class="panel before-panel" style="background: linear-gradient(135deg, rgba(30, 80, 140, 0.6) 0%, rgba(20, 60, 110, 0.8) 100%);">
<div class="panel-title">AI处理前预警</div>
<div class="panel-content">
<div class="info-item">
<span class="info-label">发布单位&#xff1a;</span>
<span class="info-value">{{ beforeData.publishOrg }}</span>
</div>
<div class="info-item">
<span class="info-label">发布内容&#xff1a;</span>
<span class="info-value content-text">{{ beforeData.publishContent }}</span>
</div>
<div class="info-item">
<span class="info-label">生效时间&#xff1a;</span>
<span class="info-value">{{ beforeData.effectiveTime }}</span>
</div>
<div class="info-item">
<span class="info-label">失效时间&#xff1a;</span>
<span class="info-value">{{ beforeData.expireTime }}</span>
</div>
</div>
</div>
<!-- 中间AI标识 -->
<div class="ai-center">
<img class="ai-icon-img" src="../../../assets/RiskWarning_img/AI1@2x.png" alt="AI" />
<div class="ai-arrow">
<el-icon><DArrowRight /></el-icon>
</div>
</div>
<!-- AI处理后预警 -->
<div class="panel after-panel panel1" style="background: #2699FF">
<div class="panel-title" style="color:#fff">AI处理后预警</div>
<div class="panel-content">
<!-- 标签页 -->
<div class="tab-header">
<div
v-for="tab in tabs"
:key="tab.key"
class="tab-item"
:class="{ active: activeTab === tab.key }"
@click="activeTab = tab.key"
>
{{ tab.label }}
</div>
</div>
<!-- 标签内容 -->
<div class="tab-content">
<div class="info-item">
<span class="info-label">预警等级&#xff1a;</span>
<span class="info-value">{{ afterData.warningLevel }}</span>
</div>
<div class="info-item">
<span class="info-label">预警信息&#xff1a;</span>
<span class="info-value content-text">{{ afterData.warningInfo }}</span>
</div>
<div class="info-item">
<span class="info-label">预警详情&#xff1a;</span>
<span class="info-value content-text">{{ afterData.warningDetail }}</span>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<script setup>
import { ref, watch } from "vue";
import { Close, DArrowRight } from "@element-plus/icons-vue";
const props = defineProps({
visible: {
type: Boolean,
default: false,
},
aiData: {
type: Object,
default: () => ({}),
},
});
const emit = defineEmits(["update:visible", "close"]);
//
const tabs = ref([
{ key: "frontline", label: "一线人员" },
{ key: "district", label: "区县人员" },
{ key: "handler", label: "处室人员" },
{ key: "leader", label: "中心领导" },
]);
const activeTab = ref("frontline");
// AI
const beforeData = ref({
publishOrg: "潼南区预警中心",
publishContent: '潼南区气象台2025年11月13日0时40分发布"暴雨蓝色预警信号"预计23日0:40-6:40龙形、宝龙、上和、大佛、桂林、玉溪、米心、花岩、双江、古溪、群力、柏梓、崇龛、梓潼、太安等15个乡镇街道强降水趋于减弱未来6小时累计雨量将达50100毫米最大小时雨强将达2040毫米局地伴有雷电、阵性大风请各地注意防范。',
effectiveTime: "2025-11-13 00:31:30.0",
expireTime: "2025-11-13 00:31:30.0",
});
// AI
const afterData = ref({
warningLevel: "重庆市潼南气象台发布大雾黄色预警[III级/较重]",
warningInfo: '潼南区区气象台发布暴雨红色预警按照相关要求启动I级防御响应并请及时关注地质、水文等风险提示信息落实主动封闭管控/"关停撒转"措施',
warningDetail: "请立即按照2小时一次频率对你管养的重点路段/重点部位进行巡查,重点巡查较高及以上风险路段、涉灾隐患点、地质条件复杂路段、临河临崖路段/两区三厂、大型设施设备、取弃土(渣)场、砂石料场、涉水桥梁、富水隧道、围堰、支架脚手架、高切坡、滑坡处置等部位,重点关注涉水桥梁基础及墩台、不良地质隧道、隧道洞口边仰坡及侧切结构、高陡边坡支挡防护以及防排水设施,发现异常情况,立即向上报告,采取紧急排危、告警阻拦、吹哨撒转等措施,并及时报送工作开展情况。",
});
//
const handleClose = () => {
emit("update:visible", false);
emit("close");
};
//
const handleOverlayClick = () => {
handleClose();
};
// visible
watch(
() => props.visible,
(newVal) => {
if (newVal && props.aiData) {
Object.assign(beforeData.value, props.aiData.before);
Object.assign(afterData.value, props.aiData.after);
}
}
);
</script>
<style lang="scss" scoped>
.ai-dialog-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(0, 0, 0, 0.6);
display: flex;
align-items: center;
justify-content: center;
z-index: 1000;
}
.ai-dialog {
width: 1000px;
background: linear-gradient(135deg, rgba(20, 50, 90, 0.95) 0%, rgba(10, 30, 60, 0.98) 100%);
border: 1px solid rgba(64, 169, 255, 0.3);
border-radius: 12px;
padding: 24px;
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.4);
}
//
.dialog-header {
display: flex;
align-items: center;
justify-content: center;
position: relative;
margin-bottom: 24px;
.header-title {
font-size: 20px;
font-weight: 600;
color: #fff;
padding: 8px 40px;
background: linear-gradient(90deg, transparent 0%, rgba(64, 169, 255, 0.2) 20%, rgba(64, 169, 255, 0.2) 80%, transparent 100%);
border-bottom: 2px solid #40a9ff;
}
.close-btn {
position: absolute;
right: 0;
top: 0;
width: 32px;
height: 32px;
display: flex;
align-items: center;
justify-content: center;
color: rgba(255, 255, 255, 0.7);
cursor: pointer;
font-size: 20px;
transition: color 0.3s;
&:hover {
color: #fff;
}
}
}
//
.content-wrapper {
display: flex;
gap: 20px;
align-items: stretch;
}
//
.panel {
flex: 1;
border: 1px solid rgba(64, 169, 255, 0.3);
border-radius: 12px;
padding: 20px;
position: relative;
&::before {
content: '';
position: absolute;
top: -1px;
left: -1px;
right: -1px;
bottom: -1px;
border-radius: 12px;
padding: 1px;
background: linear-gradient(135deg, rgba(64, 169, 255, 0.5) 0%, transparent 50%, rgba(64, 169, 255, 0.5) 100%);
-webkit-mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0);
mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0);
-webkit-mask-composite: xor;
mask-composite: exclude;
pointer-events: none;
}
.panel-title {
font-size: 16px;
font-weight: 600;
color: #40a9ff;
text-align: center;
margin-bottom: 16px;
padding-bottom: 12px;
border-bottom: 1px solid rgba(64, 169, 255, 0.2);
}
.panel-content {
.info-item {
margin-bottom: 12px;
&:last-child {
margin-bottom: 0;
}
}
.info-label {
font-size: 13px;
color: rgba(255, 255, 255, 0.6);
margin-right: 8px;
}
.info-value {
font-size: 13px;
color: rgba(255, 255, 255, 0.9);
line-height: 1.6;
&.content-text {
display: block;
margin-top: 6px;
text-align: justify;
}
}
}
}
// AI
.ai-center {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 16px;
padding: 0 10px;
.ai-icon-img {
width: 60px;
height: 60px;
}
.ai-icon {
width: 60px;
height: 60px;
border-radius: 50%;
background: linear-gradient(135deg, #40a9ff 0%, #1890ff 100%);
display: flex;
align-items: center;
justify-content: center;
font-size: 20px;
font-weight: 700;
color: #fff;
box-shadow: 0 4px 20px rgba(64, 169, 255, 0.4);
}
.ai-arrow {
font-size: 32px;
color: #40a9ff;
animation: pulse 1.5s infinite;
:deep(.el-icon) {
font-size: 32px;
}
}
}
@keyframes pulse {
0%, 100% {
opacity: 1;
transform: translateX(0);
}
50% {
opacity: 0.6;
transform: translateX(5px);
}
}
//
.tab-header {
display: flex;
gap: 8px;
margin-bottom: 16px;
padding-bottom: 12px;
border-bottom: 1px solid rgba(64, 169, 255, 0.2);
.tab-item {
padding: 6px 14px;
font-size: 12px;
color: rgba(255, 255, 255, 0.7);
background-color: rgba(64, 169, 255, 0.1);
border: 1px solid rgba(64, 169, 255, 0.2);
border-radius: 4px;
cursor: pointer;
transition: all 0.3s;
&:hover {
background-color: rgba(64, 169, 255, 0.2);
border-color: rgba(64, 169, 255, 0.4);
}
&.active {
// background: linear-gradient(135deg, #40a9ff 0%, #1890ff 100%);
background: #163B6C;
border-color: #40a9ff;
color: #fff;
}
}
}
.tab-content {
.info-item {
margin-bottom: 12px;
&:last-child {
margin-bottom: 0;
}
}
}
</style>

View File

@ -0,0 +1,590 @@
<template>
<div v-if="visible" class="clearance-dialog-overlay" @click="handleOverlayClick">
<div class="clearance-dialog" @click.stop>
<!-- 标题栏 -->
<div class="dialog-header">
<div class="header-title">抢通情况</div>
<div class="close-btn" @click="handleClose">
<el-icon><Close /></el-icon>
</div>
</div>
<!-- 筛选区域 -->
<div class="filter-section">
<div class="filter-row">
<div class="filter-item">
<span class="filter-label">行政区域</span>
<el-select v-model="filterForm.region" placeholder="请选择" class="filter-select" clearable>
<el-option
v-for="item in regionOptions"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
</div>
<div class="filter-item">
<span class="filter-label">类型</span>
<el-select v-model="filterForm.type" placeholder="请选择" class="filter-select" clearable>
<el-option
v-for="item in typeOptions"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
</div>
<div class="filter-item">
<span class="filter-label">管控措施</span>
<el-select v-model="filterForm.controlMeasure" placeholder="请选择" class="filter-select" clearable>
<el-option
v-for="item in controlMeasureOptions"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
</div>
</div>
</div>
<!-- 数据表格 -->
<div class="table-section">
<div class="table-header">
<div class="th" style="width: 50px">序号</div>
<div class="th" style="width: 80px">行政区域</div>
<div class="th" style="width: 80px">线路编号</div>
<div class="th" style="width: 100px">起止桩号</div>
<div class="th" style="width: 100px">路况位置</div>
<div class="th" style="width: 140px">发生时间</div>
<div class="th" style="width: 80px">线路编号</div>
<div class="th" style="width: 80px">类型</div>
<div class="th" style="width: 100px">管控措施</div>
<div class="th" style="flex: 1">操作</div>
</div>
<div class="table-body">
<div
v-for="(item, index) in tableData"
:key="item.id"
class="table-row"
:class="{ 'row-even': index % 2 === 1 }"
>
<div class="td" style="width: 50px">{{ item.id }}</div>
<div class="td" style="width: 80px">{{ item.region }}</div>
<div class="td" style="width: 80px">{{ item.routeNo }}</div>
<div class="td" style="width: 100px">
<el-tooltip :content="item.stakeNo" placement="top" :show-after="500">
<span class="ellipsis-text">{{ item.stakeNo }}</span>
</el-tooltip>
</div>
<div class="td" style="width: 100px">
<el-tooltip :content="item.location" placement="top" :show-after="500">
<span class="ellipsis-text">{{ item.location }}</span>
</el-tooltip>
</div>
<div class="td" style="width: 140px">{{ item.occurrenceTime }}</div>
<div class="td" style="width: 80px">{{ item.routeNo2 }}</div>
<div class="td" style="width: 80px">{{ item.type }}</div>
<div class="td" style="width: 100px">
<span :class="['control-tag', getControlClass(item.controlMeasure)]">{{ item.controlMeasure }}</span>
</div>
<div class="td" style="flex: 1">
<span class="detail-link" @click="handleDetail(item)">详情</span>
</div>
</div>
</div>
</div>
<!-- 分页 -->
<div class="pagination">
<div class="page-btn" :class="{ disabled: currentPage === 1 }" @click="prevPage">
上一个
</div>
<div class="page-numbers">
<div
v-for="page in visiblePages"
:key="page"
class="page-num"
:class="{ active: currentPage === page }"
@click="goToPage(page)"
>
{{ page }}
</div>
</div>
<div class="page-btn" :class="{ disabled: currentPage === totalPages }" @click="nextPage">
下一个
</div>
</div>
</div>
</div>
</template>
<script setup>
import { ref, computed, watch } from "vue";
import { Close } from "@element-plus/icons-vue";
const props = defineProps({
visible: {
type: Boolean,
default: false,
},
});
const emit = defineEmits(["update:visible", "close", "detail"]);
//
const filterForm = ref({
region: "",
type: "",
controlMeasure: "",
});
//
const regionOptions = ref([
{ label: "巫溪县", value: "巫溪县" },
{ label: "万州区", value: "万州区" },
{ label: "沙坪坝区", value: "沙坪坝区" },
{ label: "渝中区", value: "渝中区" },
]);
//
const typeOptions = ref([
{ label: "边坡坍塌", value: "边坡坍塌" },
{ label: "路面塌陷", value: "路面塌陷" },
{ label: "桥梁损坏", value: "桥梁损坏" },
{ label: "隧道事故", value: "隧道事故" },
]);
//
const controlMeasureOptions = ref([
{ label: "全幅封闭", value: "全幅封闭" },
{ label: "半幅封闭", value: "半幅封闭" },
{ label: "正常通行", value: "正常通行" },
{ label: "限制通行", value: "限制通行" },
]);
//
const tableData = ref([
{
id: 1,
region: "巫溪县",
routeNo: "G242",
stakeNo: "336.800-338.850",
location: "三星乡五斗村",
occurrenceTime: "2025-08-11 04:53:42",
routeNo2: "G242",
type: "边坡坍塌",
controlMeasure: "全幅封闭",
},
{
id: 2,
region: "万州区",
routeNo: "G242",
stakeNo: "338.800-338.850",
location: "三星乡五斗村",
occurrenceTime: "2025-08-11 04:53:42",
routeNo2: "G242",
type: "边坡坍塌",
controlMeasure: "正常通行",
},
{
id: 3,
region: "沙坪坝区",
routeNo: "G319",
stakeNo: "120.500-122.000",
location: "沙坪坝镇",
occurrenceTime: "2025-08-10 14:30:00",
routeNo2: "G319",
type: "路面塌陷",
controlMeasure: "半幅封闭",
},
{
id: 4,
region: "渝中区",
routeNo: "G212",
stakeNo: "88.200-89.100",
location: "渝中大道",
occurrenceTime: "2025-08-09 09:15:30",
routeNo2: "G212",
type: "桥梁损坏",
controlMeasure: "限制通行",
},
]);
//
const currentPage = ref(1);
const pageSize = ref(10);
const total = ref(36);
const totalPages = computed(() => Math.ceil(total.value / pageSize.value));
const visiblePages = computed(() => {
const pages = [];
const maxVisible = 5;
let start = Math.max(1, currentPage.value - Math.floor(maxVisible / 2));
let end = Math.min(totalPages.value, start + maxVisible - 1);
if (end - start + 1 < maxVisible) {
start = Math.max(1, end - maxVisible + 1);
}
for (let i = start; i <= end; i++) {
pages.push(i);
}
return pages;
});
//
const getControlClass = (measure) => {
const classMap = {
"全幅封闭": "control-close",
"半幅封闭": "control-half",
"正常通行": "control-normal",
"限制通行": "control-limit",
};
return classMap[measure] || "";
};
//
const handleClose = () => {
emit("update:visible", false);
emit("close");
};
//
const handleOverlayClick = () => {
handleClose();
};
//
const handleDetail = (item) => {
emit("detail", item);
};
//
const prevPage = () => {
if (currentPage.value > 1) {
currentPage.value--;
fetchData();
}
};
const nextPage = () => {
if (currentPage.value < totalPages.value) {
currentPage.value++;
fetchData();
}
};
const goToPage = (page) => {
currentPage.value = page;
fetchData();
};
//
const fetchData = () => {
console.log("获取第", currentPage.value, "页数据");
// API
};
// visible
watch(
() => props.visible,
(newVal) => {
if (newVal) {
currentPage.value = 1;
fetchData();
}
}
);
</script>
<style lang="scss" scoped>
.clearance-dialog-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(0, 0, 0, 0.6);
display: flex;
align-items: center;
justify-content: center;
z-index: 2000;
}
.clearance-dialog {
width: 1000px;
background: linear-gradient(135deg, rgba(20, 50, 90, 0.95) 0%, rgba(10, 30, 60, 0.98) 100%);
border: 1px solid rgba(64, 169, 255, 0.3);
border-radius: 12px;
padding: 24px;
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.4);
}
//
.dialog-header {
display: flex;
align-items: center;
justify-content: center;
position: relative;
margin-bottom: 20px;
.header-title {
font-size: 20px;
font-weight: 600;
color: #fff;
padding: 8px 40px;
background: linear-gradient(90deg, transparent 0%, rgba(64, 169, 255, 0.2) 20%, rgba(64, 169, 255, 0.2) 80%, transparent 100%);
border-bottom: 2px solid #40a9ff;
}
.close-btn {
position: absolute;
right: 0;
top: 0;
width: 32px;
height: 32px;
display: flex;
align-items: center;
justify-content: center;
color: rgba(255, 255, 255, 0.7);
cursor: pointer;
font-size: 20px;
transition: color 0.3s;
&:hover {
color: #fff;
}
}
}
//
.filter-section {
margin-bottom: 16px;
.filter-row {
display: flex;
gap: 24px;
}
.filter-item {
display: flex;
align-items: center;
gap: 8px;
.filter-label {
font-size: 13px;
color: rgba(255, 255, 255, 0.8);
white-space: nowrap;
}
.filter-select {
width: 120px;
:deep(.el-input__wrapper) {
background-color: rgba(30, 70, 120, 0.4);
border: 1px solid rgba(64, 169, 255, 0.3);
box-shadow: none;
border-radius: 4px;
.el-input__inner {
color: #fff;
font-size: 13px;
&::placeholder {
color: rgba(255, 255, 255, 0.4);
}
}
.el-input__suffix {
.el-icon {
color: rgba(255, 255, 255, 0.6);
}
}
}
}
}
}
//
.table-section {
background-color: rgba(30, 70, 120, 0.3);
border-radius: 8px;
overflow: hidden;
margin-bottom: 20px;
.table-header {
display: flex;
background-color: rgba(64, 169, 255, 0.2);
padding: 12px 16px;
.th {
font-size: 14px;
font-weight: 500;
color: #fff;
text-align: center;
}
}
.table-body {
max-height: 320px;
overflow-y: auto;
.table-row {
display: flex;
padding: 14px 16px;
align-items: center;
transition: background-color 0.3s;
&:hover {
background-color: rgba(64, 169, 255, 0.1);
}
&.row-even {
background-color: rgba(30, 70, 120, 0.2);
}
.td {
font-size: 13px;
color: rgba(255, 255, 255, 0.85);
text-align: center;
.ellipsis-text {
display: block;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
max-width: 100%;
cursor: pointer;
}
//
.control-tag {
display: inline-block;
padding: 2px 10px;
border-radius: 4px;
font-size: 12px;
font-weight: 500;
&.control-close {
background-color: rgba(255, 77, 79, 0.2);
color: #ff4d4f;
border: 1px solid rgba(255, 77, 79, 0.4);
}
&.control-half {
background-color: rgba(255, 122, 0, 0.2);
color: #ff7a00;
border: 1px solid rgba(255, 122, 0, 0.4);
}
&.control-normal {
background-color: rgba(82, 196, 26, 0.2);
color: #52c41a;
border: 1px solid rgba(82, 196, 26, 0.4);
}
&.control-limit {
background-color: rgba(250, 219, 20, 0.2);
color: #fadb14;
border: 1px solid rgba(250, 219, 20, 0.4);
}
}
//
.detail-link {
color: #40a9ff;
cursor: pointer;
font-size: 13px;
transition: color 0.3s;
&:hover {
color: #69c0ff;
text-decoration: underline;
}
}
}
}
}
}
//
.pagination {
display: flex;
align-items: center;
justify-content: flex-end;
gap: 8px;
.page-btn {
padding: 6px 16px;
background-color: rgba(64, 169, 255, 0.1);
border: 1px solid rgba(64, 169, 255, 0.3);
border-radius: 4px;
font-size: 13px;
color: rgba(255, 255, 255, 0.8);
cursor: pointer;
transition: all 0.3s;
&:hover:not(.disabled) {
background-color: rgba(64, 169, 255, 0.2);
border-color: rgba(64, 169, 255, 0.5);
}
&.disabled {
opacity: 0.4;
cursor: not-allowed;
}
}
.page-numbers {
display: flex;
gap: 8px;
.page-num {
min-width: 28px;
height: 28px;
display: flex;
align-items: center;
justify-content: center;
background-color: rgba(64, 169, 255, 0.1);
border: 1px solid rgba(64, 169, 255, 0.3);
border-radius: 4px;
font-size: 13px;
color: rgba(255, 255, 255, 0.8);
cursor: pointer;
transition: all 0.3s;
&:hover:not(.active) {
background-color: rgba(64, 169, 255, 0.2);
border-color: rgba(64, 169, 255, 0.5);
}
&.active {
background-color: #40a9ff;
border-color: #40a9ff;
color: #fff;
}
}
}
}
//
.table-body::-webkit-scrollbar {
width: 6px;
}
.table-body::-webkit-scrollbar-track {
background: rgba(0, 0, 0, 0.2);
border-radius: 3px;
}
.table-body::-webkit-scrollbar-thumb {
background: linear-gradient(180deg, #40a9ff 0%, #1890ff 100%);
border-radius: 3px;
}
.table-body::-webkit-scrollbar-thumb:hover {
background: linear-gradient(180deg, #69c0ff 0%, #40a9ff 100%);
}
</style>

View File

@ -0,0 +1,183 @@
<template>
<div v-if="visible" class="confirm-dialog-overlay" @click="handleOverlayClick">
<div class="confirm-dialog" @click.stop>
<!-- 标题栏 -->
<div class="dialog-header">
<div class="header-title">{{ title }}</div>
<div class="close-btn" @click="handleCancel">
<el-icon><Close /></el-icon>
</div>
</div>
<!-- 提示内容 -->
<div class="dialog-content">
<p class="confirm-message">{{ message }}</p>
</div>
<!-- 按钮区域 -->
<div class="dialog-footer">
<el-button class="btn-cancel" @click="handleCancel">
{{ cancelText }}
</el-button>
<el-button type="primary" class="btn-confirm" @click="handleConfirm">
{{ confirmText }}
</el-button>
</div>
</div>
</div>
</template>
<script setup>
import { Close } from "@element-plus/icons-vue";
const props = defineProps({
visible: {
type: Boolean,
default: false,
},
title: {
type: String,
default: "提示",
},
message: {
type: String,
default: "",
},
confirmText: {
type: String,
default: "确定",
},
cancelText: {
type: String,
default: "取消",
},
});
const emit = defineEmits(["update:visible", "confirm", "cancel"]);
//
const handleConfirm = () => {
emit("confirm");
emit("update:visible", false);
};
//
const handleCancel = () => {
emit("cancel");
emit("update:visible", false);
};
//
const handleOverlayClick = () => {
handleCancel();
};
</script>
<style lang="scss" scoped>
.confirm-dialog-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(0, 0, 0, 0.6);
display: flex;
align-items: center;
justify-content: center;
z-index: 2200;
}
.confirm-dialog {
width: 360px;
background: linear-gradient(135deg, rgba(20, 50, 90, 0.95) 0%, rgba(10, 30, 60, 0.98) 100%);
border: 1px solid rgba(64, 169, 255, 0.3);
border-radius: 8px;
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.4);
overflow: hidden;
}
//
.dialog-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 12px 16px;
background: linear-gradient(90deg, rgba(64, 169, 255, 0.15) 0%, rgba(64, 169, 255, 0.05) 100%);
border-bottom: 1px solid rgba(64, 169, 255, 0.2);
.header-title {
font-size: 16px;
font-weight: 600;
color: #fff;
}
.close-btn {
width: 24px;
height: 24px;
display: flex;
align-items: center;
justify-content: center;
color: rgba(255, 255, 255, 0.6);
cursor: pointer;
font-size: 16px;
transition: color 0.3s;
&:hover {
color: #fff;
}
}
}
//
.dialog-content {
padding: 24px 20px;
text-align: center;
.confirm-message {
font-size: 14px;
color: rgba(255, 255, 255, 0.9);
line-height: 1.6;
margin: 0;
}
}
//
.dialog-footer {
display: flex;
justify-content: center;
gap: 16px;
padding: 0 20px 20px;
.btn-cancel {
min-width: 80px;
height: 32px;
background-color: transparent;
border: 1px solid rgba(64, 169, 255, 0.4);
color: rgba(255, 255, 255, 0.8);
font-size: 13px;
border-radius: 4px;
transition: all 0.3s;
&:hover {
background-color: rgba(64, 169, 255, 0.1);
border-color: rgba(64, 169, 255, 0.6);
color: #fff;
}
}
.btn-confirm {
min-width: 80px;
height: 32px;
background: linear-gradient(135deg, #40a9ff 0%, #1890ff 100%);
border: none;
color: #fff;
font-size: 13px;
border-radius: 4px;
transition: all 0.3s;
&:hover {
background: linear-gradient(135deg, #69c0ff 0%, #40a9ff 100%);
}
}
}
</style>

View File

@ -0,0 +1,530 @@
<template>
<div v-if="visible" class="control-dialog-overlay" @click="handleOverlayClick">
<div class="control-dialog" @click.stop>
<!-- 标题栏 -->
<div class="dialog-header">
<div class="header-title">管控情况</div>
<div class="close-btn" @click="handleClose">
<el-icon><Close /></el-icon>
</div>
</div>
<!-- 筛选区域 -->
<div class="filter-section">
<div class="filter-row">
<div class="filter-item">
<span class="filter-label">行政区域</span>
<el-select v-model="filterForm.region" placeholder="请选择" class="filter-select" clearable>
<el-option
v-for="item in regionOptions"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
</div>
<div class="filter-item">
<span class="filter-label">驻地风险等级</span>
<el-select v-model="filterForm.riskLevel" placeholder="请选择" class="filter-select" clearable>
<el-option
v-for="item in riskLevelOptions"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
</div>
</div>
</div>
<!-- 数据表格 -->
<div class="table-section">
<div class="table-header">
<div class="th" style="width: 60px">序号</div>
<div class="th" style="width: 100px">行政区域</div>
<div class="th" style="width: 200px">驻地名称</div>
<div class="th" style="width: 200px">所属项目</div>
<div class="th" style="width: 80px">驻地人数</div>
<div class="th" style="flex: 1">驻地风险等级</div>
</div>
<div class="table-body">
<div
v-for="(item, index) in tableData"
:key="item.id"
class="table-row"
:class="{ 'row-even': index % 2 === 1 }"
>
<div class="td" style="width: 60px">{{ item.id }}</div>
<div class="td" style="width: 100px">{{ item.region }}</div>
<div class="td" style="width: 200px">
<el-tooltip :content="item.stationName" placement="top" :show-after="500">
<span class="ellipsis-text">{{ item.stationName }}</span>
</el-tooltip>
</div>
<div class="td" style="width: 200px">
<el-tooltip :content="item.project" placement="top" :show-after="500">
<span class="ellipsis-text">{{ item.project }}</span>
</el-tooltip>
</div>
<div class="td" style="width: 80px">{{ item.peopleCount }}</div>
<div class="td" style="flex: 1">
<span :class="['risk-tag', getRiskClass(item.riskLevel)]">{{ item.riskLevel }}</span>
</div>
</div>
</div>
</div>
<!-- 分页 -->
<div class="pagination">
<div class="page-btn" :class="{ disabled: currentPage === 1 }" @click="prevPage">
上一个
</div>
<div class="page-numbers">
<div
v-for="page in visiblePages"
:key="page"
class="page-num"
:class="{ active: currentPage === page }"
@click="goToPage(page)"
>
{{ page }}
</div>
</div>
<div class="page-btn" :class="{ disabled: currentPage === totalPages }" @click="nextPage">
下一个
</div>
</div>
</div>
</div>
</template>
<script setup>
import { ref, computed, watch } from "vue";
import { Close } from "@element-plus/icons-vue";
const props = defineProps({
visible: {
type: Boolean,
default: false,
},
});
const emit = defineEmits(["update:visible", "close"]);
//
const filterForm = ref({
region: "",
riskLevel: "",
});
//
const regionOptions = ref([
{ label: "沙坪坝区", value: "沙坪坝区" },
{ label: "万州区", value: "万州区" },
{ label: "渝中区", value: "渝中区" },
{ label: "江北区", value: "江北区" },
]);
//
const riskLevelOptions = ref([
{ label: "Ⅰ级", value: "Ⅰ级" },
{ label: "Ⅱ级", value: "Ⅱ级" },
{ label: "Ⅲ级", value: "Ⅲ级" },
{ label: "Ⅳ级", value: "Ⅳ级" },
]);
//
const tableData = ref([
{
id: 1,
region: "沙坪坝区",
stationName: "沙坪坝区S545茅山峡公路桥新建工程渝黔铁路扩能改造工程项目经理部",
project: "沙坪坝区S545茅山峡公路桥新建工程渝黔铁路扩能改造工程",
peopleCount: 21,
riskLevel: "Ⅳ级",
},
{
id: 2,
region: "万州区",
stationName: "万州区项目经理部",
project: "万州区公路改造项目",
peopleCount: 15,
riskLevel: "Ⅲ级",
},
{
id: 3,
region: "渝中区",
stationName: "渝中区桥梁维护项目部",
project: "渝中区桥梁维护工程",
peopleCount: 8,
riskLevel: "Ⅱ级",
},
{
id: 4,
region: "江北区",
stationName: "江北区道路施工项目部",
project: "江北区道路施工项目",
peopleCount: 32,
riskLevel: "Ⅰ级",
},
]);
//
const currentPage = ref(1);
const pageSize = ref(10);
const total = ref(36);
const totalPages = computed(() => Math.ceil(total.value / pageSize.value));
const visiblePages = computed(() => {
const pages = [];
const maxVisible = 5;
let start = Math.max(1, currentPage.value - Math.floor(maxVisible / 2));
let end = Math.min(totalPages.value, start + maxVisible - 1);
if (end - start + 1 < maxVisible) {
start = Math.max(1, end - maxVisible + 1);
}
for (let i = start; i <= end; i++) {
pages.push(i);
}
return pages;
});
//
const getRiskClass = (level) => {
const classMap = {
"Ⅰ级": "risk-level-1",
"Ⅱ级": "risk-level-2",
"Ⅲ级": "risk-level-3",
"Ⅳ级": "risk-level-4",
};
return classMap[level] || "";
};
//
const handleClose = () => {
emit("update:visible", false);
emit("close");
};
//
const handleOverlayClick = () => {
handleClose();
};
//
const prevPage = () => {
if (currentPage.value > 1) {
currentPage.value--;
fetchData();
}
};
const nextPage = () => {
if (currentPage.value < totalPages.value) {
currentPage.value++;
fetchData();
}
};
const goToPage = (page) => {
currentPage.value = page;
fetchData();
};
//
const fetchData = () => {
console.log("获取第", currentPage.value, "页数据");
// API
};
// visible
watch(
() => props.visible,
(newVal) => {
if (newVal) {
currentPage.value = 1;
fetchData();
}
}
);
</script>
<style lang="scss" scoped>
.control-dialog-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(0, 0, 0, 0.6);
display: flex;
align-items: center;
justify-content: center;
z-index: 2000;
}
.control-dialog {
width: 900px;
background: linear-gradient(135deg, rgba(20, 50, 90, 0.95) 0%, rgba(10, 30, 60, 0.98) 100%);
border: 1px solid rgba(64, 169, 255, 0.3);
border-radius: 12px;
padding: 24px;
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.4);
}
//
.dialog-header {
display: flex;
align-items: center;
justify-content: center;
position: relative;
margin-bottom: 20px;
.header-title {
font-size: 20px;
font-weight: 600;
color: #fff;
padding: 8px 40px;
background: linear-gradient(90deg, transparent 0%, rgba(64, 169, 255, 0.2) 20%, rgba(64, 169, 255, 0.2) 80%, transparent 100%);
border-bottom: 2px solid #40a9ff;
}
.close-btn {
position: absolute;
right: 0;
top: 0;
width: 32px;
height: 32px;
display: flex;
align-items: center;
justify-content: center;
color: rgba(255, 255, 255, 0.7);
cursor: pointer;
font-size: 20px;
transition: color 0.3s;
&:hover {
color: #fff;
}
}
}
//
.filter-section {
margin-bottom: 16px;
.filter-row {
display: flex;
gap: 24px;
}
.filter-item {
display: flex;
align-items: center;
gap: 8px;
.filter-label {
font-size: 13px;
color: rgba(255, 255, 255, 0.8);
white-space: nowrap;
}
.filter-select {
width: 140px;
:deep(.el-input__wrapper) {
background-color: rgba(30, 70, 120, 0.4);
border: 1px solid rgba(64, 169, 255, 0.3);
box-shadow: none;
border-radius: 4px;
.el-input__inner {
color: #fff;
font-size: 13px;
&::placeholder {
color: rgba(255, 255, 255, 0.4);
}
}
.el-input__suffix {
.el-icon {
color: rgba(255, 255, 255, 0.6);
}
}
}
}
}
}
//
.table-section {
background-color: rgba(30, 70, 120, 0.3);
border-radius: 8px;
overflow: hidden;
margin-bottom: 20px;
.table-header {
display: flex;
background-color: rgba(64, 169, 255, 0.2);
padding: 12px 16px;
.th {
font-size: 14px;
font-weight: 500;
color: #fff;
text-align: center;
}
}
.table-body {
max-height: 320px;
overflow-y: auto;
.table-row {
display: flex;
padding: 14px 16px;
align-items: center;
transition: background-color 0.3s;
&:hover {
background-color: rgba(64, 169, 255, 0.1);
}
&.row-even {
background-color: rgba(30, 70, 120, 0.2);
}
.td {
font-size: 13px;
color: rgba(255, 255, 255, 0.85);
text-align: center;
.ellipsis-text {
display: block;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
max-width: 100%;
cursor: pointer;
}
//
.risk-tag {
display: inline-block;
padding: 2px 12px;
border-radius: 4px;
font-size: 12px;
font-weight: 500;
&.risk-level-1 {
background-color: rgba(255, 77, 79, 0.2);
color: #ff4d4f;
border: 1px solid rgba(255, 77, 79, 0.4);
}
&.risk-level-2 {
background-color: rgba(255, 122, 0, 0.2);
color: #ff7a00;
border: 1px solid rgba(255, 122, 0, 0.4);
}
&.risk-level-3 {
background-color: rgba(250, 219, 20, 0.2);
color: #fadb14;
border: 1px solid rgba(250, 219, 20, 0.4);
}
&.risk-level-4 {
background-color: rgba(82, 196, 26, 0.2);
color: #52c41a;
border: 1px solid rgba(82, 196, 26, 0.4);
}
}
}
}
}
}
//
.pagination {
display: flex;
align-items: center;
justify-content: flex-end;
gap: 8px;
.page-btn {
padding: 6px 16px;
background-color: rgba(64, 169, 255, 0.1);
border: 1px solid rgba(64, 169, 255, 0.3);
border-radius: 4px;
font-size: 13px;
color: rgba(255, 255, 255, 0.8);
cursor: pointer;
transition: all 0.3s;
&:hover:not(.disabled) {
background-color: rgba(64, 169, 255, 0.2);
border-color: rgba(64, 169, 255, 0.5);
}
&.disabled {
opacity: 0.4;
cursor: not-allowed;
}
}
.page-numbers {
display: flex;
gap: 8px;
.page-num {
min-width: 28px;
height: 28px;
display: flex;
align-items: center;
justify-content: center;
background-color: rgba(64, 169, 255, 0.1);
border: 1px solid rgba(64, 169, 255, 0.3);
border-radius: 4px;
font-size: 13px;
color: rgba(255, 255, 255, 0.8);
cursor: pointer;
transition: all 0.3s;
&:hover:not(.active) {
background-color: rgba(64, 169, 255, 0.2);
border-color: rgba(64, 169, 255, 0.5);
}
&.active {
background-color: #40a9ff;
border-color: #40a9ff;
color: #fff;
}
}
}
}
//
.table-body::-webkit-scrollbar {
width: 6px;
}
.table-body::-webkit-scrollbar-track {
background: rgba(0, 0, 0, 0.2);
border-radius: 3px;
}
.table-body::-webkit-scrollbar-thumb {
background: linear-gradient(180deg, #40a9ff 0%, #1890ff 100%);
border-radius: 3px;
}
.table-body::-webkit-scrollbar-thumb:hover {
background: linear-gradient(180deg, #69c0ff 0%, #40a9ff 100%);
}
</style>

View File

@ -0,0 +1,473 @@
<template>
<div v-if="visible" class="dispatch-detail-overlay" @click="handleOverlayClick">
<div class="dispatch-detail-dialog" @click.stop>
<!-- 标题栏 -->
<div class="dialog-header">
<div class="header-title">调度区县情况</div>
<div class="close-btn" @click="handleClose">
<el-icon><Close /></el-icon>
</div>
</div>
<!-- 筛选区域 -->
<div class="filter-section">
<div class="filter-row">
<div class="filter-item">
<span class="filter-label">行政区域</span>
<el-select v-model="filterForm.region" placeholder="请选择" class="filter-select" clearable>
<el-option
v-for="item in regionOptions"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
</div>
<div class="filter-item">
<span class="filter-label">类型</span>
<el-select v-model="filterForm.type" placeholder="请选择" class="filter-select" clearable>
<el-option
v-for="item in typeOptions"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
</div>
</div>
</div>
<!-- 数据表格 -->
<div class="table-section">
<div class="table-header">
<div class="th" style="width: 50px">序号</div>
<div class="th" style="width: 100px">区县/镇街</div>
<div class="th" style="width: 80px">姓名</div>
<div class="th" style="width: 120px">电话</div>
<div class="th" style="width: 100px">类型</div>
<div class="th" style="width: 140px">角色</div>
<div class="th" style="flex: 1">调度时间</div>
</div>
<div class="table-body">
<div
v-for="(item, index) in tableData"
:key="item.id"
class="table-row"
:class="{ 'row-even': index % 2 === 1 }"
>
<div class="td" style="width: 50px">{{ item.id }}</div>
<div class="td" style="width: 100px">{{ item.district }}</div>
<div class="td" style="width: 80px">{{ item.name }}</div>
<div class="td" style="width: 120px">{{ item.phone }}</div>
<div class="td" style="width: 100px">{{ item.type }}</div>
<div class="td" style="width: 140px">{{ item.role }}</div>
<div class="td" style="flex: 1">{{ item.dispatchTime }}</div>
</div>
</div>
</div>
<!-- 分页 -->
<div class="pagination">
<div class="page-btn" :class="{ disabled: currentPage === 1 }" @click="prevPage">
上一个
</div>
<div class="page-numbers">
<div
v-for="page in visiblePages"
:key="page"
class="page-num"
:class="{ active: currentPage === page }"
@click="goToPage(page)"
>
{{ page }}
</div>
</div>
<div class="page-btn" :class="{ disabled: currentPage === totalPages }" @click="nextPage">
下一个
</div>
</div>
</div>
</div>
</template>
<script setup>
import { ref, computed, watch } from "vue";
import { Close } from "@element-plus/icons-vue";
const props = defineProps({
visible: {
type: Boolean,
default: false,
},
});
const emit = defineEmits(["update:visible", "close"]);
//
const filterForm = ref({
region: "",
type: "",
});
//
const regionOptions = ref([
{ label: "柏梓镇", value: "柏梓镇" },
{ label: "万州区", value: "万州区" },
{ label: "沙坪坝区", value: "沙坪坝区" },
{ label: "渝中区", value: "渝中区" },
]);
//
const typeOptions = ref([
{ label: "交通主管部门", value: "交通主管部门" },
{ label: "公路机构", value: "公路机构" },
{ label: "养护站", value: "养护站" },
{ label: "护路员", value: "护路员" },
]);
//
const tableData = ref([
{
id: 1,
district: "柏梓镇",
name: "赵海浪",
phone: "18623520681",
type: "交通主管部门",
role: "一般人员(路长履职)",
dispatchTime: "2025-08-11 04:53:42",
},
{
id: 2,
district: "柏梓镇",
name: "赵海浪",
phone: "18623520681",
type: "公路机构",
role: "一般人员(路长履职)",
dispatchTime: "2025-08-11 04:53:42",
},
{
id: 3,
district: "万州区",
name: "王鑫",
phone: "18623520682",
type: "养护站",
role: "站长",
dispatchTime: "2025-08-10 14:30:00",
},
{
id: 4,
district: "沙坪坝区",
name: "李华",
phone: "18623520683",
type: "护路员",
role: "一般人员",
dispatchTime: "2025-08-09 09:15:30",
},
]);
//
const currentPage = ref(1);
const pageSize = ref(10);
const total = ref(36);
const totalPages = computed(() => Math.ceil(total.value / pageSize.value));
const visiblePages = computed(() => {
const pages = [];
const maxVisible = 5;
let start = Math.max(1, currentPage.value - Math.floor(maxVisible / 2));
let end = Math.min(totalPages.value, start + maxVisible - 1);
if (end - start + 1 < maxVisible) {
start = Math.max(1, end - maxVisible + 1);
}
for (let i = start; i <= end; i++) {
pages.push(i);
}
return pages;
});
//
const handleClose = () => {
emit("update:visible", false);
emit("close");
};
//
const handleOverlayClick = () => {
handleClose();
};
//
const prevPage = () => {
if (currentPage.value > 1) {
currentPage.value--;
fetchData();
}
};
const nextPage = () => {
if (currentPage.value < totalPages.value) {
currentPage.value++;
fetchData();
}
};
const goToPage = (page) => {
currentPage.value = page;
fetchData();
};
//
const fetchData = () => {
console.log("获取第", currentPage.value, "页数据");
// API
};
// visible
watch(
() => props.visible,
(newVal) => {
if (newVal) {
currentPage.value = 1;
fetchData();
}
}
);
</script>
<style lang="scss" scoped>
.dispatch-detail-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(0, 0, 0, 0.6);
display: flex;
align-items: center;
justify-content: center;
z-index: 2100;
}
.dispatch-detail-dialog {
width: 900px;
background: linear-gradient(135deg, rgba(20, 50, 90, 0.95) 0%, rgba(10, 30, 60, 0.98) 100%);
border: 1px solid rgba(64, 169, 255, 0.3);
border-radius: 12px;
padding: 24px;
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.4);
}
//
.dialog-header {
display: flex;
align-items: center;
justify-content: center;
position: relative;
margin-bottom: 20px;
.header-title {
font-size: 20px;
font-weight: 600;
color: #fff;
padding: 8px 40px;
background: linear-gradient(90deg, transparent 0%, rgba(64, 169, 255, 0.2) 20%, rgba(64, 169, 255, 0.2) 80%, transparent 100%);
border-bottom: 2px solid #40a9ff;
}
.close-btn {
position: absolute;
right: 0;
top: 0;
width: 32px;
height: 32px;
display: flex;
align-items: center;
justify-content: center;
color: rgba(255, 255, 255, 0.7);
cursor: pointer;
font-size: 20px;
transition: color 0.3s;
&:hover {
color: #fff;
}
}
}
//
.filter-section {
margin-bottom: 16px;
.filter-row {
display: flex;
gap: 24px;
}
.filter-item {
display: flex;
align-items: center;
gap: 8px;
.filter-label {
font-size: 13px;
color: rgba(255, 255, 255, 0.8);
white-space: nowrap;
}
.filter-select {
width: 140px;
:deep(.el-input__wrapper) {
background-color: rgba(30, 70, 120, 0.4);
border: 1px solid rgba(64, 169, 255, 0.3);
box-shadow: none;
border-radius: 4px;
.el-input__inner {
color: #fff;
font-size: 13px;
&::placeholder {
color: rgba(255, 255, 255, 0.4);
}
}
.el-input__suffix {
.el-icon {
color: rgba(255, 255, 255, 0.6);
}
}
}
}
}
}
//
.table-section {
background-color: rgba(30, 70, 120, 0.3);
border-radius: 8px;
overflow: hidden;
margin-bottom: 20px;
.table-header {
display: flex;
background-color: rgba(64, 169, 255, 0.2);
padding: 12px 16px;
.th {
font-size: 14px;
font-weight: 500;
color: #fff;
text-align: center;
}
}
.table-body {
max-height: 320px;
overflow-y: auto;
.table-row {
display: flex;
padding: 14px 16px;
align-items: center;
transition: background-color 0.3s;
&:hover {
background-color: rgba(64, 169, 255, 0.1);
}
&.row-even {
background-color: rgba(30, 70, 120, 0.2);
}
.td {
font-size: 13px;
color: rgba(255, 255, 255, 0.85);
text-align: center;
}
}
}
}
//
.pagination {
display: flex;
align-items: center;
justify-content: flex-end;
gap: 8px;
.page-btn {
padding: 6px 16px;
background-color: rgba(64, 169, 255, 0.1);
border: 1px solid rgba(64, 169, 255, 0.3);
border-radius: 4px;
font-size: 13px;
color: rgba(255, 255, 255, 0.8);
cursor: pointer;
transition: all 0.3s;
&:hover:not(.disabled) {
background-color: rgba(64, 169, 255, 0.2);
border-color: rgba(64, 169, 255, 0.5);
}
&.disabled {
opacity: 0.4;
cursor: not-allowed;
}
}
.page-numbers {
display: flex;
gap: 8px;
.page-num {
min-width: 28px;
height: 28px;
display: flex;
align-items: center;
justify-content: center;
background-color: rgba(64, 169, 255, 0.1);
border: 1px solid rgba(64, 169, 255, 0.3);
border-radius: 4px;
font-size: 13px;
color: rgba(255, 255, 255, 0.8);
cursor: pointer;
transition: all 0.3s;
&:hover:not(.active) {
background-color: rgba(64, 169, 255, 0.2);
border-color: rgba(64, 169, 255, 0.5);
}
&.active {
background-color: #40a9ff;
border-color: #40a9ff;
color: #fff;
}
}
}
}
//
.table-body::-webkit-scrollbar {
width: 6px;
}
.table-body::-webkit-scrollbar-track {
background: rgba(0, 0, 0, 0.2);
border-radius: 3px;
}
.table-body::-webkit-scrollbar-thumb {
background: linear-gradient(180deg, #40a9ff 0%, #1890ff 100%);
border-radius: 3px;
}
.table-body::-webkit-scrollbar-thumb:hover {
background: linear-gradient(180deg, #69c0ff 0%, #40a9ff 100%);
}
</style>

View File

@ -0,0 +1,455 @@
<template>
<div v-if="visible" class="dispatch-dialog-overlay" @click="handleOverlayClick">
<div class="dispatch-dialog" @click.stop>
<!-- 标题栏 -->
<div class="dialog-header">
<div class="header-title">调度区县情况</div>
<div class="close-btn" @click="handleClose">
<el-icon><Close /></el-icon>
</div>
</div>
<!-- 筛选区域 -->
<div class="filter-section">
<div class="filter-row">
<div class="filter-item">
<span class="filter-label">行政区域</span>
<el-select v-model="filterForm.region" placeholder="请选择" class="filter-select" clearable>
<el-option
v-for="item in regionOptions"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
</div>
</div>
</div>
<!-- 数据表格 -->
<div class="table-section">
<div class="table-header">
<div class="th" style="width: 60px">序号</div>
<div class="th" style="width: 120px">行政区域</div>
<div class="th" style="width: 100px">调度数</div>
<div class="th" style="flex: 1">最近调度时间</div>
</div>
<div class="table-body">
<div
v-for="(item, index) in tableData"
:key="item.id"
class="table-row"
:class="{ 'row-even': index % 2 === 1 }"
>
<div class="td" style="width: 60px">{{ item.id }}</div>
<div class="td" style="width: 120px">{{ item.region }}</div>
<div class="td" style="width: 100px">
<span class="dispatch-count" @click="handleDispatchClick(item)">{{ item.dispatchCount }}</span>
</div>
<div class="td" style="flex: 1">{{ item.lastDispatchTime }}</div>
</div>
</div>
</div>
<!-- 分页 -->
<div class="pagination">
<div class="page-btn" :class="{ disabled: currentPage === 1 }" @click="prevPage">
上一个
</div>
<div class="page-numbers">
<div
v-for="page in visiblePages"
:key="page"
class="page-num"
:class="{ active: currentPage === page }"
@click="goToPage(page)"
>
{{ page }}
</div>
</div>
<div class="page-btn" :class="{ disabled: currentPage === totalPages }" @click="nextPage">
下一个
</div>
</div>
</div>
</div>
</template>
<script setup>
import { ref, computed, watch } from "vue";
import { Close } from "@element-plus/icons-vue";
const props = defineProps({
visible: {
type: Boolean,
default: false,
},
});
const emit = defineEmits(["update:visible", "close", "dispatchClick"]);
//
const filterForm = ref({
region: "",
});
//
const regionOptions = ref([
{ label: "重庆市", value: "重庆市" },
{ label: "万州区", value: "万州区" },
{ label: "沙坪坝区", value: "沙坪坝区" },
{ label: "渝中区", value: "渝中区" },
]);
//
const tableData = ref([
{
id: 1,
region: "重庆市",
dispatchCount: 1,
lastDispatchTime: "2025-08-11 04:53:42",
},
{
id: 2,
region: "万州区",
dispatchCount: 1,
lastDispatchTime: "2025-08-11 04:53:42",
},
{
id: 3,
region: "沙坪坝区",
dispatchCount: 3,
lastDispatchTime: "2025-08-10 16:20:15",
},
{
id: 4,
region: "渝中区",
dispatchCount: 2,
lastDispatchTime: "2025-08-09 11:45:30",
},
]);
//
const currentPage = ref(1);
const pageSize = ref(10);
const total = ref(36);
const totalPages = computed(() => Math.ceil(total.value / pageSize.value));
const visiblePages = computed(() => {
const pages = [];
const maxVisible = 5;
let start = Math.max(1, currentPage.value - Math.floor(maxVisible / 2));
let end = Math.min(totalPages.value, start + maxVisible - 1);
if (end - start + 1 < maxVisible) {
start = Math.max(1, end - maxVisible + 1);
}
for (let i = start; i <= end; i++) {
pages.push(i);
}
return pages;
});
//
const handleClose = () => {
emit("update:visible", false);
emit("close");
};
//
const handleOverlayClick = () => {
handleClose();
};
//
const handleDispatchClick = (item) => {
emit("dispatchClick", item);
};
//
const prevPage = () => {
if (currentPage.value > 1) {
currentPage.value--;
fetchData();
}
};
const nextPage = () => {
if (currentPage.value < totalPages.value) {
currentPage.value++;
fetchData();
}
};
const goToPage = (page) => {
currentPage.value = page;
fetchData();
};
//
const fetchData = () => {
console.log("获取第", currentPage.value, "页数据");
// API
};
// visible
watch(
() => props.visible,
(newVal) => {
if (newVal) {
currentPage.value = 1;
fetchData();
}
}
);
</script>
<style lang="scss" scoped>
.dispatch-dialog-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(0, 0, 0, 0.6);
display: flex;
align-items: center;
justify-content: center;
z-index: 2000;
}
.dispatch-dialog {
width: 700px;
background: linear-gradient(135deg, rgba(20, 50, 90, 0.95) 0%, rgba(10, 30, 60, 0.98) 100%);
border: 1px solid rgba(64, 169, 255, 0.3);
border-radius: 12px;
padding: 24px;
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.4);
}
//
.dialog-header {
display: flex;
align-items: center;
justify-content: center;
position: relative;
margin-bottom: 20px;
.header-title {
font-size: 20px;
font-weight: 600;
color: #fff;
padding: 8px 40px;
background: linear-gradient(90deg, transparent 0%, rgba(64, 169, 255, 0.2) 20%, rgba(64, 169, 255, 0.2) 80%, transparent 100%);
border-bottom: 2px solid #40a9ff;
}
.close-btn {
position: absolute;
right: 0;
top: 0;
width: 32px;
height: 32px;
display: flex;
align-items: center;
justify-content: center;
color: rgba(255, 255, 255, 0.7);
cursor: pointer;
font-size: 20px;
transition: color 0.3s;
&:hover {
color: #fff;
}
}
}
//
.filter-section {
margin-bottom: 16px;
.filter-row {
display: flex;
gap: 24px;
}
.filter-item {
display: flex;
align-items: center;
gap: 8px;
.filter-label {
font-size: 13px;
color: rgba(255, 255, 255, 0.8);
white-space: nowrap;
}
.filter-select {
width: 140px;
:deep(.el-input__wrapper) {
background-color: rgba(30, 70, 120, 0.4);
border: 1px solid rgba(64, 169, 255, 0.3);
box-shadow: none;
border-radius: 4px;
.el-input__inner {
color: #fff;
font-size: 13px;
&::placeholder {
color: rgba(255, 255, 255, 0.4);
}
}
.el-input__suffix {
.el-icon {
color: rgba(255, 255, 255, 0.6);
}
}
}
}
}
}
//
.table-section {
background-color: rgba(30, 70, 120, 0.3);
border-radius: 8px;
overflow: hidden;
margin-bottom: 20px;
.table-header {
display: flex;
background-color: rgba(64, 169, 255, 0.2);
padding: 12px 16px;
.th {
font-size: 14px;
font-weight: 500;
color: #fff;
text-align: center;
}
}
.table-body {
max-height: 320px;
overflow-y: auto;
.table-row {
display: flex;
padding: 14px 16px;
align-items: center;
transition: background-color 0.3s;
&:hover {
background-color: rgba(64, 169, 255, 0.1);
}
&.row-even {
background-color: rgba(30, 70, 120, 0.2);
}
.td {
font-size: 13px;
color: rgba(255, 255, 255, 0.85);
text-align: center;
//
.dispatch-count {
color: #40a9ff;
cursor: pointer;
font-weight: 600;
transition: all 0.3s;
&:hover {
color: #69c0ff;
text-shadow: 0 0 8px rgba(105, 192, 255, 0.6);
}
}
}
}
}
}
//
.pagination {
display: flex;
align-items: center;
justify-content: flex-end;
gap: 8px;
.page-btn {
padding: 6px 16px;
background-color: rgba(64, 169, 255, 0.1);
border: 1px solid rgba(64, 169, 255, 0.3);
border-radius: 4px;
font-size: 13px;
color: rgba(255, 255, 255, 0.8);
cursor: pointer;
transition: all 0.3s;
&:hover:not(.disabled) {
background-color: rgba(64, 169, 255, 0.2);
border-color: rgba(64, 169, 255, 0.5);
}
&.disabled {
opacity: 0.4;
cursor: not-allowed;
}
}
.page-numbers {
display: flex;
gap: 8px;
.page-num {
min-width: 28px;
height: 28px;
display: flex;
align-items: center;
justify-content: center;
background-color: rgba(64, 169, 255, 0.1);
border: 1px solid rgba(64, 169, 255, 0.3);
border-radius: 4px;
font-size: 13px;
color: rgba(255, 255, 255, 0.8);
cursor: pointer;
transition: all 0.3s;
&:hover:not(.active) {
background-color: rgba(64, 169, 255, 0.2);
border-color: rgba(64, 169, 255, 0.5);
}
&.active {
background-color: #40a9ff;
border-color: #40a9ff;
color: #fff;
}
}
}
}
//
.table-body::-webkit-scrollbar {
width: 6px;
}
.table-body::-webkit-scrollbar-track {
background: rgba(0, 0, 0, 0.2);
border-radius: 3px;
}
.table-body::-webkit-scrollbar-thumb {
background: linear-gradient(180deg, #40a9ff 0%, #1890ff 100%);
border-radius: 3px;
}
.table-body::-webkit-scrollbar-thumb:hover {
background: linear-gradient(180deg, #69c0ff 0%, #40a9ff 100%);
}
</style>

View File

@ -0,0 +1,542 @@
<template>
<div v-if="visible" class="event-dialog-overlay" @click="handleOverlayClick">
<div class="event-dialog" @click.stop>
<!-- 标题栏 -->
<div class="dialog-header">
<div class="header-title">详情</div>
<div class="close-btn" @click="handleClose">
<el-icon><Close /></el-icon>
</div>
</div>
<!-- 事件基本信息 -->
<div class="section">
<div class="section-title">
<span class="title-icon"></span>
事件基本信息
</div>
<div class="info-grid">
<div class="info-row">
<div class="info-item">
<span class="info-label">事件编号</span>
<span class="info-value">{{ eventInfo.eventNo }}</span>
</div>
<div class="info-item">
<span class="info-label">发生时间</span>
<span class="info-value">{{ eventInfo.occurTime }}</span>
</div>
</div>
<div class="info-row">
<div class="info-item">
<span class="info-label">事件类型</span>
<span class="info-value">{{ eventInfo.eventType }}</span>
</div>
<div class="info-item">
<span class="info-label">事件等级</span>
<span class="info-value level-tag" :class="eventInfo.levelClass">{{ eventInfo.eventLevel }}</span>
</div>
</div>
<div class="info-row">
<div class="info-item">
<span class="info-label">所属区域</span>
<span class="info-value">{{ eventInfo.region }}</span>
</div>
<div class="info-item">
<span class="info-label">上报人</span>
<span class="info-value">{{ eventInfo.reporter }}</span>
</div>
</div>
<div class="info-row">
<div class="info-item">
<span class="info-label">详细地址</span>
<span class="info-value">{{ eventInfo.address }}</span>
</div>
<div class="info-item">
<span class="info-label">上报时间</span>
<span class="info-value">{{ eventInfo.reportTime }}</span>
</div>
</div>
<div class="info-row">
<div class="info-item full-width">
<span class="info-label">事件描述</span>
<span class="info-value">{{ eventInfo.description }}</span>
</div>
</div>
</div>
</div>
<!-- 处置反馈信息 -->
<div class="section">
<div class="section-title">
<span class="title-icon"></span>
处置反馈信息
</div>
<div class="feedback-list">
<div v-for="(item, index) in feedbackList" :key="item.id" class="feedback-item">
<div class="feedback-header">
<div class="feedback-num">{{ index + 1 }}</div>
<div class="feedback-info">
<div class="feedback-row">
<span class="feedback-label">处置时间</span>
<span class="feedback-value">{{ item.handleTime }}</span>
</div>
<div class="feedback-row">
<span class="feedback-label">处置人</span>
<span class="feedback-value">{{ item.handler }}</span>
</div>
</div>
<div class="feedback-image" @click="previewImage(item.image)">
<img :src="item.image" alt="处置图片" />
</div>
</div>
<div class="feedback-detail">
<div class="detail-row">
<span class="detail-label">预计开始时间</span>
<span class="detail-value">{{ item.estimatedStartTime }}</span>
<span class="detail-label" style="margin-left: 40px">预计结束时间</span>
<span class="detail-value">{{ item.estimatedEndTime }}</span>
</div>
<div class="detail-row">
<span class="detail-label">实际开始时间</span>
<span class="detail-value">{{ item.actualStartTime }}</span>
<span class="detail-label" style="margin-left: 40px">实际结束时间</span>
<span class="detail-value">{{ item.actualEndTime }}</span>
</div>
<div class="detail-row">
<span class="detail-label">处置人</span>
<span class="detail-value">{{ item.handlerName }}</span>
<span class="detail-label" style="margin-left: 40px">联系电话</span>
<span class="detail-value">{{ item.contactPhone }}</span>
</div>
<div class="detail-row">
<span class="detail-label">处置情况描述</span>
<span class="detail-value">{{ item.handleDesc }}</span>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- 图片预览弹窗 -->
<div v-if="previewVisible" class="image-preview-overlay" @click="closePreview">
<div class="image-preview-container" @click.stop>
<img :src="previewImageUrl" alt="预览" />
<div class="close-preview-btn" @click="closePreview">
<el-icon><Close /></el-icon>
</div>
</div>
</div>
</div>
</template>
<script setup>
import { ref, watch } from "vue";
import { Close } from "@element-plus/icons-vue";
const props = defineProps({
visible: {
type: Boolean,
default: false,
},
eventData: {
type: Object,
default: () => ({}),
},
});
const emit = defineEmits(["update:visible", "close"]);
//
const eventInfo = ref({
eventNo: "202310120001",
occurTime: "2023-10-12 14:30:00",
eventType: "路面塌陷",
eventLevel: "一般事件",
levelClass: "level-normal",
region: "万州区",
reporter: "张三",
address: "万州区太白路123号附近",
reportTime: "2023-10-12 14:35:00",
description: "路面出现塌陷面积约2平方米深度约0.5米,已设置警示标志,请尽快处理。",
});
//
const feedbackList = ref([
{
id: 1,
handleTime: "2023-10-12 14:45:26",
handler: "李四",
image: "https://via.placeholder.com/80x60/40a9ff/ffffff?text=图片1",
estimatedStartTime: "2023-10-12 14:30:00",
estimatedEndTime: "2023-10-12 16:30:00",
actualStartTime: "2023-10-12 14:45:00",
actualEndTime: "2023-10-12 16:20:00",
handlerName: "王五",
contactPhone: "13800138000",
handleDesc: "已到达现场,正在设置围挡,准备进行修复作业。",
},
{
id: 2,
handleTime: "2023-10-12 16:20:45",
handler: "李四",
image: "https://via.placeholder.com/80x60/40a9ff/ffffff?text=图片2",
estimatedStartTime: "2023-10-12 14:30:00",
estimatedEndTime: "2023-10-12 16:30:00",
actualStartTime: "2023-10-12 14:45:00",
actualEndTime: "2023-10-12 16:20:00",
handlerName: "王五",
contactPhone: "13800138000",
handleDesc: "修复作业已完成,路面已恢复平整,围挡已拆除,交通恢复正常。",
},
]);
//
const previewVisible = ref(false);
const previewImageUrl = ref("");
const previewImage = (url) => {
previewImageUrl.value = url;
previewVisible.value = true;
};
const closePreview = () => {
previewVisible.value = false;
previewImageUrl.value = "";
};
//
const handleClose = () => {
emit("update:visible", false);
emit("close");
};
//
const handleOverlayClick = () => {
handleClose();
};
// visible
watch(
() => props.visible,
(newVal) => {
if (newVal && props.eventData) {
//
Object.assign(eventInfo.value, props.eventData);
}
}
);
</script>
<style lang="scss" scoped>
.event-dialog-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(0, 0, 0, 0.6);
display: flex;
align-items: center;
justify-content: center;
z-index: 2100;
}
.event-dialog {
width: 700px;
max-height: 85vh;
overflow-y: auto;
background: linear-gradient(135deg, rgba(20, 50, 90, 0.95) 0%, rgba(10, 30, 60, 0.98) 100%);
border: 1px solid rgba(64, 169, 255, 0.3);
border-radius: 12px;
padding: 24px;
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.4);
}
//
.dialog-header {
display: flex;
align-items: center;
justify-content: center;
position: relative;
margin-bottom: 24px;
.header-title {
font-size: 20px;
font-weight: 600;
color: #fff;
padding: 8px 40px;
background: linear-gradient(90deg, transparent 0%, rgba(64, 169, 255, 0.2) 20%, rgba(64, 169, 255, 0.2) 80%, transparent 100%);
border-bottom: 2px solid #40a9ff;
}
.close-btn {
position: absolute;
right: 0;
top: 0;
width: 32px;
height: 32px;
display: flex;
align-items: center;
justify-content: center;
color: rgba(255, 255, 255, 0.7);
cursor: pointer;
font-size: 20px;
transition: color 0.3s;
&:hover {
color: #fff;
}
}
}
//
.section {
margin-bottom: 24px;
&:last-child {
margin-bottom: 0;
}
}
//
.section-title {
font-size: 16px;
font-weight: 600;
color: #fff;
margin-bottom: 16px;
display: flex;
align-items: center;
.title-icon {
color: #40a9ff;
margin-right: 8px;
}
}
//
.info-grid {
background-color: rgba(30, 70, 120, 0.3);
border-radius: 8px;
padding: 16px 20px;
}
.info-row {
display: flex;
margin-bottom: 12px;
&:last-child {
margin-bottom: 0;
}
}
.info-item {
flex: 1;
display: flex;
align-items: flex-start;
&.full-width {
flex: none;
width: 100%;
}
}
.info-label {
font-size: 13px;
color: rgba(255, 255, 255, 0.6);
white-space: nowrap;
min-width: 70px;
}
.info-value {
font-size: 13px;
color: rgba(255, 255, 255, 0.9);
flex: 1;
word-break: break-all;
&.level-tag {
display: inline-block;
padding: 2px 10px;
border-radius: 4px;
font-size: 12px;
font-weight: 500;
&.level-urgent {
background-color: rgba(255, 77, 79, 0.2);
color: #ff4d4f;
border: 1px solid rgba(255, 77, 79, 0.4);
}
&.level-normal {
background-color: rgba(64, 169, 255, 0.2);
color: #40a9ff;
border: 1px solid rgba(64, 169, 255, 0.4);
}
&.level-low {
background-color: rgba(82, 196, 26, 0.2);
color: #52c41a;
border: 1px solid rgba(82, 196, 26, 0.4);
}
}
}
//
.feedback-list {
display: flex;
flex-direction: column;
gap: 16px;
}
.feedback-item {
background-color: rgba(30, 70, 120, 0.3);
border-radius: 8px;
padding: 16px 20px;
border-left: 3px solid #40a9ff;
}
.feedback-header {
display: flex;
align-items: flex-start;
margin-bottom: 12px;
}
.feedback-num {
width: 24px;
height: 24px;
background: linear-gradient(135deg, #40a9ff 0%, #1890ff 100%);
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-size: 12px;
font-weight: 600;
color: #fff;
margin-right: 12px;
flex-shrink: 0;
}
.feedback-info {
flex: 1;
}
.feedback-row {
margin-bottom: 6px;
&:last-child {
margin-bottom: 0;
}
}
.feedback-label {
font-size: 13px;
color: rgba(255, 255, 255, 0.6);
}
.feedback-value {
font-size: 13px;
color: rgba(255, 255, 255, 0.9);
}
.feedback-image {
width: 80px;
height: 60px;
border-radius: 4px;
overflow: hidden;
cursor: pointer;
border: 1px solid rgba(64, 169, 255, 0.3);
transition: all 0.3s;
&:hover {
border-color: #40a9ff;
transform: scale(1.05);
}
img {
width: 100%;
height: 100%;
object-fit: cover;
}
}
//
.feedback-detail {
padding-left: 36px;
padding-top: 12px;
border-top: 1px solid rgba(64, 169, 255, 0.1);
}
.detail-row {
margin-bottom: 8px;
&:last-child {
margin-bottom: 0;
}
}
.detail-label {
font-size: 12px;
color: rgba(255, 255, 255, 0.5);
}
.detail-value {
font-size: 12px;
color: rgba(255, 255, 255, 0.8);
}
//
.image-preview-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(0, 0, 0, 0.85);
display: flex;
align-items: center;
justify-content: center;
z-index: 1100;
}
.image-preview-container {
position: relative;
max-width: 80%;
max-height: 80%;
img {
max-width: 100%;
max-height: 80vh;
border-radius: 8px;
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.5);
}
}
.close-preview-btn {
position: absolute;
top: -40px;
right: 0;
width: 32px;
height: 32px;
display: flex;
align-items: center;
justify-content: center;
color: rgba(255, 255, 255, 0.8);
cursor: pointer;
font-size: 24px;
transition: color 0.3s;
&:hover {
color: #fff;
}
}
//
.event-dialog {
scrollbar-width: none;
-ms-overflow-style: none;
&::-webkit-scrollbar {
display: none;
}
}
</style>

View File

@ -0,0 +1,567 @@
<template>
<div v-if="visible" class="impact-detail-dialog-overlay" @click="handleOverlayClick">
<div class="impact-detail-dialog" @click.stop>
<!-- 标题栏 -->
<div class="dialog-header">
<div class="header-title">影响点详情</div>
<div class="close-btn" @click="handleClose">
<el-icon><Close /></el-icon>
</div>
</div>
<!-- 隐患点基本信息 -->
<div class="section">
<div class="section-title">
<span class="title-icon"></span>
隐患点基本信息
</div>
<div class="basic-info">
<div class="info-row">
<div class="info-item">
<span class="info-dot"></span>
<span class="info-label">所属区县</span>
<span class="info-value">{{ basicInfo.district }}</span>
</div>
<div class="info-item">
<span class="info-dot"></span>
<span class="info-label">影响点等级</span>
<span class="info-value level-tag" :class="basicInfo.levelClass">{{ basicInfo.level }}</span>
</div>
<div class="info-item">
<span class="info-dot"></span>
<span class="info-label">公路编号</span>
<span class="info-value">{{ basicInfo.roadCode }}</span>
</div>
</div>
<div class="info-row">
<div class="info-item">
<span class="info-dot"></span>
<span class="info-label">位置</span>
<span class="info-value">{{ basicInfo.location }}</span>
</div>
<div class="info-item">
<span class="info-dot"></span>
<span class="info-label">风险点描述</span>
<span class="info-value" :class="getStatusClass(basicInfo.riskDesc)">{{ basicInfo.riskDesc }}</span>
</div>
<div class="info-item">
<span class="info-dot"></span>
<span class="info-label">发现时间</span>
<span class="info-value">{{ basicInfo.discoverTime }}</span>
</div>
</div>
</div>
</div>
<!-- 照片 -->
<div class="section">
<div class="section-title">
<span class="title-icon"></span>
照片
</div>
<div class="photo-list">
<div
v-for="(photo, index) in photoList"
:key="index"
class="photo-item"
@click="previewImage(photo)"
>
<img :src="photo" alt="照片" />
</div>
</div>
</div>
<!-- 填报动态信息 -->
<div class="section">
<div class="section-title">
<span class="title-icon"></span>
填报动态信息
</div>
<div class="timeline-list">
<div v-for="(record, index) in dynamicRecords" :key="index" class="timeline-item">
<div class="timeline-marker">{{ index + 1 }}</div>
<div class="timeline-content">
<div class="timeline-header">
<span class="timeline-type">{{ record.type }}</span>
</div>
<div class="timeline-detail">
<div class="detail-row">
<span class="detail-label">巡查时间</span>
<span class="detail-value">{{ record.patrolTime }}</span>
</div>
<div class="detail-row">
<span class="detail-label">巡查人</span>
<span class="detail-value">{{ record.patrolPerson }}</span>
</div>
<div class="detail-row">
<span class="detail-label">现场情况描述</span>
<span class="detail-value">{{ record.description }}</span>
<div v-if="record.image" class="detail-image" @click="previewImage(record.image)">
<img :src="record.image" alt="现场照片" />
</div>
</div>
<div class="detail-row">
<span class="detail-label">是否发现问题</span>
<span class="detail-value" :class="record.hasProblem ? 'status-yes' : 'status-no'">{{ record.hasProblem ? '是' : '否' }}</span>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- 图片预览弹窗 -->
<div v-if="previewVisible" class="image-preview-overlay" @click="closePreview">
<div class="image-preview-container" @click.stop>
<img :src="previewImageUrl" alt="预览" />
<div class="close-preview-btn" @click="closePreview">
<el-icon><Close /></el-icon>
</div>
</div>
</div>
</div>
</template>
<script setup>
import { ref, watch } from "vue";
import { Close } from "@element-plus/icons-vue";
const props = defineProps({
visible: {
type: Boolean,
default: false,
},
pointData: {
type: Object,
default: () => ({}),
},
});
const emit = defineEmits(["update:visible", "close"]);
//
const basicInfo = ref({
district: "合川区",
level: "一般隐患",
levelClass: "level-normal",
roadCode: "G348",
location: "丁吴路(K116+656至K116+739)",
riskDesc: "未回应",
discoverTime: "立即启动防汛Ⅰ级应急响应,立即转移危险区群众,医疗机构做好应急准备",
});
//
const photoList = ref([
"https://via.placeholder.com/120x80/40a9ff/ffffff?text=照片",
]);
//
const dynamicRecords = ref([
{
type: "隐患",
patrolTime: "2026-03-28 14:30:00",
patrolPerson: "刘伟",
description: "设置警示标识,半幅通行",
hasProblem: true,
image: "https://via.placeholder.com/80x60/40a9ff/ffffff?text=现场",
},
{
type: "隐患",
patrolTime: "2026-03-28 14:30:00",
patrolPerson: "刘伟",
description: "设置警示标识,半幅通行",
hasProblem: false,
image: null,
},
]);
//
const getStatusClass = (status) => {
if (status === "未回应") return "status-unresponse";
if (status === "已回应") return "status-response";
return "";
};
//
const previewVisible = ref(false);
const previewImageUrl = ref("");
const previewImage = (url) => {
previewImageUrl.value = url;
previewVisible.value = true;
};
const closePreview = () => {
previewVisible.value = false;
previewImageUrl.value = "";
};
//
const handleClose = () => {
emit("update:visible", false);
emit("close");
};
//
const handleOverlayClick = () => {
handleClose();
};
// visible
watch(
() => props.visible,
(newVal) => {
if (newVal && props.pointData) {
Object.assign(basicInfo.value, props.pointData);
}
}
);
</script>
<style lang="scss" scoped>
.impact-detail-dialog-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(0, 0, 0, 0.6);
display: flex;
align-items: center;
justify-content: center;
z-index: 2500;
}
.impact-detail-dialog {
width: 650px;
max-height: 90vh;
overflow-y: auto;
background: linear-gradient(135deg, rgba(20, 50, 90, 0.95) 0%, rgba(10, 30, 60, 0.98) 100%);
border: 1px solid rgba(64, 169, 255, 0.3);
border-radius: 12px;
padding: 24px;
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.4);
}
//
.dialog-header {
display: flex;
align-items: center;
justify-content: center;
position: relative;
margin-bottom: 24px;
.header-title {
font-size: 20px;
font-weight: 600;
color: #fff;
padding: 8px 40px;
background: linear-gradient(90deg, transparent 0%, rgba(64, 169, 255, 0.2) 20%, rgba(64, 169, 255, 0.2) 80%, transparent 100%);
border-bottom: 2px solid #40a9ff;
}
.close-btn {
position: absolute;
right: 0;
top: 0;
width: 32px;
height: 32px;
display: flex;
align-items: center;
justify-content: center;
color: rgba(255, 255, 255, 0.7);
cursor: pointer;
font-size: 20px;
transition: color 0.3s;
&:hover {
color: #fff;
}
}
}
//
.section {
margin-bottom: 20px;
&:last-child {
margin-bottom: 0;
}
}
//
.section-title {
font-size: 15px;
font-weight: 600;
color: #fff;
margin-bottom: 12px;
display: flex;
align-items: center;
.title-icon {
color: #40a9ff;
margin-right: 6px;
}
}
//
.basic-info {
background-color: rgba(30, 70, 120, 0.3);
border-radius: 8px;
padding: 16px 20px;
}
.info-row {
display: flex;
margin-bottom: 12px;
&:last-child {
margin-bottom: 0;
}
}
.info-item {
flex: 1;
display: flex;
align-items: flex-start;
gap: 6px;
}
.info-dot {
width: 6px;
height: 6px;
background-color: rgba(255, 255, 255, 0.4);
border-radius: 50%;
margin-top: 6px;
flex-shrink: 0;
}
.info-label {
font-size: 12px;
color: rgba(255, 255, 255, 0.5);
white-space: nowrap;
min-width: 70px;
}
.info-value {
font-size: 12px;
color: rgba(255, 255, 255, 0.9);
line-height: 1.4;
flex: 1;
&.level-tag {
display: inline-block;
padding: 2px 10px;
border-radius: 4px;
font-size: 12px;
font-weight: 500;
width: fit-content;
&.level-normal {
background-color: rgba(250, 219, 95, 0.2);
color: #fadb5f;
border: 1px solid rgba(250, 219, 95, 0.4);
}
&.level-serious {
background-color: rgba(255, 77, 79, 0.2);
color: #ff4d4f;
border: 1px solid rgba(255, 77, 79, 0.4);
}
}
&.status-unresponse {
color: #ff7a45;
}
&.status-response {
color: #52c41a;
}
}
//
.photo-list {
display: flex;
gap: 12px;
flex-wrap: wrap;
}
.photo-item {
width: 120px;
height: 80px;
border-radius: 6px;
overflow: hidden;
cursor: pointer;
border: 1px solid rgba(64, 169, 255, 0.3);
transition: all 0.3s;
&:hover {
border-color: #40a9ff;
transform: scale(1.05);
}
img {
width: 100%;
height: 100%;
object-fit: cover;
}
}
//
.timeline-list {
display: flex;
flex-direction: column;
gap: 16px;
}
.timeline-item {
display: flex;
gap: 12px;
position: relative;
}
.timeline-marker {
width: 24px;
height: 24px;
border-radius: 50%;
background: linear-gradient(135deg, #40a9ff 0%, #1890ff 100%);
display: flex;
align-items: center;
justify-content: center;
font-size: 12px;
font-weight: 600;
color: #fff;
flex-shrink: 0;
margin-top: 2px;
}
.timeline-content {
flex: 1;
background-color: rgba(30, 70, 120, 0.3);
border-radius: 8px;
padding: 12px 16px;
}
.timeline-header {
margin-bottom: 10px;
}
.timeline-type {
font-size: 14px;
color: #40a9ff;
font-weight: 600;
}
//
.timeline-detail {
display: flex;
flex-direction: column;
gap: 8px;
}
.detail-row {
display: flex;
align-items: flex-start;
gap: 8px;
}
.detail-label {
font-size: 12px;
color: rgba(255, 255, 255, 0.5);
white-space: nowrap;
min-width: 90px;
}
.detail-value {
font-size: 12px;
color: rgba(255, 255, 255, 0.85);
flex: 1;
line-height: 1.5;
&.status-yes {
color: #52c41a;
}
&.status-no {
color: #ff7a45;
}
}
.detail-image {
width: 80px;
height: 60px;
border-radius: 4px;
overflow: hidden;
cursor: pointer;
border: 1px solid rgba(64, 169, 255, 0.3);
margin-left: 12px;
flex-shrink: 0;
&:hover {
border-color: #40a9ff;
}
img {
width: 100%;
height: 100%;
object-fit: cover;
}
}
//
.image-preview-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(0, 0, 0, 0.85);
display: flex;
align-items: center;
justify-content: center;
z-index: 2600;
}
.image-preview-container {
position: relative;
max-width: 80%;
max-height: 80%;
img {
max-width: 100%;
max-height: 80vh;
border-radius: 8px;
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.5);
}
}
.close-preview-btn {
position: absolute;
top: -40px;
right: 0;
width: 32px;
height: 32px;
display: flex;
align-items: center;
justify-content: center;
color: rgba(255, 255, 255, 0.8);
cursor: pointer;
font-size: 24px;
transition: color 0.3s;
&:hover {
color: #fff;
}
}
//
.impact-detail-dialog {
scrollbar-width: none;
-ms-overflow-style: none;
&::-webkit-scrollbar {
display: none;
}
}
</style>

View File

@ -0,0 +1,569 @@
<template>
<div v-if="visible" class="impact-dialog-overlay" @click="handleOverlayClick">
<div class="impact-dialog" @click.stop>
<!-- 标题栏 -->
<div class="dialog-header">
<div class="header-title">影响点情况</div>
<div class="close-btn" @click="handleClose">
<el-icon><Close /></el-icon>
</div>
</div>
<!-- 筛选区域 -->
<div class="filter-section">
<div class="filter-row">
<div class="filter-item">
<el-select v-model="filterForm.pointType" placeholder="影响点类型" class="filter-select">
<el-option label="全部" value="" />
<el-option label="边坡" value="slope" />
<el-option label="桥梁" value="bridge" />
<el-option label="隧道" value="tunnel" />
<el-option label="路面" value="road" />
</el-select>
</div>
<div class="filter-item">
<el-select v-model="filterForm.pointLevel" placeholder="影响点等级" class="filter-select">
<el-option label="全部" value="" />
<el-option label="一般隐患" value="normal" />
<el-option label="重大隐患" value="serious" />
</el-select>
</div>
<div class="filter-item">
<el-select v-model="filterForm.region" placeholder="行政区域" class="filter-select">
<el-option label="全部" value="" />
<el-option label="万州区" value="wanzhou" />
<el-option label="涪陵区" value="fuling" />
<el-option label="合川区" value="hechuan" />
</el-select>
</div>
<div class="filter-item">
<el-button type="primary" class="search-btn" @click="handleSearch">
查询
</el-button>
</div>
</div>
</div>
<!-- 数据表格 -->
<div class="table-section">
<div class="table-header">
<div class="th" style="width: 50px">序号</div>
<div class="th" style="width: 80px">行政区域</div>
<div class="th" style="width: 80px">影响点类型</div>
<div class="th" style="width: 180px">影响点位置</div>
<div class="th" style="width: 90px">影响点等级</div>
<div class="th" style="width: 130px">交通主管部门负责人</div>
<div class="th" style="width: 110px">公路机构责任人</div>
<div class="th" style="width: 110px">养护站负责人</div>
<div class="th" style="width: 100px">护路员</div>
<div class="th" style="width: 60px">操作</div>
</div>
<div class="table-body">
<div
v-for="(item, index) in tableData"
:key="item.id"
class="table-row"
:class="{ 'row-even': index % 2 === 1 }"
>
<div class="td" style="width: 50px">{{ item.id }}</div>
<div class="td" style="width: 80px">{{ item.region }}</div>
<div class="td" style="width: 80px">{{ item.pointType }}</div>
<div class="td" style="width: 180px">{{ item.pointLocation }}</div>
<div class="td" style="width: 90px">
<span class="level-tag" :class="item.levelClass">{{ item.pointLevel }}</span>
</div>
<div class="td" style="width: 130px">
<div class="person-info">
<span class="person-name">{{ item.trafficDept.name }}</span>
<span class="person-phone">{{ item.trafficDept.phone }}</span>
</div>
</div>
<div class="td" style="width: 110px">
<div class="person-info">
<span class="person-name">{{ item.roadOrg.name }}</span>
<span class="person-phone">{{ item.roadOrg.phone }}</span>
</div>
</div>
<div class="td" style="width: 110px">
<div class="person-info">
<span class="person-name">{{ item.maintenance.name }}</span>
<span class="person-phone">{{ item.maintenance.phone }}</span>
</div>
</div>
<div class="td" style="width: 100px">
<div class="person-info">
<span class="person-name">{{ item.roadKeeper.name }}</span>
<span class="person-phone">{{ item.roadKeeper.phone }}</span>
</div>
</div>
<div class="td" style="width: 60px">
<span class="detail-link" @click="handleDetail(item)">详情</span>
</div>
</div>
</div>
</div>
<!-- 分页 -->
<div class="pagination">
<span class="total">{{ total }}条数据</span>
<div class="page-btns">
<div class="page-btn" :class="{ disabled: currentPage === 1 }" @click="prevPage">
<el-icon><ArrowLeft /></el-icon>
</div>
<div
v-for="page in visiblePages"
:key="page"
class="page-btn"
:class="{ active: currentPage === page }"
@click="goToPage(page)"
>
{{ page }}
</div>
<div class="page-btn" :class="{ disabled: currentPage === totalPages }" @click="nextPage">
<el-icon><ArrowRight /></el-icon>
</div>
</div>
</div>
</div>
</div>
</template>
<script setup>
import { ref, computed, watch } from "vue";
import { Close, ArrowLeft, ArrowRight } from "@element-plus/icons-vue";
const props = defineProps({
visible: {
type: Boolean,
default: false,
},
});
const emit = defineEmits(["update:visible", "close", "detail"]);
//
const filterForm = ref({
pointType: "",
pointLevel: "",
region: "",
});
//
const tableData = ref([
{
id: 1,
region: "重庆市",
pointType: "边坡",
pointLocation: "武汉-大理(K1452+951至K1462+209)",
pointLevel: "一般隐患",
levelClass: "level-normal",
trafficDept: { name: "罗宸", phone: "17623865172" },
roadOrg: { name: "李海平", phone: "13708320801" },
maintenance: { name: "苏祖兵", phone: "13594331090" },
roadKeeper: { name: "凌承礼", phone: "1592393704" },
},
{
id: 2,
region: "重庆市",
pointType: "边坡",
pointLocation: "武汉-大理(K1452+951至K1462+209)",
pointLevel: "一般隐患",
levelClass: "level-normal",
trafficDept: { name: "罗宸", phone: "17623865172" },
roadOrg: { name: "李海平", phone: "13708320801" },
maintenance: { name: "苏祖兵", phone: "13594331090" },
roadKeeper: { name: "凌承礼", phone: "1592393704" },
},
{
id: 3,
region: "重庆市",
pointType: "边坡",
pointLocation: "武汉-大理(K1452+951至K1462+209)",
pointLevel: "一般隐患",
levelClass: "level-normal",
trafficDept: { name: "罗宸", phone: "17623865172" },
roadOrg: { name: "李海平", phone: "13708320801" },
maintenance: { name: "苏祖兵", phone: "13594331090" },
roadKeeper: { name: "凌承礼", phone: "1592393704" },
},
]);
//
const currentPage = ref(1);
const pageSize = ref(10);
const total = ref(36);
const totalPages = computed(() => Math.ceil(total.value / pageSize.value));
const visiblePages = computed(() => {
const pages = [];
const maxVisible = 4;
let start = Math.max(1, currentPage.value - Math.floor(maxVisible / 2));
let end = Math.min(totalPages.value, start + maxVisible - 1);
if (end - start + 1 < maxVisible) {
start = Math.max(1, end - maxVisible + 1);
}
for (let i = start; i <= end; i++) {
pages.push(i);
}
return pages;
});
//
const handleClose = () => {
emit("update:visible", false);
emit("close");
};
//
const handleOverlayClick = () => {
handleClose();
};
//
const handleSearch = () => {
console.log("查询条件:", filterForm.value);
currentPage.value = 1;
fetchData();
};
//
const handleDetail = (item) => {
emit("detail", item);
};
//
const prevPage = () => {
if (currentPage.value > 1) {
currentPage.value--;
fetchData();
}
};
const nextPage = () => {
if (currentPage.value < totalPages.value) {
currentPage.value++;
fetchData();
}
};
const goToPage = (page) => {
currentPage.value = page;
fetchData();
};
//
const fetchData = () => {
console.log("获取第", currentPage.value, "页数据");
// API
};
// visible
watch(
() => props.visible,
(newVal) => {
if (newVal) {
currentPage.value = 1;
fetchData();
}
}
);
</script>
<style lang="scss" scoped>
.impact-dialog-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(0, 0, 0, 0.6);
display: flex;
align-items: center;
justify-content: center;
z-index: 2000;
}
.impact-dialog {
width: 1050px;
background: linear-gradient(135deg, rgba(20, 50, 90, 0.95) 0%, rgba(10, 30, 60, 0.98) 100%);
border: 1px solid rgba(64, 169, 255, 0.3);
border-radius: 12px;
padding: 24px;
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.4);
}
//
.dialog-header {
display: flex;
align-items: center;
justify-content: center;
position: relative;
margin-bottom: 20px;
.header-title {
font-size: 20px;
font-weight: 600;
color: #fff;
padding: 8px 40px;
background: linear-gradient(90deg, transparent 0%, rgba(64, 169, 255, 0.2) 20%, rgba(64, 169, 255, 0.2) 80%, transparent 100%);
border-bottom: 2px solid #40a9ff;
}
.close-btn {
position: absolute;
right: 0;
top: 0;
width: 32px;
height: 32px;
display: flex;
align-items: center;
justify-content: center;
color: rgba(255, 255, 255, 0.7);
cursor: pointer;
font-size: 20px;
transition: color 0.3s;
&:hover {
color: #fff;
}
}
}
//
.filter-section {
margin-bottom: 20px;
.filter-row {
display: flex;
align-items: center;
gap: 12px;
}
.filter-item {
.filter-select {
width: 150px;
:deep(.el-input__wrapper) {
background-color: rgba(30, 70, 120, 0.4);
border: 1px solid rgba(64, 169, 255, 0.3);
box-shadow: none;
border-radius: 4px;
.el-input__inner {
color: #fff;
font-size: 13px;
&::placeholder {
color: rgba(255, 255, 255, 0.5);
}
}
.el-input__suffix {
.el-icon {
color: rgba(255, 255, 255, 0.6);
}
}
}
}
.search-btn {
background: linear-gradient(135deg, #40a9ff 0%, #1890ff 100%);
border: none;
border-radius: 4px;
padding: 0 24px;
height: 32px;
font-size: 13px;
&:hover {
background: linear-gradient(135deg, #69c0ff 0%, #40a9ff 100%);
}
}
}
}
//
.table-section {
background-color: rgba(30, 70, 120, 0.3);
border-radius: 8px;
overflow: hidden;
margin-bottom: 20px;
.table-header {
display: flex;
background-color: rgba(64, 169, 255, 0.2);
padding: 12px 16px;
.th {
font-size: 13px;
font-weight: 500;
color: #fff;
text-align: center;
}
}
.table-body {
max-height: 320px;
overflow-y: auto;
.table-row {
display: flex;
padding: 12px 16px;
align-items: center;
transition: background-color 0.3s;
min-height: 60px;
&:hover {
background-color: rgba(64, 169, 255, 0.1);
}
&.row-even {
background-color: rgba(30, 70, 120, 0.2);
}
.td {
font-size: 12px;
color: rgba(255, 255, 255, 0.85);
text-align: center;
word-break: break-all;
padding: 0 4px;
.level-tag {
display: inline-block;
padding: 2px 8px;
border-radius: 4px;
font-size: 11px;
&.level-normal {
background-color: rgba(250, 219, 95, 0.2);
color: #fadb5f;
border: 1px solid rgba(250, 219, 95, 0.4);
}
&.level-serious {
background-color: rgba(255, 77, 79, 0.2);
color: #ff4d4f;
border: 1px solid rgba(255, 77, 79, 0.4);
}
}
.person-info {
display: flex;
flex-direction: column;
gap: 2px;
.person-name {
font-size: 12px;
color: rgba(255, 255, 255, 0.9);
}
.person-phone {
font-size: 11px;
color: rgba(255, 255, 255, 0.6);
}
}
.detail-link {
color: #40a9ff;
cursor: pointer;
font-size: 12px;
&:hover {
color: #69c0ff;
text-decoration: underline;
}
}
}
}
}
}
//
.pagination {
display: flex;
align-items: center;
justify-content: flex-end;
gap: 16px;
.total {
font-size: 13px;
color: rgba(255, 255, 255, 0.6);
}
.page-btns {
display: flex;
gap: 8px;
.page-btn {
min-width: 28px;
height: 28px;
display: flex;
align-items: center;
justify-content: center;
background-color: rgba(64, 169, 255, 0.1);
border: 1px solid rgba(64, 169, 255, 0.2);
border-radius: 4px;
font-size: 12px;
color: rgba(255, 255, 255, 0.8);
cursor: pointer;
transition: all 0.3s;
&:hover:not(.disabled):not(.active) {
background-color: rgba(64, 169, 255, 0.2);
border-color: rgba(64, 169, 255, 0.4);
}
&.active {
background-color: #40a9ff;
border-color: #40a9ff;
color: #fff;
}
&.disabled {
opacity: 0.4;
cursor: not-allowed;
}
}
}
}
//
.table-body::-webkit-scrollbar {
width: 6px;
}
.table-body::-webkit-scrollbar-track {
background: rgba(0, 0, 0, 0.2);
border-radius: 3px;
}
.table-body::-webkit-scrollbar-thumb {
background: linear-gradient(180deg, #40a9ff 0%, #1890ff 100%);
border-radius: 3px;
}
.table-body::-webkit-scrollbar-thumb:hover {
background: linear-gradient(180deg, #69c0ff 0%, #40a9ff 100%);
}
//
:deep(.el-select-dropdown) {
background-color: rgba(20, 50, 90, 0.98);
border: 1px solid rgba(64, 169, 255, 0.3);
.el-select-dropdown__item {
color: rgba(255, 255, 255, 0.85);
&:hover {
background-color: rgba(64, 169, 255, 0.2);
}
&.selected {
background-color: rgba(64, 169, 255, 0.3);
color: #40a9ff;
}
}
}
</style>

View File

@ -0,0 +1,677 @@
<template>
<div v-if="visible" class="response-dialog-overlay" @click="handleOverlayClick">
<div class="response-dialog" @click.stop>
<!-- 标题栏 -->
<div class="dialog-header">
<div class="header-title">响应点详情</div>
<div class="close-btn" @click="handleClose">
<el-icon><Close /></el-icon>
</div>
</div>
<!-- 隐患点基本信息 -->
<div class="section">
<div class="section-title">
<span class="title-icon"></span>
隐患点基本信息
</div>
<div class="basic-info">
<div class="info-row three-col">
<div class="info-item">
<span class="info-label">所属区县</span>
<span class="info-value">{{ basicInfo.district }}</span>
</div>
<div class="info-item">
<span class="info-label">影响点等级</span>
<span class="info-value level-tag" :class="basicInfo.levelClass">{{ basicInfo.level }}</span>
</div>
<div class="info-item">
<span class="info-label">公路编号</span>
<span class="info-value">{{ basicInfo.roadCode }}</span>
</div>
</div>
<div class="info-row three-col">
<div class="info-item">
<span class="info-label">位置</span>
<span class="info-value">{{ basicInfo.location }}</span>
</div>
<div class="info-item">
<span class="info-label">风险点描述</span>
<span class="info-value" :class="getStatusClass(basicInfo.riskDesc)">{{ basicInfo.riskDesc }}</span>
</div>
<div class="info-item">
<span class="info-label">发现时间</span>
<span class="info-value">{{ basicInfo.discoverTime }}</span>
</div>
</div>
</div>
</div>
<!-- 照片 -->
<div class="section">
<div class="section-title">
<span class="title-icon"></span>
照片
</div>
<div class="photo-list">
<div
v-for="(photo, index) in photoList"
:key="index"
class="photo-item"
@click="previewImage(photo)"
>
<img :src="photo" alt="照片" />
</div>
</div>
</div>
<!-- 巡查记录 -->
<div class="section">
<div class="section-title">
<span class="title-icon"></span>
巡查记录
</div>
<div class="timeline-list">
<!-- 巡查记录详情 -->
<div class="timeline-item patrol-item">
<div class="timeline-marker patrol"></div>
<div class="timeline-content">
<div class="timeline-header">
<span class="timeline-type">巡查记录</span>
<span class="timeline-person">{{ patrolRecord.person }}</span>
<span class="timeline-time">{{ patrolRecord.time }}</span>
</div>
<div class="timeline-detail">
<div class="detail-row">
<span class="detail-label">巡查轨迹</span>
<span class="detail-link" @click="viewTrack">查看轨迹</span>
</div>
<div class="detail-row">
<span class="detail-label">现场情况</span>
<span class="detail-text">{{ patrolRecord.situation }}</span>
<div v-if="patrolRecord.image" class="detail-image" @click="previewImage(patrolRecord.image)">
<img :src="patrolRecord.image" alt="现场照片" />
</div>
</div>
</div>
</div>
</div>
<!-- 动态记录列表 -->
<div v-for="(record, index) in dynamicRecords" :key="index" class="timeline-item">
<div class="timeline-marker" :class="record.type"></div>
<div class="timeline-content">
<div class="timeline-header">
<span class="timeline-type">{{ record.typeName }}</span>
<span class="timeline-person">{{ record.person }}</span>
<span class="timeline-time">{{ record.time }}</span>
<span v-if="record.status" class="timeline-status" :class="record.statusClass">{{ record.status }}</span>
<span v-if="record.target" class="timeline-target">
<span class="target-name">{{ record.target }}</span> {{ record.targetPhone }}
</span>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- 图片预览弹窗 -->
<div v-if="previewVisible" class="image-preview-overlay" @click="closePreview">
<div class="image-preview-container" @click.stop>
<img :src="previewImageUrl" alt="预览" />
<div class="close-preview-btn" @click="closePreview">
<el-icon><Close /></el-icon>
</div>
</div>
</div>
</div>
</template>
<script setup>
import { ref, watch } from "vue";
import { Close } from "@element-plus/icons-vue";
const props = defineProps({
visible: {
type: Boolean,
default: false,
},
pointData: {
type: Object,
default: () => ({}),
},
});
const emit = defineEmits(["update:visible", "close", "viewTrack"]);
//
const basicInfo = ref({
district: "合川区",
level: "一般隐患",
levelClass: "level-normal",
roadCode: "G348",
location: "丁吴路(K116+656至K116+739)",
riskDesc: "未回应",
discoverTime: "立即启动防汛Ⅰ级应急响应,立即转移危险区群众,医疗机构做好应急准备",
});
//
const photoList = ref([
"https://via.placeholder.com/120x80/40a9ff/ffffff?text=照片1",
"https://via.placeholder.com/120x80/40a9ff/ffffff?text=照片2",
]);
//
const patrolRecord = ref({
person: "蒋汉成 18702307964",
time: "2025-10-14 15:43:24",
situation: "收到暴雨黄色预警信息,开展公路夜间巡查排查,道路滑坡涉灾点,无明显变化",
image: "https://via.placeholder.com/80x60/40a9ff/ffffff?text=现场",
});
//
const dynamicRecords = ref([
{
type: "dispatch",
typeName: "调度记录",
person: "蒋汉成",
time: "2025-10-13 15:43:24",
status: "【已接通语音】",
statusClass: "status-success",
target: "养护站负责人",
targetPhone: "刘孝万13609403931",
},
{
type: "warning",
typeName: "预警记录",
person: "蒋汉成18702307964",
time: "2025-10-13 15:43:24",
status: "审核驳回",
statusClass: "status-reject",
},
{
type: "warning",
typeName: "预警记录",
person: "蒋汉成18702307964",
time: "2025-10-13 15:43:24",
status: "审核通过",
statusClass: "status-success",
},
{
type: "warning",
typeName: "预警记录",
person: "蒋汉成18702307964",
time: "2025-10-13 15:43:24",
status: "响应预警",
statusClass: "status-success",
},
]);
//
const getStatusClass = (status) => {
if (status === "未回应") return "status-unresponse";
if (status === "已回应") return "status-response";
return "";
};
//
const previewVisible = ref(false);
const previewImageUrl = ref("");
const previewImage = (url) => {
previewImageUrl.value = url;
previewVisible.value = true;
};
const closePreview = () => {
previewVisible.value = false;
previewImageUrl.value = "";
};
//
const viewTrack = () => {
emit("viewTrack", patrolRecord.value);
};
//
const handleClose = () => {
emit("update:visible", false);
emit("close");
};
//
const handleOverlayClick = () => {
handleClose();
};
// visible
watch(
() => props.visible,
(newVal) => {
if (newVal && props.pointData) {
Object.assign(basicInfo.value, props.pointData);
}
}
);
</script>
<style lang="scss" scoped>
.response-dialog-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(0, 0, 0, 0.6);
display: flex;
align-items: center;
justify-content: center;
z-index: 1000;
}
.response-dialog {
width: 700px;
max-height: 90vh;
overflow-y: auto;
background: linear-gradient(135deg, rgba(20, 50, 90, 0.95) 0%, rgba(10, 30, 60, 0.98) 100%);
border: 1px solid rgba(64, 169, 255, 0.3);
border-radius: 12px;
padding: 24px;
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.4);
}
//
.dialog-header {
display: flex;
align-items: center;
justify-content: center;
position: relative;
margin-bottom: 24px;
.header-title {
font-size: 20px;
font-weight: 600;
color: #fff;
padding: 8px 40px;
background: linear-gradient(90deg, transparent 0%, rgba(64, 169, 255, 0.2) 20%, rgba(64, 169, 255, 0.2) 80%, transparent 100%);
border-bottom: 2px solid #40a9ff;
}
.close-btn {
position: absolute;
right: 0;
top: 0;
width: 32px;
height: 32px;
display: flex;
align-items: center;
justify-content: center;
color: rgba(255, 255, 255, 0.7);
cursor: pointer;
font-size: 20px;
transition: color 0.3s;
&:hover {
color: #fff;
}
}
}
//
.section {
margin-bottom: 20px;
&:last-child {
margin-bottom: 0;
}
}
//
.section-title {
font-size: 15px;
font-weight: 600;
color: #fff;
margin-bottom: 12px;
display: flex;
align-items: center;
.title-icon {
color: #40a9ff;
margin-right: 6px;
}
}
//
.basic-info {
background-color: rgba(30, 70, 120, 0.3);
border-radius: 8px;
padding: 16px 20px;
}
.info-row {
display: flex;
margin-bottom: 12px;
&:last-child {
margin-bottom: 0;
}
&.three-col {
.info-item {
flex: 1;
}
}
}
.info-item {
display: flex;
flex-direction: column;
gap: 4px;
}
.info-label {
font-size: 12px;
color: rgba(255, 255, 255, 0.5);
}
.info-value {
font-size: 13px;
color: rgba(255, 255, 255, 0.9);
line-height: 1.4;
&.level-tag {
display: inline-block;
padding: 2px 10px;
border-radius: 4px;
font-size: 12px;
font-weight: 500;
width: fit-content;
&.level-normal {
background-color: rgba(250, 219, 95, 0.2);
color: #fadb5f;
border: 1px solid rgba(250, 219, 95, 0.4);
}
&.level-serious {
background-color: rgba(255, 77, 79, 0.2);
color: #ff4d4f;
border: 1px solid rgba(255, 77, 79, 0.4);
}
}
&.status-unresponse {
color: #ff7a45;
}
&.status-response {
color: #52c41a;
}
}
//
.photo-list {
display: flex;
gap: 12px;
flex-wrap: wrap;
}
.photo-item {
width: 120px;
height: 80px;
border-radius: 6px;
overflow: hidden;
cursor: pointer;
border: 1px solid rgba(64, 169, 255, 0.3);
transition: all 0.3s;
&:hover {
border-color: #40a9ff;
transform: scale(1.05);
}
img {
width: 100%;
height: 100%;
object-fit: cover;
}
}
//
.timeline-list {
display: flex;
flex-direction: column;
gap: 12px;
}
.timeline-item {
display: flex;
gap: 12px;
position: relative;
&.patrol-item {
.timeline-content {
background-color: rgba(30, 70, 120, 0.3);
border-radius: 8px;
padding: 12px;
}
}
}
.timeline-marker {
width: 12px;
height: 12px;
border-radius: 50%;
background-color: #40a9ff;
flex-shrink: 0;
margin-top: 4px;
position: relative;
&::after {
content: '';
position: absolute;
top: 12px;
left: 50%;
transform: translateX(-50%);
width: 2px;
height: calc(100% + 12px);
background-color: rgba(64, 169, 255, 0.3);
}
&.patrol {
background-color: #40a9ff;
}
&.dispatch {
background-color: #faad14;
}
&.warning {
background-color: #ff4d4f;
}
}
.timeline-item:last-child .timeline-marker::after {
display: none;
}
.timeline-content {
flex: 1;
padding-bottom: 8px;
}
.timeline-header {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 8px;
margin-bottom: 8px;
}
.timeline-type {
font-size: 13px;
color: #40a9ff;
font-weight: 500;
}
.timeline-person {
font-size: 13px;
color: rgba(255, 255, 255, 0.9);
}
.timeline-time {
font-size: 12px;
color: rgba(255, 255, 255, 0.6);
}
.timeline-status {
font-size: 12px;
padding: 2px 8px;
border-radius: 4px;
&.status-success {
color: #52c41a;
}
&.status-reject {
color: #ff4d4f;
}
}
.timeline-target {
font-size: 12px;
color: rgba(255, 255, 255, 0.7);
.target-name {
color: #40a9ff;
font-weight: 500;
}
}
//
.timeline-detail {
margin-top: 8px;
}
.detail-row {
display: flex;
align-items: flex-start;
gap: 8px;
margin-bottom: 8px;
&:last-child {
margin-bottom: 0;
}
}
.detail-label {
font-size: 12px;
color: rgba(255, 255, 255, 0.5);
white-space: nowrap;
}
.detail-text {
font-size: 12px;
color: rgba(255, 255, 255, 0.85);
flex: 1;
line-height: 1.5;
}
.detail-link {
font-size: 12px;
color: #40a9ff;
cursor: pointer;
text-decoration: underline;
&:hover {
color: #69c0ff;
}
}
.detail-image {
width: 80px;
height: 60px;
border-radius: 4px;
overflow: hidden;
cursor: pointer;
border: 1px solid rgba(64, 169, 255, 0.3);
margin-left: 12px;
flex-shrink: 0;
&:hover {
border-color: #40a9ff;
}
img {
width: 100%;
height: 100%;
object-fit: cover;
}
}
//
.image-preview-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(0, 0, 0, 0.85);
display: flex;
align-items: center;
justify-content: center;
z-index: 1100;
}
.image-preview-container {
position: relative;
max-width: 80%;
max-height: 80%;
img {
max-width: 100%;
max-height: 80vh;
border-radius: 8px;
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.5);
}
}
.close-preview-btn {
position: absolute;
top: -40px;
right: 0;
width: 32px;
height: 32px;
display: flex;
align-items: center;
justify-content: center;
color: rgba(255, 255, 255, 0.8);
cursor: pointer;
font-size: 24px;
transition: color 0.3s;
&:hover {
color: #fff;
}
}
//
.response-dialog::-webkit-scrollbar {
width: 6px;
}
.response-dialog::-webkit-scrollbar-track {
background: rgba(0, 0, 0, 0.2);
border-radius: 3px;
}
.response-dialog::-webkit-scrollbar-thumb {
background: linear-gradient(180deg, #40a9ff 0%, #1890ff 100%);
border-radius: 3px;
}
.response-dialog::-webkit-scrollbar-thumb:hover {
background: linear-gradient(180deg, #69c0ff 0%, #40a9ff 100%);
}
</style>

View File

@ -0,0 +1,714 @@
<template>
<div v-if="visible" class="response-info-dialog-overlay" @click="handleOverlayClick">
<div class="response-info-dialog" @click.stop>
<!-- 标题栏 -->
<div class="dialog-header">
<div class="header-title">响应点详情</div>
<div class="close-btn" @click="handleClose">
<el-icon><Close /></el-icon>
</div>
</div>
<!-- 基本信息 -->
<div class="section">
<div class="section-title">
<span class="title-icon"></span>
基本信息
</div>
<div class="basic-info">
<div class="info-row">
<div class="info-item">
<span class="info-dot"></span>
<span class="info-label">所属区县</span>
<span class="info-value">{{ basicInfo.district }}</span>
</div>
<div class="info-item">
<span class="info-dot"></span>
<span class="info-label">公路编号</span>
<span class="info-value">{{ basicInfo.roadCode }}</span>
</div>
<div class="info-item">
<span class="info-dot"></span>
<span class="info-label">风险点类型</span>
<span class="info-value">{{ basicInfo.riskType }}</span>
</div>
</div>
<div class="info-row">
<div class="info-item">
<span class="info-dot"></span>
<span class="info-label">风险点位置</span>
<span class="info-value">{{ basicInfo.riskLocation }}</span>
</div>
<div class="info-item">
<span class="info-dot"></span>
<span class="info-label">回应状态</span>
<span class="info-value" :class="getStatusClass(basicInfo.responseStatus)">{{ basicInfo.responseStatus }}</span>
</div>
<div class="info-item">
<span class="info-dot"></span>
<span class="info-label">审核状态</span>
<span class="info-value" :class="getAuditClass(basicInfo.auditStatus)">{{ basicInfo.auditStatus }}</span>
</div>
</div>
<div class="info-row">
<div class="info-item">
<span class="info-dot"></span>
<span class="info-label">预警等级</span>
<span class="info-value level-red">{{ basicInfo.warningLevel }}</span>
</div>
<div class="info-item">
<span class="info-dot"></span>
<span class="info-label">起点桩号-止点桩号</span>
<span class="info-value">{{ basicInfo.stakeRange }}</span>
</div>
<div class="info-item">
<span class="info-dot"></span>
<span class="info-label">发现时间</span>
<span class="info-value">{{ basicInfo.discoverTime }}</span>
</div>
</div>
</div>
</div>
<!-- 照片 -->
<div class="section">
<div class="section-title">
<span class="title-icon"></span>
照片
</div>
<div class="photo-list">
<div
v-for="(photo, index) in photoList"
:key="index"
class="photo-item"
@click="previewImage(photo)"
>
<img :src="photo" alt="照片" />
</div>
</div>
</div>
<!-- 巡查记录 -->
<div class="section">
<div class="section-title">
<span class="title-icon"></span>
巡查记录
</div>
<div class="timeline-list">
<!-- 巡查记录详情 -->
<div class="timeline-item patrol-item">
<div class="timeline-marker patrol"></div>
<div class="timeline-content">
<div class="timeline-header">
<span class="timeline-type">巡查记录</span>
<span class="timeline-person">{{ patrolRecord.person }}</span>
<span class="timeline-time">{{ patrolRecord.time }}</span>
</div>
<div class="timeline-detail">
<div class="detail-row">
<span class="detail-label">巡查轨迹</span>
<span class="detail-link" @click="viewTrack">查看轨迹</span>
</div>
<div class="detail-row">
<span class="detail-label">现场情况</span>
<span class="detail-text">{{ patrolRecord.situation }}</span>
<div v-if="patrolRecord.image" class="detail-image" @click="previewImage(patrolRecord.image)">
<img :src="patrolRecord.image" alt="现场照片" />
</div>
</div>
</div>
</div>
</div>
<!-- 动态记录列表 -->
<div v-for="(record, index) in dynamicRecords" :key="index" class="timeline-item">
<div class="timeline-marker" :class="record.type"></div>
<div class="timeline-content">
<div class="timeline-header">
<span class="timeline-type">{{ record.typeName }}</span>
<span class="timeline-person">{{ record.person }}</span>
<span class="timeline-time">{{ record.time }}</span>
<span v-if="record.status" class="timeline-status" :class="record.statusClass">{{ record.status }}</span>
<span v-if="record.target" class="timeline-target">
<span class="target-name">{{ record.target }}</span> {{ record.targetPhone }}
</span>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- 图片预览弹窗 -->
<div v-if="previewVisible" class="image-preview-overlay" @click="closePreview">
<div class="image-preview-container" @click.stop>
<img :src="previewImageUrl" alt="预览" />
<div class="close-preview-btn" @click="closePreview">
<el-icon><Close /></el-icon>
</div>
</div>
</div>
</div>
</template>
<script setup>
import { ref, watch } from "vue";
import { Close } from "@element-plus/icons-vue";
const props = defineProps({
visible: {
type: Boolean,
default: false,
},
pointData: {
type: Object,
default: () => ({}),
},
});
const emit = defineEmits(["update:visible", "close", "viewTrack"]);
//
const basicInfo = ref({
district: "合川区",
roadCode: "G542",
riskType: "风险路段",
riskLocation: "丁吴路(K116+656至K116+739)",
responseStatus: "未回应",
auditStatus: "未审核",
warningLevel: "红色预警",
stakeRange: "三级治理中心发布发布暴雨红色预警信号",
discoverTime: "立即启动防汛Ⅰ级应急响应,立即转移危险区群众,医疗机构做好应急准备",
});
//
const photoList = ref([
"https://via.placeholder.com/100x70/40a9ff/ffffff?text=照片1",
"https://via.placeholder.com/100x70/40a9ff/ffffff?text=照片2",
"https://via.placeholder.com/100x70/40a9ff/ffffff?text=照片3",
"https://via.placeholder.com/100x70/40a9ff/ffffff?text=照片4",
]);
//
const patrolRecord = ref({
person: "蒋汉成 18702307964",
time: "2025-10-14 15:43:24",
situation: "收到暴雨黄色预警信息,开展公路夜间巡查排查,道路滑坡涉灾点,无明显变化",
image: "https://via.placeholder.com/80x60/40a9ff/ffffff?text=现场",
});
//
const dynamicRecords = ref([
{
type: "dispatch",
typeName: "调度记录",
person: "蒋汉成",
time: "2025-10-13 15:43:24",
status: "【已接通语音】",
statusClass: "status-success",
target: "养护站负责人",
targetPhone: "刘孝万13609403931",
},
{
type: "warning",
typeName: "预警记录",
person: "蒋汉成18702307964",
time: "2025-10-13 15:43:24",
status: "审核驳回",
statusClass: "status-reject",
},
{
type: "warning",
typeName: "预警记录",
person: "蒋汉成18702307964",
time: "2025-10-13 15:43:24",
status: "审核通过",
statusClass: "status-success",
},
{
type: "warning",
typeName: "预警记录",
person: "蒋汉成18702307964",
time: "2025-10-13 15:43:24",
status: "响应预警",
statusClass: "status-success",
},
]);
//
const getStatusClass = (status) => {
if (status === "未回应") return "status-unresponse";
if (status === "已回应") return "status-response";
return "";
};
const getAuditClass = (status) => {
if (status === "未审核") return "status-unaudit";
if (status === "审核通过") return "status-pass";
if (status === "审核驳回") return "status-reject";
return "";
};
//
const previewVisible = ref(false);
const previewImageUrl = ref("");
const previewImage = (url) => {
previewImageUrl.value = url;
previewVisible.value = true;
};
const closePreview = () => {
previewVisible.value = false;
previewImageUrl.value = "";
};
//
const viewTrack = () => {
emit("viewTrack", patrolRecord.value);
};
//
const handleClose = () => {
emit("update:visible", false);
emit("close");
};
//
const handleOverlayClick = () => {
handleClose();
};
// visible
watch(
() => props.visible,
(newVal) => {
if (newVal && props.pointData) {
Object.assign(basicInfo.value, props.pointData);
}
}
);
</script>
<style lang="scss" scoped>
.response-info-dialog-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(0, 0, 0, 0.6);
display: flex;
align-items: center;
justify-content: center;
z-index: 1000;
}
.response-info-dialog {
width: 750px;
max-height: 90vh;
overflow-y: auto;
background: linear-gradient(135deg, rgba(20, 50, 90, 0.95) 0%, rgba(10, 30, 60, 0.98) 100%);
border: 1px solid rgba(64, 169, 255, 0.3);
border-radius: 12px;
padding: 24px;
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.4);
}
//
.dialog-header {
display: flex;
align-items: center;
justify-content: center;
position: relative;
margin-bottom: 24px;
.header-title {
font-size: 20px;
font-weight: 600;
color: #fff;
padding: 8px 40px;
background: linear-gradient(90deg, transparent 0%, rgba(64, 169, 255, 0.2) 20%, rgba(64, 169, 255, 0.2) 80%, transparent 100%);
border-bottom: 2px solid #40a9ff;
}
.close-btn {
position: absolute;
right: 0;
top: 0;
width: 32px;
height: 32px;
display: flex;
align-items: center;
justify-content: center;
color: rgba(255, 255, 255, 0.7);
cursor: pointer;
font-size: 20px;
transition: color 0.3s;
&:hover {
color: #fff;
}
}
}
//
.section {
margin-bottom: 20px;
&:last-child {
margin-bottom: 0;
}
}
//
.section-title {
font-size: 15px;
font-weight: 600;
color: #fff;
margin-bottom: 12px;
display: flex;
align-items: center;
.title-icon {
color: #40a9ff;
margin-right: 6px;
}
}
//
.basic-info {
background-color: rgba(30, 70, 120, 0.3);
border-radius: 8px;
padding: 16px 20px;
}
.info-row {
display: flex;
margin-bottom: 12px;
&:last-child {
margin-bottom: 0;
}
}
.info-item {
flex: 1;
display: flex;
align-items: flex-start;
gap: 6px;
}
.info-dot {
width: 6px;
height: 6px;
background-color: rgba(255, 255, 255, 0.4);
border-radius: 50%;
margin-top: 6px;
flex-shrink: 0;
}
.info-label {
font-size: 12px;
color: rgba(255, 255, 255, 0.5);
white-space: nowrap;
min-width: 80px;
}
.info-value {
font-size: 12px;
color: rgba(255, 255, 255, 0.9);
line-height: 1.4;
flex: 1;
&.level-red {
color: #ff4d4f;
font-weight: 500;
}
&.status-unresponse {
color: #ff7a45;
}
&.status-response {
color: #52c41a;
}
&.status-unaudit {
color: #ff4d4f;
}
&.status-pass {
color: #52c41a;
}
&.status-reject {
color: #ff4d4f;
}
}
//
.photo-list {
display: flex;
gap: 12px;
flex-wrap: wrap;
}
.photo-item {
width: 100px;
height: 70px;
border-radius: 6px;
overflow: hidden;
cursor: pointer;
border: 1px solid rgba(64, 169, 255, 0.3);
transition: all 0.3s;
&:hover {
border-color: #40a9ff;
transform: scale(1.05);
}
img {
width: 100%;
height: 100%;
object-fit: cover;
}
}
//
.timeline-list {
display: flex;
flex-direction: column;
gap: 12px;
}
.timeline-item {
display: flex;
gap: 12px;
position: relative;
&.patrol-item {
.timeline-content {
background-color: rgba(30, 70, 120, 0.3);
border-radius: 8px;
padding: 12px;
}
}
}
.timeline-marker {
width: 12px;
height: 12px;
border-radius: 50%;
background-color: #40a9ff;
flex-shrink: 0;
margin-top: 4px;
position: relative;
&::after {
content: '';
position: absolute;
top: 12px;
left: 50%;
transform: translateX(-50%);
width: 2px;
height: calc(100% + 12px);
background-color: rgba(64, 169, 255, 0.3);
}
&.patrol {
background-color: #40a9ff;
}
&.dispatch {
background-color: #faad14;
}
&.warning {
background-color: #ff4d4f;
}
}
.timeline-item:last-child .timeline-marker::after {
display: none;
}
.timeline-content {
flex: 1;
padding-bottom: 8px;
}
.timeline-header {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 8px;
margin-bottom: 8px;
}
.timeline-type {
font-size: 13px;
color: #40a9ff;
font-weight: 500;
}
.timeline-person {
font-size: 13px;
color: rgba(255, 255, 255, 0.9);
}
.timeline-time {
font-size: 12px;
color: rgba(255, 255, 255, 0.6);
}
.timeline-status {
font-size: 12px;
padding: 2px 8px;
border-radius: 4px;
&.status-success {
color: #52c41a;
}
&.status-reject {
color: #ff4d4f;
}
}
.timeline-target {
font-size: 12px;
color: rgba(255, 255, 255, 0.7);
.target-name {
color: #40a9ff;
font-weight: 500;
}
}
//
.timeline-detail {
margin-top: 8px;
}
.detail-row {
display: flex;
align-items: flex-start;
gap: 8px;
margin-bottom: 8px;
&:last-child {
margin-bottom: 0;
}
}
.detail-label {
font-size: 12px;
color: rgba(255, 255, 255, 0.5);
white-space: nowrap;
}
.detail-text {
font-size: 12px;
color: rgba(255, 255, 255, 0.85);
flex: 1;
line-height: 1.5;
}
.detail-link {
font-size: 12px;
color: #40a9ff;
cursor: pointer;
text-decoration: underline;
&:hover {
color: #69c0ff;
}
}
.detail-image {
width: 80px;
height: 60px;
border-radius: 4px;
overflow: hidden;
cursor: pointer;
border: 1px solid rgba(64, 169, 255, 0.3);
margin-left: 12px;
flex-shrink: 0;
&:hover {
border-color: #40a9ff;
}
img {
width: 100%;
height: 100%;
object-fit: cover;
}
}
//
.image-preview-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(0, 0, 0, 0.85);
display: flex;
align-items: center;
justify-content: center;
z-index: 1100;
}
.image-preview-container {
position: relative;
max-width: 80%;
max-height: 80%;
img {
max-width: 100%;
max-height: 80vh;
border-radius: 8px;
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.5);
}
}
.close-preview-btn {
position: absolute;
top: -40px;
right: 0;
width: 32px;
height: 32px;
display: flex;
align-items: center;
justify-content: center;
color: rgba(255, 255, 255, 0.8);
cursor: pointer;
font-size: 24px;
transition: color 0.3s;
&:hover {
color: #fff;
}
}
//
.response-info-dialog::-webkit-scrollbar {
width: 6px;
}
.response-info-dialog::-webkit-scrollbar-track {
background: rgba(0, 0, 0, 0.2);
border-radius: 3px;
}
.response-info-dialog::-webkit-scrollbar-thumb {
background: linear-gradient(180deg, #40a9ff 0%, #1890ff 100%);
border-radius: 3px;
}
.response-info-dialog::-webkit-scrollbar-thumb:hover {
background: linear-gradient(180deg, #69c0ff 0%, #40a9ff 100%);
}
</style>

View File

@ -0,0 +1,502 @@
<template>
<div v-if="visible" class="response-dialog-overlay" @click="handleOverlayClick">
<div class="response-dialog" @click.stop>
<!-- 标题栏 -->
<div class="dialog-header">
<div class="header-title">潼南三级路长明细</div>
<div class="close-btn" @click="handleClose">
<el-icon><Close /></el-icon>
</div>
</div>
<!-- 统计数据卡片 -->
<div class="stats-cards">
<div class="stat-card">
<div class="card-icon">
<el-icon class="stat-icon"><User /></el-icon>
</div>
<div class="card-info">
<div class="card-label">路长总人数</div>
<div class="card-value">
<span class="value-num">{{ stats.total }}</span>
<span class="value-unit"></span>
</div>
</div>
</div>
<div class="stat-card">
<div class="card-icon">
<el-icon class="stat-icon"><OfficeBuilding /></el-icon>
</div>
<div class="card-info">
<div class="card-label">县级路长</div>
<div class="card-value">
<span class="value-num">{{ stats.county }}</span>
<span class="value-unit">公里</span>
</div>
</div>
</div>
<div class="stat-card">
<div class="card-icon">
<el-icon class="stat-icon"><MapLocation /></el-icon>
</div>
<div class="card-info">
<div class="card-label">村路长</div>
<div class="card-value">
<span class="value-num">{{ stats.village }}</span>
<span class="value-unit"></span>
</div>
</div>
</div>
</div>
<!-- 数据表格 -->
<div class="table-section">
<div class="table-header">
<div class="th" style="width: 60px">序号</div>
<div class="th" style="width: 120px">区县/镇街</div>
<div class="th" style="width: 100px">姓名</div>
<div class="th" style="width: 120px">电话</div>
<div class="th" style="flex: 1">角色</div>
<div class="th" style="width: 100px">职务</div>
<div class="th" style="width: 120px">操作</div>
</div>
<div class="table-body">
<div
v-for="(item, index) in tableData"
:key="item.id"
class="table-row"
:class="{ 'row-even': index % 2 === 1 }"
>
<div class="td" style="width: 60px">{{ index + 1 }}</div>
<div class="td" style="width: 120px">{{ item.district }}</div>
<div class="td" style="width: 100px">{{ item.name }}</div>
<div class="td" style="width: 120px">{{ item.phone }}</div>
<div class="td" style="flex: 1">{{ item.role }}</div>
<div class="td" style="width: 100px">{{ item.position }}</div>
<div class="td" style="width: 120px">
<div class="action-btns">
<div class="action-btn" @click="handleView(item)">
<el-icon><VideoCamera /></el-icon>
</div>
<div class="action-btn" @click="handleVoice(item)">
<el-icon><Microphone /></el-icon>
</div>
<div class="action-btn" @click="handleCall(item)">
<el-icon><Phone /></el-icon>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- 分页 -->
<div class="pagination">
<span class="total">{{ total }}条数据</span>
<div class="page-btns">
<div class="page-btn" :class="{ disabled: currentPage === 1 }" @click="prevPage">
<el-icon><ArrowLeft /></el-icon>
</div>
<div
v-for="page in visiblePages"
:key="page"
class="page-btn"
:class="{ active: currentPage === page }"
@click="goToPage(page)"
>
{{ page }}
</div>
<div class="page-btn" :class="{ disabled: currentPage === totalPages }" @click="nextPage">
<el-icon><ArrowRight /></el-icon>
</div>
</div>
</div>
</div>
</div>
</template>
<script setup>
import { ref, computed, watch } from "vue";
import { Close, VideoCamera, Microphone, Phone, ArrowLeft, ArrowRight, User, OfficeBuilding, MapLocation } from "@element-plus/icons-vue";
const props = defineProps({
visible: {
type: Boolean,
default: false,
},
});
const emit = defineEmits(["update:visible", "close"]);
//
const stats = ref({
total: 1127,
county: 216,
village: 1099,
});
//
const tableData = ref([
{ id: 1, district: "万州区柏梓镇", name: "赵海浪", phone: "1862352068", role: "一般人员(路长履职)", position: "其他" },
{ id: 2, district: "万州区柏梓镇", name: "赵海浪", phone: "1862352068", role: "一般人员(路长履职)", position: "其他" },
{ id: 3, district: "万州区柏梓镇", name: "赵海浪", phone: "1862352068", role: "一般人员(路长履职)", position: "其他" },
{ id: 4, district: "万州区柏梓镇", name: "赵海浪", phone: "1862352068", role: "一般人员(路长履职)", position: "其他" },
{ id: 5, district: "万州区李河镇", name: "王建国", phone: "1398324567", role: "一般人员(路长履职)", position: "其他" },
{ id: 6, district: "万州区李河镇", name: "王建国", phone: "1398324567", role: "一般人员(路长履职)", position: "其他" },
{ id: 7, district: "万州区李河镇", name: "王建国", phone: "1398324567", role: "一般人员(路长履职)", position: "其他" },
{ id: 8, district: "万州区李河镇", name: "王建国", phone: "1398324567", role: "一般人员(路长履职)", position: "其他" },
{ id: 9, district: "万州区分水镇", name: "刘志强", phone: "1387654321", role: "一般人员(路长履职)", position: "其他" },
{ id: 10, district: "万州区分水镇", name: "刘志强", phone: "1387654321", role: "一般人员(路长履职)", position: "其他" },
{ id: 11, district: "万州区分水镇", name: "刘志强", phone: "1387654321", role: "一般人员(路长履职)", position: "其他" },
{ id: 12, district: "万州区分水镇", name: "刘志强", phone: "1387654321", role: "一般人员(路长履职)", position: "其他" },
]);
//
const currentPage = ref(1);
const pageSize = ref(10);
const total = ref(36);
const totalPages = computed(() => Math.ceil(total.value / pageSize.value));
const visiblePages = computed(() => {
const pages = [];
const maxVisible = 4;
let start = Math.max(1, currentPage.value - Math.floor(maxVisible / 2));
let end = Math.min(totalPages.value, start + maxVisible - 1);
if (end - start + 1 < maxVisible) {
start = Math.max(1, end - maxVisible + 1);
}
for (let i = start; i <= end; i++) {
pages.push(i);
}
return pages;
});
//
const handleClose = () => {
emit("update:visible", false);
emit("close");
};
//
const handleOverlayClick = () => {
handleClose();
};
//
const prevPage = () => {
if (currentPage.value > 1) {
currentPage.value--;
fetchData();
}
};
const nextPage = () => {
if (currentPage.value < totalPages.value) {
currentPage.value++;
fetchData();
}
};
const goToPage = (page) => {
currentPage.value = page;
fetchData();
};
//
const fetchData = () => {
console.log("获取第", currentPage.value, "页数据");
// API
};
//
const handleView = (item) => {
console.log("查看视频:", item);
};
const handleVoice = (item) => {
console.log("语音通话:", item);
};
const handleCall = (item) => {
console.log("拨打电话:", item);
};
// visible
watch(
() => props.visible,
(newVal) => {
if (newVal) {
currentPage.value = 1;
fetchData();
}
}
);
</script>
<style lang="scss" scoped>
.response-dialog-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(0, 0, 0, 0.6);
display: flex;
align-items: center;
justify-content: center;
z-index: 1000;
}
.response-dialog {
width: 900px;
background: linear-gradient(135deg, rgba(20, 50, 90, 0.95) 0%, rgba(10, 30, 60, 0.98) 100%);
border: 1px solid rgba(64, 169, 255, 0.3);
border-radius: 12px;
padding: 24px;
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.4);
}
//
.dialog-header {
display: flex;
align-items: center;
justify-content: center;
position: relative;
margin-bottom: 24px;
.header-title {
font-size: 20px;
font-weight: 600;
color: #fff;
padding: 8px 40px;
background: linear-gradient(90deg, transparent 0%, rgba(64, 169, 255, 0.2) 20%, rgba(64, 169, 255, 0.2) 80%, transparent 100%);
border-bottom: 2px solid #40a9ff;
}
.close-btn {
position: absolute;
right: 0;
top: 0;
width: 32px;
height: 32px;
display: flex;
align-items: center;
justify-content: center;
color: rgba(255, 255, 255, 0.7);
cursor: pointer;
font-size: 20px;
transition: color 0.3s;
&:hover {
color: #fff;
}
}
}
//
.stats-cards {
display: flex;
gap: 20px;
margin-bottom: 24px;
.stat-card {
flex: 1;
display: flex;
align-items: center;
gap: 12px;
background: linear-gradient(135deg, rgba(30, 80, 140, 0.6) 0%, rgba(20, 60, 110, 0.8) 100%);
border: 1px solid rgba(64, 169, 255, 0.2);
border-radius: 8px;
padding: 16px 20px;
.card-icon {
width: 48px;
height: 48px;
display: flex;
align-items: center;
justify-content: center;
background-color: rgba(64, 169, 255, 0.15);
border-radius: 8px;
.stat-icon {
font-size: 28px;
color: #40a9ff;
}
}
.card-info {
.card-label {
font-size: 14px;
color: rgba(255, 255, 255, 0.7);
margin-bottom: 4px;
}
.card-value {
display: flex;
align-items: baseline;
gap: 4px;
.value-num {
font-size: 28px;
font-weight: 700;
color: #40a9ff;
text-shadow: 0 0 10px rgba(64, 169, 255, 0.5);
}
.value-unit {
font-size: 13px;
color: rgba(255, 255, 255, 0.6);
}
}
}
}
}
//
.table-section {
background-color: rgba(30, 70, 120, 0.3);
border-radius: 8px;
overflow: hidden;
margin-bottom: 20px;
.table-header {
display: flex;
background-color: rgba(64, 169, 255, 0.2);
padding: 12px 16px;
.th {
font-size: 14px;
font-weight: 500;
color: #fff;
text-align: center;
}
}
.table-body {
max-height: 240px;
overflow-y: auto;
.table-row {
display: flex;
padding: 12px 16px;
align-items: center;
transition: background-color 0.3s;
&:hover {
background-color: rgba(64, 169, 255, 0.1);
}
&.row-even {
background-color: rgba(30, 70, 120, 0.2);
}
.td {
font-size: 13px;
color: rgba(255, 255, 255, 0.85);
text-align: center;
.action-btns {
display: flex;
justify-content: center;
gap: 12px;
.action-btn {
width: 28px;
height: 28px;
display: flex;
align-items: center;
justify-content: center;
background-color: rgba(64, 169, 255, 0.15);
border-radius: 4px;
color: #40a9ff;
cursor: pointer;
transition: all 0.3s;
&:hover {
background-color: rgba(64, 169, 255, 0.3);
transform: scale(1.1);
}
}
}
}
}
}
}
//
.pagination {
display: flex;
align-items: center;
justify-content: flex-end;
gap: 16px;
.total {
font-size: 13px;
color: rgba(255, 255, 255, 0.6);
}
.page-btns {
display: flex;
gap: 8px;
.page-btn {
min-width: 32px;
height: 32px;
display: flex;
align-items: center;
justify-content: center;
background-color: rgba(64, 169, 255, 0.1);
border: 1px solid rgba(64, 169, 255, 0.2);
border-radius: 4px;
font-size: 13px;
color: rgba(255, 255, 255, 0.8);
cursor: pointer;
transition: all 0.3s;
&:hover:not(.disabled):not(.active) {
background-color: rgba(64, 169, 255, 0.2);
border-color: rgba(64, 169, 255, 0.4);
}
&.active {
background-color: #40a9ff;
border-color: #40a9ff;
color: #fff;
}
&.disabled {
opacity: 0.4;
cursor: not-allowed;
}
}
}
}
//
.table-body::-webkit-scrollbar {
width: 8px;
}
.table-body::-webkit-scrollbar-track {
background: rgba(0, 0, 0, 0.2);
border-radius: 4px;
}
.table-body::-webkit-scrollbar-thumb {
background: linear-gradient(180deg, #40a9ff 0%, #1890ff 100%);
border-radius: 4px;
border: 1px solid rgba(255, 255, 255, 0.1);
}
.table-body::-webkit-scrollbar-thumb:hover {
background: linear-gradient(180deg, #69c0ff 0%, #40a9ff 100%);
}
//
.table-body::-webkit-scrollbar-corner {
background: transparent;
}
</style>

View File

@ -0,0 +1,573 @@
<template>
<div v-if="visible" class="response-status-dialog-overlay" @click="handleOverlayClick">
<div class="response-status-dialog" @click.stop>
<!-- 标题栏 -->
<div class="dialog-header">
<div class="header-title">响应情况</div>
<div class="close-btn" @click="handleClose">
<el-icon><Close /></el-icon>
</div>
</div>
<!-- 筛选区域 -->
<div class="filter-section">
<div class="filter-row">
<div class="filter-item">
<el-select v-model="filterForm.pointType" placeholder="影响点类型" class="filter-select">
<el-option label="全部" value="" />
<el-option label="边坡" value="slope" />
<el-option label="桥梁" value="bridge" />
<el-option label="隧道" value="tunnel" />
<el-option label="路面" value="road" />
</el-select>
</div>
<div class="filter-item">
<el-select v-model="filterForm.pointLevel" placeholder="影响点等级" class="filter-select">
<el-option label="全部" value="" />
<el-option label="一般隐患" value="normal" />
<el-option label="重大隐患" value="serious" />
</el-select>
</div>
<div class="filter-item">
<el-select v-model="filterForm.isResponded" placeholder="是否回应" class="filter-select">
<el-option label="全部" value="" />
<el-option label="是" value="yes" />
<el-option label="否" value="no" />
</el-select>
</div>
</div>
</div>
<!-- 数据表格 -->
<div class="table-section">
<div class="table-header">
<div class="th" style="width: 50px">序号</div>
<div class="th" style="width: 80px">影响点类型</div>
<div class="th" style="width: 180px">影响点位置</div>
<div class="th" style="width: 90px">影响点等级</div>
<div class="th" style="width: 60px">查次数</div>
<div class="th" style="width: 120px">交通主管部门负责人</div>
<div class="th" style="width: 110px">公路机构责任人</div>
<div class="th" style="width: 110px">养护站负责人</div>
<div class="th" style="width: 80px">护路员</div>
<div class="th" style="width: 70px">回应状态</div>
<div class="th" style="width: 110px">最新催告时间</div>
<div class="th" style="width: 50px">操作</div>
</div>
<div class="table-body">
<div
v-for="(item, index) in tableData"
:key="item.id"
class="table-row"
:class="{ 'row-even': index % 2 === 1 }"
>
<div class="td" style="width: 50px">{{ item.id }}</div>
<div class="td" style="width: 80px">{{ item.pointType }}</div>
<div class="td" style="width: 180px">{{ item.pointLocation }}</div>
<div class="td" style="width: 90px">
<span class="level-tag" :class="item.levelClass">{{ item.pointLevel }}</span>
</div>
<div class="td" style="width: 60px">{{ item.checkCount }}</div>
<div class="td" style="width: 120px">
<div class="person-info">
<span class="person-name">{{ item.trafficDept.name }}</span>
<span class="person-phone">{{ item.trafficDept.phone }}</span>
</div>
</div>
<div class="td" style="width: 110px">
<div class="person-info">
<span class="person-name">{{ item.roadOrg.name }}</span>
<span class="person-phone">{{ item.roadOrg.phone }}</span>
</div>
</div>
<div class="td" style="width: 110px">
<div class="person-info">
<span class="person-name">{{ item.maintenance.name }}</span>
<span class="person-phone">{{ item.maintenance.phone }}</span>
</div>
</div>
<div class="td" style="width: 80px">
<div class="person-info">
<span class="person-name">{{ item.roadKeeper.name }}</span>
<span class="person-phone">{{ item.roadKeeper.phone }}</span>
</div>
</div>
<div class="td" style="width: 70px">
<span class="response-status" :class="item.responseClass">{{ item.responseStatus }}</span>
</div>
<div class="td" style="width: 110px">
<div class="time-info">
<span class="time-date">{{ item.urgeTime.date }}</span>
<span class="time-clock">{{ item.urgeTime.time }}</span>
</div>
</div>
<div class="td" style="width: 50px">
<span class="detail-link" @click="handleDetail(item)">详情</span>
</div>
</div>
</div>
</div>
<!-- 分页 -->
<div class="pagination">
<span class="total">{{ total }}条数据</span>
<div class="page-btns">
<div class="page-btn" :class="{ disabled: currentPage === 1 }" @click="prevPage">
<el-icon><ArrowLeft /></el-icon>
</div>
<div
v-for="page in visiblePages"
:key="page"
class="page-btn"
:class="{ active: currentPage === page }"
@click="goToPage(page)"
>
{{ page }}
</div>
<div class="page-btn" :class="{ disabled: currentPage === totalPages }" @click="nextPage">
<el-icon><ArrowRight /></el-icon>
</div>
</div>
</div>
</div>
</div>
</template>
<script setup>
import { ref, computed, watch } from "vue";
import { Close, ArrowLeft, ArrowRight } from "@element-plus/icons-vue";
const props = defineProps({
visible: {
type: Boolean,
default: false,
},
});
const emit = defineEmits(["update:visible", "close", "detail"]);
//
const filterForm = ref({
pointType: "",
pointLevel: "",
isResponded: "",
});
//
const tableData = ref([
{
id: 1,
pointType: "边坡",
pointLocation: "武汉-大理(K1452+951至K1462+209)",
pointLevel: "一般隐患",
levelClass: "level-normal",
checkCount: 2,
trafficDept: { name: "罗宸", phone: "17623865172" },
roadOrg: { name: "李海平", phone: "1372386532" },
maintenance: { name: "苏祖兵", phone: "13594331090" },
roadKeeper: { name: "凌承礼", phone: "1592393704" },
responseStatus: "已回应",
responseClass: "status-responded",
urgeTime: { date: "2026-03-28", time: "12:30:00" },
},
{
id: 12,
pointType: "边坡",
pointLocation: "武汉-大理(K1452+951至K1462+209)",
pointLevel: "一般隐患",
levelClass: "level-normal",
checkCount: 2,
trafficDept: { name: "罗宸", phone: "17623865172" },
roadOrg: { name: "李海平", phone: "1372386532" },
maintenance: { name: "苏祖兵", phone: "13594331090" },
roadKeeper: { name: "凌承礼", phone: "1592393704" },
responseStatus: "已回应",
responseClass: "status-responded",
urgeTime: { date: "2026-03-28", time: "12:30:00" },
},
]);
//
const currentPage = ref(1);
const pageSize = ref(10);
const total = ref(36);
const totalPages = computed(() => Math.ceil(total.value / pageSize.value));
const visiblePages = computed(() => {
const pages = [];
const maxVisible = 4;
let start = Math.max(1, currentPage.value - Math.floor(maxVisible / 2));
let end = Math.min(totalPages.value, start + maxVisible - 1);
if (end - start + 1 < maxVisible) {
start = Math.max(1, end - maxVisible + 1);
}
for (let i = start; i <= end; i++) {
pages.push(i);
}
return pages;
});
//
const handleClose = () => {
emit("update:visible", false);
emit("close");
};
//
const handleOverlayClick = () => {
handleClose();
};
//
const handleDetail = (item) => {
emit("detail", item);
};
//
const prevPage = () => {
if (currentPage.value > 1) {
currentPage.value--;
fetchData();
}
};
const nextPage = () => {
if (currentPage.value < totalPages.value) {
currentPage.value++;
fetchData();
}
};
const goToPage = (page) => {
currentPage.value = page;
fetchData();
};
//
const fetchData = () => {
console.log("获取第", currentPage.value, "页数据");
// API
};
// visible
watch(
() => props.visible,
(newVal) => {
if (newVal) {
currentPage.value = 1;
fetchData();
}
}
);
</script>
<style lang="scss" scoped>
.response-status-dialog-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(0, 0, 0, 0.6);
display: flex;
align-items: center;
justify-content: center;
z-index: 1000;
}
.response-status-dialog {
width: 1150px;
background: linear-gradient(135deg, rgba(20, 50, 90, 0.95) 0%, rgba(10, 30, 60, 0.98) 100%);
border: 1px solid rgba(64, 169, 255, 0.3);
border-radius: 12px;
padding: 24px;
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.4);
}
//
.dialog-header {
display: flex;
align-items: center;
justify-content: center;
position: relative;
margin-bottom: 20px;
.header-title {
font-size: 20px;
font-weight: 600;
color: #fff;
padding: 8px 40px;
background: linear-gradient(90deg, transparent 0%, rgba(64, 169, 255, 0.2) 20%, rgba(64, 169, 255, 0.2) 80%, transparent 100%);
border-bottom: 2px solid #40a9ff;
}
.close-btn {
position: absolute;
right: 0;
top: 0;
width: 32px;
height: 32px;
display: flex;
align-items: center;
justify-content: center;
color: rgba(255, 255, 255, 0.7);
cursor: pointer;
font-size: 20px;
transition: color 0.3s;
&:hover {
color: #fff;
}
}
}
//
.filter-section {
margin-bottom: 20px;
.filter-row {
display: flex;
align-items: center;
gap: 12px;
}
.filter-item {
.filter-select {
width: 150px;
:deep(.el-input__wrapper) {
background-color: rgba(30, 70, 120, 0.4);
border: 1px solid rgba(64, 169, 255, 0.3);
box-shadow: none;
border-radius: 4px;
.el-input__inner {
color: #fff;
font-size: 13px;
&::placeholder {
color: rgba(255, 255, 255, 0.5);
}
}
.el-input__suffix {
.el-icon {
color: rgba(255, 255, 255, 0.6);
}
}
}
}
}
}
//
.table-section {
background-color: rgba(30, 70, 120, 0.3);
border-radius: 8px;
overflow: hidden;
margin-bottom: 20px;
.table-header {
display: flex;
background-color: rgba(64, 169, 255, 0.2);
padding: 12px 16px;
.th {
font-size: 13px;
font-weight: 500;
color: #fff;
text-align: center;
}
}
.table-body {
max-height: 200px;
overflow-y: auto;
.table-row {
display: flex;
padding: 12px 16px;
align-items: center;
transition: background-color 0.3s;
&:hover {
background-color: rgba(64, 169, 255, 0.1);
}
&.row-even {
background-color: rgba(30, 70, 120, 0.2);
}
.td {
font-size: 12px;
color: rgba(255, 255, 255, 0.85);
text-align: center;
.level-tag {
display: inline-block;
padding: 2px 8px;
border-radius: 4px;
font-size: 11px;
&.level-normal {
background-color: rgba(250, 219, 95, 0.2);
color: #fadb5f;
border: 1px solid rgba(250, 219, 95, 0.4);
}
&.level-serious {
background-color: rgba(255, 77, 79, 0.2);
color: #ff4d4f;
border: 1px solid rgba(255, 77, 79, 0.4);
}
}
.person-info {
display: flex;
flex-direction: column;
gap: 2px;
.person-name {
font-size: 12px;
color: rgba(255, 255, 255, 0.9);
}
.person-phone {
font-size: 11px;
color: rgba(255, 255, 255, 0.6);
}
}
.response-status {
font-size: 12px;
&.status-responded {
color: #52c41a;
}
&.status-unresponded {
color: #ff7a45;
}
}
.time-info {
display: flex;
flex-direction: column;
gap: 2px;
.time-date {
font-size: 12px;
color: rgba(255, 255, 255, 0.9);
}
.time-clock {
font-size: 11px;
color: rgba(255, 255, 255, 0.6);
}
}
.detail-link {
color: #40a9ff;
cursor: pointer;
font-size: 12px;
&:hover {
color: #69c0ff;
text-decoration: underline;
}
}
}
}
}
}
//
.pagination {
display: flex;
align-items: center;
justify-content: flex-end;
gap: 16px;
.total {
font-size: 13px;
color: rgba(255, 255, 255, 0.6);
}
.page-btns {
display: flex;
gap: 8px;
.page-btn {
min-width: 28px;
height: 28px;
display: flex;
align-items: center;
justify-content: center;
background-color: rgba(64, 169, 255, 0.1);
border: 1px solid rgba(64, 169, 255, 0.2);
border-radius: 4px;
font-size: 12px;
color: rgba(255, 255, 255, 0.8);
cursor: pointer;
transition: all 0.3s;
&:hover:not(.disabled):not(.active) {
background-color: rgba(64, 169, 255, 0.2);
border-color: rgba(64, 169, 255, 0.4);
}
&.active {
background-color: #40a9ff;
border-color: #40a9ff;
color: #fff;
}
&.disabled {
opacity: 0.4;
cursor: not-allowed;
}
}
}
}
//
.table-body::-webkit-scrollbar {
width: 6px;
}
.table-body::-webkit-scrollbar-track {
background: rgba(0, 0, 0, 0.2);
border-radius: 3px;
}
.table-body::-webkit-scrollbar-thumb {
background: linear-gradient(180deg, #40a9ff 0%, #1890ff 100%);
border-radius: 3px;
}
.table-body::-webkit-scrollbar-thumb:hover {
background: linear-gradient(180deg, #69c0ff 0%, #40a9ff 100%);
}
//
:deep(.el-select-dropdown) {
background-color: rgba(20, 50, 90, 0.98);
border: 1px solid rgba(64, 169, 255, 0.3);
.el-select-dropdown__item {
color: rgba(255, 255, 255, 0.85);
&:hover {
background-color: rgba(64, 169, 255, 0.2);
}
&.selected {
background-color: rgba(64, 169, 255, 0.3);
color: #40a9ff;
}
}
}
</style>

View File

@ -0,0 +1,784 @@
<template>
<div v-if="visible" class="risk-dialog-overlay" @click="handleOverlayClick">
<div class="risk-dialog" @click.stop>
<!-- 标题栏 -->
<div class="dialog-header">
<div class="header-title">影响点详情</div>
<div class="close-btn" @click="handleClose">
<el-icon><Close /></el-icon>
</div>
</div>
<!-- 基本信息 -->
<div class="section">
<div class="section-title">
<span class="title-icon"></span>
基本信息
</div>
<div class="basic-info">
<div class="info-row three-col">
<div class="info-item">
<span class="info-label">所属区县</span>
<span class="info-value">{{ basicInfo.district }}</span>
</div>
<div class="info-item">
<span class="info-label">公路编号</span>
<span class="info-value">{{ basicInfo.roadCode }}</span>
</div>
<div class="info-item">
<span class="info-label">风险点类型</span>
<span class="info-value">{{ basicInfo.riskType }}</span>
</div>
</div>
<div class="info-row three-col">
<div class="info-item">
<span class="info-label">风险点位置</span>
<span class="info-value">{{ basicInfo.riskLocation }}</span>
</div>
<div class="info-item">
<span class="info-label">回应状态</span>
<span class="info-value" :class="getStatusClass(basicInfo.responseStatus)">{{ basicInfo.responseStatus }}</span>
</div>
<div class="info-item">
<span class="info-label">审核状态</span>
<span class="info-value" :class="getAuditClass(basicInfo.auditStatus)">{{ basicInfo.auditStatus }}</span>
</div>
</div>
<div class="info-row three-col">
<div class="info-item">
<span class="info-label">预警等级</span>
<span class="info-value level-red">{{ basicInfo.warningLevel }}</span>
</div>
<div class="info-item">
<span class="info-label">起点桩号-止点桩号</span>
<span class="info-value">{{ basicInfo.stakeRange }}</span>
</div>
<div class="info-item">
<span class="info-label">发现时间</span>
<span class="info-value">{{ basicInfo.discoverTime }}</span>
</div>
</div>
<div class="info-row full-width">
<div class="info-item">
<span class="info-label">预警内容</span>
<span class="info-value">{{ basicInfo.warningContent }}</span>
</div>
</div>
</div>
</div>
<!-- 照片 -->
<div class="section">
<div class="section-title">
<span class="title-icon"></span>
照片
</div>
<div class="photo-list">
<div
v-for="(photo, index) in photoList"
:key="index"
class="photo-item"
@click="previewImage(photo)"
>
<img :src="photo" alt="照片" />
</div>
</div>
</div>
<!-- 三级包保责任人 -->
<div class="section">
<div class="section-title">
<span class="title-icon"></span>
三级包保责任人
</div>
<div class="responsible-table">
<div class="table-header">
<div class="th" style="width: 30%">责任人类型</div>
<div class="th" style="width: 40%">责任人</div>
<div class="th" style="width: 30%">巡查频率</div>
</div>
<div class="table-body">
<div
v-for="(item, index) in responsibleList"
:key="index"
class="table-row"
:class="{ 'row-even': index % 2 === 1 }"
>
<div class="td" style="width: 30%">{{ item.type }}</div>
<div class="td" style="width: 40%">{{ item.name }} {{ item.phone }}</div>
<div class="td" style="width: 30%">{{ item.frequency }}</div>
</div>
</div>
</div>
</div>
<!-- 填报动态信息 -->
<div class="section">
<div class="section-title">
<span class="title-icon"></span>
填报动态信息
</div>
<div class="timeline-list">
<!-- 巡查记录 -->
<div class="timeline-item">
<div class="timeline-marker patrol"></div>
<div class="timeline-content">
<div class="timeline-header">
<span class="timeline-type">巡查记录</span>
<span class="timeline-person">{{ patrolRecord.person }}</span>
<span class="timeline-time">{{ patrolRecord.time }}</span>
</div>
<div class="timeline-detail">
<div class="detail-row">
<span class="detail-label">巡查轨迹</span>
<span class="detail-link" @click="viewTrack">查看轨迹</span>
</div>
<div class="detail-row">
<span class="detail-label">现场情况</span>
<span class="detail-text">{{ patrolRecord.situation }}</span>
<div v-if="patrolRecord.image" class="detail-image" @click="previewImage(patrolRecord.image)">
<img :src="patrolRecord.image" alt="现场照片" />
</div>
</div>
</div>
</div>
</div>
<!-- 动态记录列表 -->
<div v-for="(record, index) in dynamicRecords" :key="index" class="timeline-item">
<div class="timeline-marker" :class="record.type"></div>
<div class="timeline-content">
<div class="timeline-header">
<span class="timeline-type">{{ record.typeName }}</span>
<span class="timeline-person">{{ record.person }}</span>
<span class="timeline-time">{{ record.time }}</span>
<span v-if="record.status" class="timeline-status" :class="record.statusClass">{{ record.status }}</span>
<span v-if="record.target" class="timeline-target">
<span class="target-name">{{ record.target }}</span> {{ record.targetPhone }}
</span>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- 图片预览弹窗 -->
<div v-if="previewVisible" class="image-preview-overlay" @click="closePreview">
<div class="image-preview-container" @click.stop>
<img :src="previewImageUrl" alt="预览" />
<div class="close-preview-btn" @click="closePreview">
<el-icon><Close /></el-icon>
</div>
</div>
</div>
</div>
</template>
<script setup>
import { ref, watch } from "vue";
import { Close } from "@element-plus/icons-vue";
const props = defineProps({
visible: {
type: Boolean,
default: false,
},
riskData: {
type: Object,
default: () => ({}),
},
});
const emit = defineEmits(["update:visible", "close", "viewTrack"]);
//
const basicInfo = ref({
district: "合川区",
roadCode: "G542",
riskType: "风险路段",
riskLocation: "丁吴路(K116+656至K116+739)",
responseStatus: "未回应",
auditStatus: "未审核",
warningLevel: "红色预警",
stakeRange: "三级治理中心发布发布暴雨红色预警信号",
discoverTime: "立即启动防汛Ⅰ级应急响应,立即转移危险区群众,医疗机构做好应急准备",
warningContent: "",
});
//
const photoList = ref([
"https://via.placeholder.com/120x80/40a9ff/ffffff?text=照片1",
"https://via.placeholder.com/120x80/40a9ff/ffffff?text=照片2",
"https://via.placeholder.com/120x80/40a9ff/ffffff?text=照片3",
"https://via.placeholder.com/120x80/40a9ff/ffffff?text=照片4",
]);
//
const responsibleList = ref([
{ type: "交通主管部门负责人", name: "胡雷", phone: "18983923577", frequency: "半年巡查一次" },
{ type: "公路机构负责人", name: "刘孝万", phone: "13609403931", frequency: "每月巡查一次" },
{ type: "养护站负责人", name: "彭应成", phone: "18323031454", frequency: "每周巡查一次" },
{ type: "护路员", name: "蒋汉成", phone: "1870230796", frequency: "每周巡查两次" },
]);
//
const patrolRecord = ref({
person: "蒋汉成 18702307964",
time: "2025-10-14 15:43:24",
situation: "收到暴雨黄色预警信息,开展公路夜间巡查排查,道路滑坡涉灾点,无明显变化",
image: "https://via.placeholder.com/80x60/40a9ff/ffffff?text=现场",
});
//
const dynamicRecords = ref([
{
type: "dispatch",
typeName: "调度记录",
person: "蒋汉成",
time: "2025-10-13 15:43:24",
status: "【已接通语音】",
statusClass: "status-success",
target: "养护站负责人",
targetPhone: "刘孝万13609403931",
},
{
type: "warning",
typeName: "预警记录",
person: "蒋汉成18702307964",
time: "2025-10-13 15:43:24",
status: "审核驳回",
statusClass: "status-reject",
},
{
type: "warning",
typeName: "预警记录",
person: "蒋汉成18702307964",
time: "2025-10-13 15:43:24",
status: "审核通过",
statusClass: "status-success",
},
{
type: "warning",
typeName: "预警记录",
person: "蒋汉成18702307964",
time: "2025-10-13 15:43:24",
status: "响应预警",
statusClass: "status-success",
},
]);
//
const getStatusClass = (status) => {
if (status === "未回应") return "status-unresponse";
if (status === "已回应") return "status-response";
return "";
};
const getAuditClass = (status) => {
if (status === "未审核") return "status-unaudit";
if (status === "审核通过") return "status-pass";
if (status === "审核驳回") return "status-reject";
return "";
};
//
const previewVisible = ref(false);
const previewImageUrl = ref("");
const previewImage = (url) => {
previewImageUrl.value = url;
previewVisible.value = true;
};
const closePreview = () => {
previewVisible.value = false;
previewImageUrl.value = "";
};
//
const viewTrack = () => {
emit("viewTrack", patrolRecord.value);
};
//
const handleClose = () => {
emit("update:visible", false);
emit("close");
};
//
const handleOverlayClick = () => {
handleClose();
};
// visible
watch(
() => props.visible,
(newVal) => {
if (newVal && props.riskData) {
Object.assign(basicInfo.value, props.riskData);
}
}
);
</script>
<style lang="scss" scoped>
.risk-dialog-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(0, 0, 0, 0.6);
display: flex;
align-items: center;
justify-content: center;
z-index: 1000;
}
.risk-dialog {
width: 800px;
max-height: 90vh;
overflow-y: auto;
background: linear-gradient(135deg, rgba(20, 50, 90, 0.95) 0%, rgba(10, 30, 60, 0.98) 100%);
border: 1px solid rgba(64, 169, 255, 0.3);
border-radius: 12px;
padding: 24px;
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.4);
}
//
.dialog-header {
display: flex;
align-items: center;
justify-content: center;
position: relative;
margin-bottom: 24px;
.header-title {
font-size: 20px;
font-weight: 600;
color: #fff;
padding: 8px 40px;
background: linear-gradient(90deg, transparent 0%, rgba(64, 169, 255, 0.2) 20%, rgba(64, 169, 255, 0.2) 80%, transparent 100%);
border-bottom: 2px solid #40a9ff;
}
.close-btn {
position: absolute;
right: 0;
top: 0;
width: 32px;
height: 32px;
display: flex;
align-items: center;
justify-content: center;
color: rgba(255, 255, 255, 0.7);
cursor: pointer;
font-size: 20px;
transition: color 0.3s;
&:hover {
color: #fff;
}
}
}
//
.section {
margin-bottom: 20px;
&:last-child {
margin-bottom: 0;
}
}
//
.section-title {
font-size: 15px;
font-weight: 600;
color: #fff;
margin-bottom: 12px;
display: flex;
align-items: center;
.title-icon {
color: #40a9ff;
margin-right: 6px;
}
}
//
.basic-info {
background-color: rgba(30, 70, 120, 0.3);
border-radius: 8px;
padding: 16px 20px;
}
.info-row {
display: flex;
margin-bottom: 10px;
&:last-child {
margin-bottom: 0;
}
&.three-col {
.info-item {
flex: 1;
}
}
&.full-width {
.info-item {
flex: 1;
}
}
}
.info-item {
display: flex;
flex-direction: column;
gap: 4px;
}
.info-label {
font-size: 12px;
color: rgba(255, 255, 255, 0.5);
}
.info-value {
font-size: 13px;
color: rgba(255, 255, 255, 0.9);
line-height: 1.4;
&.level-red {
color: #ff4d4f;
font-weight: 500;
}
&.status-unresponse {
color: #ff7a45;
}
&.status-response {
color: #52c41a;
}
&.status-unaudit {
color: #ff4d4f;
}
&.status-pass {
color: #52c41a;
}
&.status-reject {
color: #ff4d4f;
}
}
//
.photo-list {
display: flex;
gap: 12px;
flex-wrap: wrap;
}
.photo-item {
width: 120px;
height: 80px;
border-radius: 6px;
overflow: hidden;
cursor: pointer;
border: 1px solid rgba(64, 169, 255, 0.3);
transition: all 0.3s;
&:hover {
border-color: #40a9ff;
transform: scale(1.05);
}
img {
width: 100%;
height: 100%;
object-fit: cover;
}
}
//
.responsible-table {
background-color: rgba(30, 70, 120, 0.3);
border-radius: 8px;
overflow: hidden;
.table-header {
display: flex;
background-color: rgba(64, 169, 255, 0.2);
padding: 10px 16px;
.th {
font-size: 13px;
font-weight: 500;
color: #fff;
text-align: center;
}
}
.table-body {
.table-row {
display: flex;
padding: 10px 16px;
align-items: center;
transition: background-color 0.3s;
&:hover {
background-color: rgba(64, 169, 255, 0.1);
}
&.row-even {
background-color: rgba(30, 70, 120, 0.2);
}
.td {
font-size: 12px;
color: rgba(255, 255, 255, 0.85);
text-align: center;
}
}
}
}
//
.timeline-list {
display: flex;
flex-direction: column;
gap: 12px;
}
.timeline-item {
display: flex;
gap: 12px;
position: relative;
}
.timeline-marker {
width: 12px;
height: 12px;
border-radius: 50%;
background-color: #40a9ff;
flex-shrink: 0;
margin-top: 4px;
position: relative;
&::after {
content: '';
position: absolute;
top: 12px;
left: 50%;
transform: translateX(-50%);
width: 2px;
height: calc(100% + 12px);
background-color: rgba(64, 169, 255, 0.3);
}
&.patrol {
background-color: #40a9ff;
}
&.dispatch {
background-color: #faad14;
}
&.warning {
background-color: #ff4d4f;
}
}
.timeline-item:last-child .timeline-marker::after {
display: none;
}
.timeline-content {
flex: 1;
padding-bottom: 12px;
}
.timeline-header {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 8px;
margin-bottom: 8px;
}
.timeline-type {
font-size: 13px;
color: #40a9ff;
font-weight: 500;
}
.timeline-person {
font-size: 13px;
color: rgba(255, 255, 255, 0.9);
}
.timeline-time {
font-size: 12px;
color: rgba(255, 255, 255, 0.6);
}
.timeline-status {
font-size: 12px;
padding: 2px 8px;
border-radius: 4px;
&.status-success {
color: #52c41a;
}
&.status-reject {
color: #ff4d4f;
}
}
.timeline-target {
font-size: 12px;
color: rgba(255, 255, 255, 0.7);
.target-name {
color: #40a9ff;
font-weight: 500;
}
}
//
.timeline-detail {
background-color: rgba(30, 70, 120, 0.3);
border-radius: 6px;
padding: 12px;
margin-top: 8px;
}
.detail-row {
display: flex;
align-items: flex-start;
gap: 8px;
margin-bottom: 8px;
&:last-child {
margin-bottom: 0;
}
}
.detail-label {
font-size: 12px;
color: rgba(255, 255, 255, 0.5);
white-space: nowrap;
}
.detail-text {
font-size: 12px;
color: rgba(255, 255, 255, 0.85);
flex: 1;
line-height: 1.5;
}
.detail-link {
font-size: 12px;
color: #40a9ff;
cursor: pointer;
text-decoration: underline;
&:hover {
color: #69c0ff;
}
}
.detail-image {
width: 80px;
height: 60px;
border-radius: 4px;
overflow: hidden;
cursor: pointer;
border: 1px solid rgba(64, 169, 255, 0.3);
margin-left: 12px;
flex-shrink: 0;
&:hover {
border-color: #40a9ff;
}
img {
width: 100%;
height: 100%;
object-fit: cover;
}
}
//
.image-preview-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(0, 0, 0, 0.85);
display: flex;
align-items: center;
justify-content: center;
z-index: 1100;
}
.image-preview-container {
position: relative;
max-width: 80%;
max-height: 80%;
img {
max-width: 100%;
max-height: 80vh;
border-radius: 8px;
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.5);
}
}
.close-preview-btn {
position: absolute;
top: -40px;
right: 0;
width: 32px;
height: 32px;
display: flex;
align-items: center;
justify-content: center;
color: rgba(255, 255, 255, 0.8);
cursor: pointer;
font-size: 24px;
transition: color 0.3s;
&:hover {
color: #fff;
}
}
//
.risk-dialog::-webkit-scrollbar {
width: 6px;
}
.risk-dialog::-webkit-scrollbar-track {
background: rgba(0, 0, 0, 0.2);
border-radius: 3px;
}
.risk-dialog::-webkit-scrollbar-thumb {
background: linear-gradient(180deg, #40a9ff 0%, #1890ff 100%);
border-radius: 3px;
}
.risk-dialog::-webkit-scrollbar-thumb:hover {
background: linear-gradient(180deg, #69c0ff 0%, #40a9ff 100%);
}
</style>

View File

@ -28,7 +28,6 @@ defineProps({
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;
margin-bottom: 20px;
background-image: url(v-bind(bgImg)); background-image: url(v-bind(bgImg));
background-size: cover; background-size: cover;
background-position: left; background-position: left;
@ -39,7 +38,7 @@ defineProps({
gap: 8px; gap: 8px;
.title { .title {
margin-left: 35px; margin-left: 45px;
font-size: 18px; font-size: 18px;
font-weight: bold; font-weight: bold;
color: #fff; color: #fff;

View File

@ -1,13 +0,0 @@
<template>
<div>
</div>
</template>
<script setup>
</script>
<style lang="scss" scoped>
</style>

View File

@ -0,0 +1,438 @@
<template>
<div v-if="visible" class="tongnan-dialog-overlay" @click="handleOverlayClick">
<div class="tongnan-dialog" @click.stop>
<!-- 标题栏 -->
<div class="dialog-header">
<div class="header-title">潼南基本信息表</div>
<div class="close-btn" @click="handleClose">
<el-icon><Close /></el-icon>
</div>
</div>
<!-- 数据表格 -->
<div class="table-section">
<div class="table-header">
<div class="th" style="width: 60px">序号</div>
<div class="th" style="width: 140px">区县/镇街</div>
<div class="th" style="width: 100px">姓名</div>
<div class="th" style="width: 120px">电话</div>
<div class="th" style="width: 180px">驻地名称</div>
<div class="th" style="flex: 1">类型</div>
<div class="th" style="width: 140px">调度</div>
</div>
<div class="table-body">
<div
v-for="(item, index) in tableData"
:key="item.id"
class="table-row"
:class="{ 'row-even': index % 2 === 1 }"
>
<div class="td" style="width: 60px">{{ item.id }}</div>
<div class="td" style="width: 140px">{{ item.region }}</div>
<div class="td" style="width: 100px">{{ item.name }}</div>
<div class="td" style="width: 120px">{{ item.phone }}</div>
<div class="td" style="width: 180px">
<el-tooltip :content="item.stationName" placement="top" :show-after="500">
<span class="station-name-text" @click="handleStationNameClick(item)">{{ item.stationName }}</span>
</el-tooltip>
</div>
<div class="td" style="flex: 1">{{ item.type }}</div>
<div class="td" style="width: 140px">
<div class="action-btns">
<div class="action-btn" @click="handleVideo(item)" title="视频">
<el-icon><VideoCamera /></el-icon>
</div>
<div class="action-btn" @click="handleVoice(item)" title="语音">
<el-icon><Microphone /></el-icon>
</div>
<div class="action-btn" @click="handleCall(item)" title="电话">
<el-icon><Phone /></el-icon>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- 分页 -->
<div class="pagination">
<span class="total">{{ total }}条数据</span>
<div class="page-btns">
<div class="page-btn" :class="{ disabled: currentPage === 1 }" @click="prevPage">
<el-icon><ArrowLeft /></el-icon>
</div>
<div
v-for="page in visiblePages"
:key="page"
class="page-btn"
:class="{ active: currentPage === page }"
@click="goToPage(page)"
>
{{ page }}
</div>
<div class="page-btn" :class="{ disabled: currentPage === totalPages }" @click="nextPage">
<el-icon><ArrowRight /></el-icon>
</div>
</div>
</div>
</div>
</div>
</template>
<script setup>
import { ref, computed, watch } from "vue";
import { Close, VideoCamera, Microphone, Phone, ArrowLeft, ArrowRight } from "@element-plus/icons-vue";
const props = defineProps({
visible: {
type: Boolean,
default: false,
},
});
const emit = defineEmits(["update:visible", "close", "video", "voice", "call", "stationNameClick"]);
//
const tableData = ref([
{
id: 1,
region: "沙坪坝区",
name: "赵海浪",
phone: "1862352068",
stationName: "沙坪坝区S545茅山峡公路桥新建工程渝黔铁路扩能改造工程项目经理部",
type: "交通主管部门",
},
{
id: 2,
region: "沙坪坝区",
name: "府效能",
phone: "1862352068",
stationName: "沙坪坝区S545茅山峡公路桥新建工程渝黔铁路扩能改造工程项目经理部",
type: "公路机构",
},
{
id: 3,
region: "万州区柏梓镇",
name: "王鑫",
phone: "1862352068",
stationName: "万州区项目经理部",
type: "公路机构",
},
{
id: 4,
region: "万州区柏梓镇",
name: "王鑫",
phone: "1862352068",
stationName: "万州区项目经理部",
type: "公路机构",
},
]);
//
const currentPage = ref(1);
const pageSize = ref(10);
const total = ref(36);
const totalPages = computed(() => Math.ceil(total.value / pageSize.value));
const visiblePages = computed(() => {
const pages = [];
const maxVisible = 4;
let start = Math.max(1, currentPage.value - Math.floor(maxVisible / 2));
let end = Math.min(totalPages.value, start + maxVisible - 1);
if (end - start + 1 < maxVisible) {
start = Math.max(1, end - maxVisible + 1);
}
for (let i = start; i <= end; i++) {
pages.push(i);
}
return pages;
});
//
const handleClose = () => {
emit("update:visible", false);
emit("close");
};
//
const handleOverlayClick = () => {
handleClose();
};
//
const handleVideo = (item) => {
emit("video", item);
};
const handleVoice = (item) => {
emit("voice", item);
};
const handleCall = (item) => {
emit("call", item);
};
//
const handleStationNameClick = (item) => {
emit("stationNameClick", item);
};
//
const prevPage = () => {
if (currentPage.value > 1) {
currentPage.value--;
fetchData();
}
};
const nextPage = () => {
if (currentPage.value < totalPages.value) {
currentPage.value++;
fetchData();
}
};
const goToPage = (page) => {
currentPage.value = page;
fetchData();
};
//
const fetchData = () => {
console.log("获取第", currentPage.value, "页数据");
// API
};
// visible
watch(
() => props.visible,
(newVal) => {
if (newVal) {
currentPage.value = 1;
fetchData();
}
}
);
</script>
<style lang="scss" scoped>
.tongnan-dialog-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(0, 0, 0, 0.6);
display: flex;
align-items: center;
justify-content: center;
z-index: 2100;
}
.tongnan-dialog {
width: 1000px;
background: linear-gradient(135deg, rgba(20, 50, 90, 0.95) 0%, rgba(10, 30, 60, 0.98) 100%);
border: 1px solid rgba(64, 169, 255, 0.3);
border-radius: 12px;
padding: 24px;
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.4);
}
//
.dialog-header {
display: flex;
align-items: center;
justify-content: center;
position: relative;
margin-bottom: 20px;
.header-title {
font-size: 20px;
font-weight: 600;
color: #fff;
padding: 8px 40px;
background: linear-gradient(90deg, transparent 0%, rgba(64, 169, 255, 0.2) 20%, rgba(64, 169, 255, 0.2) 80%, transparent 100%);
border-bottom: 2px solid #40a9ff;
}
.close-btn {
position: absolute;
right: 0;
top: 0;
width: 32px;
height: 32px;
display: flex;
align-items: center;
justify-content: center;
color: rgba(255, 255, 255, 0.7);
cursor: pointer;
font-size: 20px;
transition: color 0.3s;
&:hover {
color: #fff;
}
}
}
//
.table-section {
background-color: rgba(30, 70, 120, 0.3);
border-radius: 8px;
overflow: hidden;
margin-bottom: 20px;
.table-header {
display: flex;
background-color: rgba(64, 169, 255, 0.2);
padding: 12px 16px;
.th {
font-size: 14px;
font-weight: 500;
color: #fff;
text-align: center;
}
}
.table-body {
max-height: 320px;
overflow-y: auto;
.table-row {
display: flex;
padding: 14px 16px;
align-items: center;
transition: background-color 0.3s;
&:hover {
background-color: rgba(64, 169, 255, 0.1);
}
&.row-even {
background-color: rgba(30, 70, 120, 0.2);
}
.td {
font-size: 13px;
color: rgba(255, 255, 255, 0.85);
text-align: center;
.station-name-text {
display: block;
cursor: pointer;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
max-width: 180px;
color: #40a9ff;
transition: all 0.3s;
&:hover {
color: #69c0ff;
text-shadow: 0 0 8px rgba(105, 192, 255, 0.6);
}
}
.action-btns {
display: flex;
justify-content: center;
gap: 16px;
.action-btn {
width: 28px;
height: 28px;
display: flex;
align-items: center;
justify-content: center;
color: rgba(255, 255, 255, 0.7);
cursor: pointer;
font-size: 18px;
transition: all 0.3s;
&:hover {
color: #40a9ff;
transform: scale(1.1);
}
.el-icon {
font-size: 18px;
}
}
}
}
}
}
}
//
.pagination {
display: flex;
align-items: center;
justify-content: flex-end;
gap: 16px;
.total {
font-size: 13px;
color: rgba(255, 255, 255, 0.6);
}
.page-btns {
display: flex;
gap: 8px;
.page-btn {
min-width: 28px;
height: 28px;
display: flex;
align-items: center;
justify-content: center;
background-color: rgba(64, 169, 255, 0.1);
border: 1px solid rgba(64, 169, 255, 0.2);
border-radius: 4px;
font-size: 12px;
color: rgba(255, 255, 255, 0.8);
cursor: pointer;
transition: all 0.3s;
&:hover:not(.disabled):not(.active) {
background-color: rgba(64, 169, 255, 0.2);
border-color: rgba(64, 169, 255, 0.4);
}
&.active {
background-color: #40a9ff;
border-color: #40a9ff;
color: #fff;
}
&.disabled {
opacity: 0.4;
cursor: not-allowed;
}
}
}
}
//
.table-body::-webkit-scrollbar {
width: 6px;
}
.table-body::-webkit-scrollbar-track {
background: rgba(0, 0, 0, 0.2);
border-radius: 3px;
}
.table-body::-webkit-scrollbar-thumb {
background: linear-gradient(180deg, #40a9ff 0%, #1890ff 100%);
border-radius: 3px;
}
.table-body::-webkit-scrollbar-thumb:hover {
background: linear-gradient(180deg, #69c0ff 0%, #40a9ff 100%);
}
</style>

View File

@ -0,0 +1,375 @@
<template>
<div v-if="visible" class="responsible-dialog-overlay" @click="handleOverlayClick">
<div class="responsible-dialog" @click.stop>
<!-- 标题栏 -->
<div class="dialog-header">
<div class="header-title">潼南建设项目责任人明细</div>
<div class="close-btn" @click="handleClose">
<el-icon><Close /></el-icon>
</div>
</div>
<!-- 数据表格 -->
<div class="table-section">
<div class="table-header">
<div class="th" style="width: 60px">序号</div>
<div class="th" style="width: 100px">区县/镇街</div>
<div class="th" style="width: 70px">总人数</div>
<div class="th" style="width: 70px">吹哨人</div>
<div class="th" style="width: 130px">建设单位包保责任人</div>
<div class="th" style="width: 130px">施工单位包保责任人</div>
<div class="th" style="width: 120px">驻地包保责任人</div>
<div class="th" style="width: 120px">区县级包保责任人</div>
<div class="th" style="width: 120px">市级包保责任人</div>
<div class="th" style="width: 60px">操作</div>
</div>
<div class="table-body">
<div
v-for="(item, index) in tableData"
:key="item.id"
class="table-row"
:class="{ 'row-even': index % 2 === 1 }"
>
<div class="td" style="width: 60px">{{ item.id }}</div>
<div class="td" style="width: 100px">{{ item.region }}</div>
<div class="td" style="width: 70px">{{ item.totalCount }}</div>
<div class="td" style="width: 70px">{{ item.whistleblower }}</div>
<div class="td" style="width: 130px">{{ item.constructionUnit }}</div>
<div class="td" style="width: 130px">{{ item.contractorUnit }}</div>
<div class="td" style="width: 120px">{{ item.stationed }}</div>
<div class="td" style="width: 120px">{{ item.districtLevel }}</div>
<div class="td" style="width: 120px">{{ item.cityLevel }}</div>
<div class="td" style="width: 60px">
<span class="detail-link" @click="handleDetail(item)">详情</span>
</div>
</div>
</div>
</div>
<!-- 分页 -->
<div class="pagination">
<span class="total">{{ total }}条数据</span>
<div class="page-btns">
<div class="page-btn" :class="{ disabled: currentPage === 1 }" @click="prevPage">
<el-icon><ArrowLeft /></el-icon>
</div>
<div
v-for="page in visiblePages"
:key="page"
class="page-btn"
:class="{ active: currentPage === page }"
@click="goToPage(page)"
>
{{ page }}
</div>
<div class="page-btn" :class="{ disabled: currentPage === totalPages }" @click="nextPage">
<el-icon><ArrowRight /></el-icon>
</div>
</div>
</div>
</div>
</div>
</template>
<script setup>
import { ref, computed, watch } from "vue";
import { Close, ArrowLeft, ArrowRight } from "@element-plus/icons-vue";
const props = defineProps({
visible: {
type: Boolean,
default: false,
},
});
const emit = defineEmits(["update:visible", "close", "detail"]);
//
const tableData = ref([
{
id: 1,
region: "万州区",
totalCount: 6,
whistleblower: 2,
constructionUnit: 2,
contractorUnit: 2,
stationed: 2,
districtLevel: 2,
cityLevel: 2,
},
{
id: 2,
region: "柏梓镇",
totalCount: 6,
whistleblower: 2,
constructionUnit: 2,
contractorUnit: 2,
stationed: 2,
districtLevel: 2,
cityLevel: 2,
},
]);
//
const currentPage = ref(1);
const pageSize = ref(10);
const total = ref(36);
const totalPages = computed(() => Math.ceil(total.value / pageSize.value));
const visiblePages = computed(() => {
const pages = [];
const maxVisible = 4;
let start = Math.max(1, currentPage.value - Math.floor(maxVisible / 2));
let end = Math.min(totalPages.value, start + maxVisible - 1);
if (end - start + 1 < maxVisible) {
start = Math.max(1, end - maxVisible + 1);
}
for (let i = start; i <= end; i++) {
pages.push(i);
}
return pages;
});
//
const handleClose = () => {
emit("update:visible", false);
emit("close");
};
//
const handleOverlayClick = () => {
handleClose();
};
//
const handleDetail = (item) => {
emit("detail", item);
};
//
const prevPage = () => {
if (currentPage.value > 1) {
currentPage.value--;
fetchData();
}
};
const nextPage = () => {
if (currentPage.value < totalPages.value) {
currentPage.value++;
fetchData();
}
};
const goToPage = (page) => {
currentPage.value = page;
fetchData();
};
//
const fetchData = () => {
console.log("获取第", currentPage.value, "页数据");
// API
};
// visible
watch(
() => props.visible,
(newVal) => {
if (newVal) {
currentPage.value = 1;
fetchData();
}
}
);
</script>
<style lang="scss" scoped>
.responsible-dialog-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(0, 0, 0, 0.6);
display: flex;
align-items: center;
justify-content: center;
z-index: 1000;
}
.responsible-dialog {
width: 1100px;
background: linear-gradient(135deg, rgba(20, 50, 90, 0.95) 0%, rgba(10, 30, 60, 0.98) 100%);
border: 1px solid rgba(64, 169, 255, 0.3);
border-radius: 12px;
padding: 24px;
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.4);
}
//
.dialog-header {
display: flex;
align-items: center;
justify-content: center;
position: relative;
margin-bottom: 20px;
.header-title {
font-size: 20px;
font-weight: 600;
color: #fff;
padding: 8px 40px;
background: linear-gradient(90deg, transparent 0%, rgba(64, 169, 255, 0.2) 20%, rgba(64, 169, 255, 0.2) 80%, transparent 100%);
border-bottom: 2px solid #40a9ff;
}
.close-btn {
position: absolute;
right: 0;
top: 0;
width: 32px;
height: 32px;
display: flex;
align-items: center;
justify-content: center;
color: rgba(255, 255, 255, 0.7);
cursor: pointer;
font-size: 20px;
transition: color 0.3s;
&:hover {
color: #fff;
}
}
}
//
.table-section {
background-color: rgba(30, 70, 120, 0.3);
border-radius: 8px;
overflow: hidden;
margin-bottom: 20px;
.table-header {
display: flex;
background-color: rgba(64, 169, 255, 0.2);
padding: 12px 16px;
.th {
font-size: 13px;
font-weight: 500;
color: #fff;
text-align: center;
}
}
.table-body {
max-height: 320px;
overflow-y: auto;
.table-row {
display: flex;
padding: 14px 16px;
align-items: center;
transition: background-color 0.3s;
&:hover {
background-color: rgba(64, 169, 255, 0.1);
}
&.row-even {
background-color: rgba(30, 70, 120, 0.2);
}
.td {
font-size: 13px;
color: rgba(255, 255, 255, 0.85);
text-align: center;
.detail-link {
color: #40a9ff;
cursor: pointer;
font-size: 13px;
&:hover {
color: #69c0ff;
text-decoration: underline;
}
}
}
}
}
}
//
.pagination {
display: flex;
align-items: center;
justify-content: flex-end;
gap: 16px;
.total {
font-size: 13px;
color: rgba(255, 255, 255, 0.6);
}
.page-btns {
display: flex;
gap: 8px;
.page-btn {
min-width: 28px;
height: 28px;
display: flex;
align-items: center;
justify-content: center;
background-color: rgba(64, 169, 255, 0.1);
border: 1px solid rgba(64, 169, 255, 0.2);
border-radius: 4px;
font-size: 12px;
color: rgba(255, 255, 255, 0.8);
cursor: pointer;
transition: all 0.3s;
&:hover:not(.disabled):not(.active) {
background-color: rgba(64, 169, 255, 0.2);
border-color: rgba(64, 169, 255, 0.4);
}
&.active {
background-color: #40a9ff;
border-color: #40a9ff;
color: #fff;
}
&.disabled {
opacity: 0.4;
cursor: not-allowed;
}
}
}
}
//
.table-body::-webkit-scrollbar {
width: 6px;
}
.table-body::-webkit-scrollbar-track {
background: rgba(0, 0, 0, 0.2);
border-radius: 3px;
}
.table-body::-webkit-scrollbar-thumb {
background: linear-gradient(180deg, #40a9ff 0%, #1890ff 100%);
border-radius: 3px;
}
.table-body::-webkit-scrollbar-thumb:hover {
background: linear-gradient(180deg, #69c0ff 0%, #40a9ff 100%);
}
</style>

View File

@ -0,0 +1,397 @@
<template>
<div v-if="visible" class="team-dialog-overlay" @click="handleOverlayClick">
<div class="team-dialog" @click.stop>
<!-- 标题栏 -->
<div class="dialog-header">
<div class="header-title">潼南护路团队成员</div>
<div class="close-btn" @click="handleClose">
<el-icon><Close /></el-icon>
</div>
</div>
<!-- 数据表格 -->
<div class="table-section">
<div class="table-header">
<div class="th" style="width: 60px">序号</div>
<div class="th" style="width: 100px">区县</div>
<div class="th" style="width: 80px">总人数</div>
<div class="th" style="width: 140px">交通主管部门责任人</div>
<div class="th" style="width: 120px">公路机构责任人</div>
<div class="th" style="width: 140px">养护站道班责任人</div>
<div class="th" style="width: 80px">护路员</div>
<div class="th" style="flex: 1">操作</div>
</div>
<div class="table-body">
<div
v-for="(item, index) in tableData"
:key="item.id"
class="table-row"
:class="{ 'row-even': index % 2 === 1 }"
>
<div class="td" style="width: 60px">{{ item.id }}</div>
<div class="td" style="width: 100px">{{ item.district }}</div>
<div class="td" style="width: 80px">{{ item.totalCount }}</div>
<div class="td" style="width: 140px">{{ item.trafficDept }}</div>
<div class="td" style="width: 120px">{{ item.roadOrg }}</div>
<div class="td" style="width: 140px">{{ item.maintenance }}</div>
<div class="td" style="width: 80px">{{ item.roadKeeper }}</div>
<div class="td" style="flex: 1">
<span class="view-link" @click="handleView(item)">查看</span>
</div>
</div>
</div>
</div>
<!-- 分页 -->
<div class="pagination">
<div class="page-btn" :class="{ disabled: currentPage === 1 }" @click="prevPage">
上一个
</div>
<div class="page-numbers">
<div
v-for="page in visiblePages"
:key="page"
class="page-num"
:class="{ active: currentPage === page }"
@click="goToPage(page)"
>
{{ page }}
</div>
</div>
<div class="page-btn" :class="{ disabled: currentPage === totalPages }" @click="nextPage">
下一个
</div>
</div>
</div>
</div>
</template>
<script setup>
import { ref, computed, watch } from "vue";
import { Close } from "@element-plus/icons-vue";
const props = defineProps({
visible: {
type: Boolean,
default: false,
},
});
const emit = defineEmits(["update:visible", "close", "view"]);
//
const tableData = ref([
{
id: 1,
district: "潼南",
totalCount: 128,
trafficDept: 2,
roadOrg: 3,
maintenance: 6,
roadKeeper: 117,
},
{
id: 2,
district: "万州",
totalCount: 96,
trafficDept: 1,
roadOrg: 2,
maintenance: 4,
roadKeeper: 89,
},
{
id: 3,
district: "沙坪坝",
totalCount: 156,
trafficDept: 3,
roadOrg: 4,
maintenance: 8,
roadKeeper: 141,
},
{
id: 4,
district: "渝中",
totalCount: 64,
trafficDept: 1,
roadOrg: 2,
maintenance: 3,
roadKeeper: 58,
},
]);
//
const currentPage = ref(1);
const pageSize = ref(10);
const total = ref(36);
const totalPages = computed(() => Math.ceil(total.value / pageSize.value));
const visiblePages = computed(() => {
const pages = [];
const maxVisible = 5;
let start = Math.max(1, currentPage.value - Math.floor(maxVisible / 2));
let end = Math.min(totalPages.value, start + maxVisible - 1);
if (end - start + 1 < maxVisible) {
start = Math.max(1, end - maxVisible + 1);
}
for (let i = start; i <= end; i++) {
pages.push(i);
}
return pages;
});
//
const handleClose = () => {
emit("update:visible", false);
emit("close");
};
//
const handleOverlayClick = () => {
handleClose();
};
//
const handleView = (item) => {
emit("view", item);
};
//
const prevPage = () => {
if (currentPage.value > 1) {
currentPage.value--;
fetchData();
}
};
const nextPage = () => {
if (currentPage.value < totalPages.value) {
currentPage.value++;
fetchData();
}
};
const goToPage = (page) => {
currentPage.value = page;
fetchData();
};
//
const fetchData = () => {
console.log("获取第", currentPage.value, "页数据");
// API
};
// visible
watch(
() => props.visible,
(newVal) => {
if (newVal) {
currentPage.value = 1;
fetchData();
}
}
);
</script>
<style lang="scss" scoped>
.team-dialog-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(0, 0, 0, 0.6);
display: flex;
align-items: center;
justify-content: center;
z-index: 2000;
}
.team-dialog {
width: 900px;
background: linear-gradient(135deg, rgba(20, 50, 90, 0.95) 0%, rgba(10, 30, 60, 0.98) 100%);
border: 1px solid rgba(64, 169, 255, 0.3);
border-radius: 12px;
padding: 24px;
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.4);
}
//
.dialog-header {
display: flex;
align-items: center;
justify-content: center;
position: relative;
margin-bottom: 20px;
.header-title {
font-size: 20px;
font-weight: 600;
color: #fff;
padding: 8px 40px;
background: linear-gradient(90deg, transparent 0%, rgba(64, 169, 255, 0.2) 20%, rgba(64, 169, 255, 0.2) 80%, transparent 100%);
border-bottom: 2px solid #40a9ff;
}
.close-btn {
position: absolute;
right: 0;
top: 0;
width: 32px;
height: 32px;
display: flex;
align-items: center;
justify-content: center;
color: rgba(255, 255, 255, 0.7);
cursor: pointer;
font-size: 20px;
transition: color 0.3s;
&:hover {
color: #fff;
}
}
}
//
.table-section {
background-color: rgba(30, 70, 120, 0.3);
border-radius: 8px;
overflow: hidden;
margin-bottom: 20px;
.table-header {
display: flex;
background-color: rgba(64, 169, 255, 0.2);
padding: 12px 16px;
.th {
font-size: 14px;
font-weight: 500;
color: #fff;
text-align: center;
}
}
.table-body {
max-height: 320px;
overflow-y: auto;
.table-row {
display: flex;
padding: 14px 16px;
align-items: center;
transition: background-color 0.3s;
&:hover {
background-color: rgba(64, 169, 255, 0.1);
}
&.row-even {
background-color: rgba(30, 70, 120, 0.2);
}
.td {
font-size: 13px;
color: rgba(255, 255, 255, 0.85);
text-align: center;
//
.view-link {
color: #40a9ff;
cursor: pointer;
font-size: 13px;
transition: color 0.3s;
&:hover {
color: #69c0ff;
text-decoration: underline;
}
}
}
}
}
}
//
.pagination {
display: flex;
align-items: center;
justify-content: flex-end;
gap: 8px;
.page-btn {
padding: 6px 16px;
background-color: rgba(64, 169, 255, 0.1);
border: 1px solid rgba(64, 169, 255, 0.3);
border-radius: 4px;
font-size: 13px;
color: rgba(255, 255, 255, 0.8);
cursor: pointer;
transition: all 0.3s;
&:hover:not(.disabled) {
background-color: rgba(64, 169, 255, 0.2);
border-color: rgba(64, 169, 255, 0.5);
}
&.disabled {
opacity: 0.4;
cursor: not-allowed;
}
}
.page-numbers {
display: flex;
gap: 8px;
.page-num {
min-width: 28px;
height: 28px;
display: flex;
align-items: center;
justify-content: center;
background-color: rgba(64, 169, 255, 0.1);
border: 1px solid rgba(64, 169, 255, 0.3);
border-radius: 4px;
font-size: 13px;
color: rgba(255, 255, 255, 0.8);
cursor: pointer;
transition: all 0.3s;
&:hover:not(.active) {
background-color: rgba(64, 169, 255, 0.2);
border-color: rgba(64, 169, 255, 0.5);
}
&.active {
background-color: #40a9ff;
border-color: #40a9ff;
color: #fff;
}
}
}
}
//
.table-body::-webkit-scrollbar {
width: 6px;
}
.table-body::-webkit-scrollbar-track {
background: rgba(0, 0, 0, 0.2);
border-radius: 3px;
}
.table-body::-webkit-scrollbar-thumb {
background: linear-gradient(180deg, #40a9ff 0%, #1890ff 100%);
border-radius: 3px;
}
.table-body::-webkit-scrollbar-thumb:hover {
background: linear-gradient(180deg, #69c0ff 0%, #40a9ff 100%);
}
</style>

View File

@ -0,0 +1,605 @@
<template>
<div v-if="visible" class="warning-dialog-overlay" @click="handleOverlayClick">
<div class="warning-dialog" @click.stop>
<!-- 标题栏 -->
<div class="dialog-header">
<div class="header-title">响应情况</div>
<div class="close-btn" @click="handleClose">
<el-icon><Close /></el-icon>
</div>
</div>
<!-- 筛选区域 -->
<div class="filter-section">
<div class="filter-row">
<div class="filter-item">
<el-select v-model="filterForm.warningLevel" placeholder="预警等级" class="filter-select">
<el-option label="全部" value="" />
<el-option label="红色预警" value="red" />
<el-option label="橙色预警" value="orange" />
<el-option label="黄色预警" value="yellow" />
<el-option label="蓝色预警" value="blue" />
</el-select>
</div>
<div class="filter-item">
<el-select v-model="filterForm.region" placeholder="行政区域" class="filter-select">
<el-option label="全部" value="" />
<el-option label="万州区" value="wanzhou" />
<el-option label="涪陵区" value="fuling" />
<el-option label="万盛区" value="wansheng" />
<el-option label="长寿区" value="changshou" />
<el-option label="城口区" value="chengkou" />
</el-select>
</div>
<div class="filter-item">
<el-select v-model="filterForm.isEnded" placeholder="是否结束" class="filter-select">
<el-option label="全部" value="" />
<el-option label="是" value="yes" />
<el-option label="否" value="no" />
</el-select>
</div>
<div class="filter-item">
<el-select v-model="filterForm.isResponded" placeholder="是否回应" class="filter-select">
<el-option label="全部" value="" />
<el-option label="是" value="yes" />
<el-option label="否" value="no" />
</el-select>
</div>
<div class="filter-item">
<el-button type="primary" class="search-btn" @click="handleSearch">
<el-icon><Search /></el-icon>
查询
</el-button>
</div>
</div>
</div>
<!-- 数据表格 -->
<div class="table-section">
<div class="table-header">
<div class="th" style="width: 60px">序号</div>
<div class="th" style="width: 100px">预警等级</div>
<div class="th" style="width: 100px">行政区域</div>
<div class="th" style="width: 160px">预警时间</div>
<div class="th" style="width: 160px">结束时间</div>
<div class="th" style="width: 100px">影响点数量</div>
<div class="th clickable" style="width: 80px">已叫应</div>
<div class="th" style="width: 80px">已回应</div>
<div class="th" style="width: 80px">未回应</div>
<div class="th" style="width: 80px">已催告</div>
</div>
<div class="table-body">
<div
v-for="(item, index) in tableData"
:key="item.id"
class="table-row"
:class="{ 'row-even': index % 2 === 1 }"
>
<div class="td" style="width: 60px">{{ index + 1 }}</div>
<div class="td" style="width: 100px">
<span class="warning-level" :class="item.levelClass">{{ item.warningLevel }}</span>
</div>
<div class="td" style="width: 100px">{{ item.region }}</div>
<div class="td" style="width: 160px">{{ item.warningTime }}</div>
<div class="td" style="width: 160px">{{ item.endTime }}</div>
<div class="td" style="width: 100px">{{ item.impactPoints }}</div>
<div class="td clickable-cell" style="width: 80px" @click.stop="handleCalledClick(item)">{{ item.called }}</div>
<div class="td" style="width: 80px">{{ item.responded }}</div>
<div class="td" style="width: 80px">{{ item.notResponded }}</div>
<div class="td" style="width: 80px">{{ item.urged }}</div>
</div>
</div>
</div>
<!-- 分页 -->
<div class="pagination">
<span class="total">{{ total }}条数据</span>
<div class="page-btns">
<div class="page-btn" :class="{ disabled: currentPage === 1 }" @click="prevPage">
<el-icon><ArrowLeft /></el-icon>
</div>
<div
v-for="page in visiblePages"
:key="page"
class="page-btn"
:class="{ active: currentPage === page }"
@click="goToPage(page)"
>
{{ page }}
</div>
<div class="page-btn" :class="{ disabled: currentPage === totalPages }" @click="nextPage">
<el-icon><ArrowRight /></el-icon>
</div>
</div>
</div>
</div>
</div>
</template>
<script setup>
import { ref, computed, watch } from "vue";
import { Close, Search, ArrowLeft, ArrowRight } from "@element-plus/icons-vue";
const props = defineProps({
visible: {
type: Boolean,
default: false,
},
});
const emit = defineEmits(["update:visible", "close", "openImpactPoint"]);
//
const filterForm = ref({
warningLevel: "",
region: "",
isEnded: "",
isResponded: "",
});
//
const tableData = ref([
{
id: 1,
warningLevel: "红色预警",
levelClass: "level-red",
region: "万州区",
warningTime: "2025-08-11 04:53:42",
endTime: "2025-08-11 04:53:42",
impactPoints: 4,
called: 2,
responded: 2,
notResponded: 2,
urged: 22,
},
{
id: 2,
warningLevel: "橙色预警",
levelClass: "level-orange",
region: "涪陵区",
warningTime: "2025-08-11 04:53:42",
endTime: "2025-08-11 04:53:42",
impactPoints: 0,
called: 0,
responded: 0,
notResponded: 0,
urged: 18,
},
{
id: 3,
warningLevel: "红色预警",
levelClass: "level-red",
region: "万盛区",
warningTime: "2025-08-11 04:53:42",
endTime: "2025-08-11 04:53:42",
impactPoints: 0,
called: 0,
responded: 0,
notResponded: 0,
urged: 15,
},
{
id: 4,
warningLevel: "黄色预警",
levelClass: "level-yellow",
region: "长寿区",
warningTime: "2025-08-11 04:53:42",
endTime: "2025-08-11 04:53:42",
impactPoints: 0,
called: 0,
responded: 0,
notResponded: 0,
urged: 20,
},
{
id: 5,
warningLevel: "红色预警",
levelClass: "level-red",
region: "城口区",
warningTime: "2025-08-11 04:53:42",
endTime: "2025-08-11 04:53:42",
impactPoints: 0,
called: 0,
responded: 0,
notResponded: 0,
urged: 15,
},
]);
//
const currentPage = ref(1);
const pageSize = ref(10);
const total = ref(36);
const totalPages = computed(() => Math.ceil(total.value / pageSize.value));
const visiblePages = computed(() => {
const pages = [];
const maxVisible = 4;
let start = Math.max(1, currentPage.value - Math.floor(maxVisible / 2));
let end = Math.min(totalPages.value, start + maxVisible - 1);
if (end - start + 1 < maxVisible) {
start = Math.max(1, end - maxVisible + 1);
}
for (let i = start; i <= end; i++) {
pages.push(i);
}
return pages;
});
//
const handleClose = () => {
emit("update:visible", false);
emit("close");
};
//
const handleOverlayClick = () => {
handleClose();
};
//
const handleCalledClick = () => {
emit("responseStatus");
};
//
const handleSearch = () => {
console.log("查询条件:", filterForm.value);
currentPage.value = 1;
fetchData();
};
//
const prevPage = () => {
if (currentPage.value > 1) {
currentPage.value--;
fetchData();
}
};
const nextPage = () => {
if (currentPage.value < totalPages.value) {
currentPage.value++;
fetchData();
}
};
const goToPage = (page) => {
currentPage.value = page;
fetchData();
};
//
const fetchData = () => {
console.log("获取第", currentPage.value, "页数据");
// API
};
// visible
watch(
() => props.visible,
(newVal) => {
if (newVal) {
currentPage.value = 1;
fetchData();
}
}
);
</script>
<style lang="scss" scoped>
.warning-dialog-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(0, 0, 0, 0.6);
display: flex;
align-items: center;
justify-content: center;
z-index: 1000;
}
.warning-dialog {
width: 1100px;
background: linear-gradient(135deg, rgba(20, 50, 90, 0.95) 0%, rgba(10, 30, 60, 0.98) 100%);
border: 1px solid rgba(64, 169, 255, 0.3);
border-radius: 12px;
padding: 24px;
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.4);
}
//
.dialog-header {
display: flex;
align-items: center;
justify-content: center;
position: relative;
margin-bottom: 20px;
.header-title {
font-size: 20px;
font-weight: 600;
color: #fff;
padding: 8px 40px;
background: linear-gradient(90deg, transparent 0%, rgba(64, 169, 255, 0.2) 20%, rgba(64, 169, 255, 0.2) 80%, transparent 100%);
border-bottom: 2px solid #40a9ff;
}
.close-btn {
position: absolute;
right: 0;
top: 0;
width: 32px;
height: 32px;
display: flex;
align-items: center;
justify-content: center;
color: rgba(255, 255, 255, 0.7);
cursor: pointer;
font-size: 20px;
transition: color 0.3s;
&:hover {
color: #fff;
}
}
}
//
.filter-section {
margin-bottom: 20px;
.filter-row {
display: flex;
align-items: center;
gap: 12px;
flex-wrap: wrap;
}
.filter-item {
.filter-select {
width: 140px;
:deep(.el-input__wrapper) {
background-color: rgba(30, 70, 120, 0.4);
border: 1px solid rgba(64, 169, 255, 0.3);
box-shadow: none;
border-radius: 4px;
.el-input__inner {
color: #fff;
font-size: 13px;
&::placeholder {
color: rgba(255, 255, 255, 0.5);
}
}
.el-input__suffix {
.el-icon {
color: rgba(255, 255, 255, 0.6);
}
}
}
}
.search-btn {
background: linear-gradient(135deg, #40a9ff 0%, #1890ff 100%);
border: none;
border-radius: 4px;
padding: 0 20px;
height: 32px;
font-size: 13px;
display: flex;
align-items: center;
gap: 6px;
&:hover {
background: linear-gradient(135deg, #69c0ff 0%, #40a9ff 100%);
}
.el-icon {
font-size: 14px;
}
}
}
}
//
.table-section {
background-color: rgba(30, 70, 120, 0.3);
border-radius: 8px;
overflow: hidden;
margin-bottom: 20px;
.table-header {
display: flex;
background-color: rgba(64, 169, 255, 0.2);
padding: 12px 16px;
.th {
font-size: 14px;
font-weight: 500;
color: #fff;
text-align: center;
&.clickable {
cursor: pointer;
transition: all 0.3s;
&:hover {
color: #69c0ff;
text-shadow: 0 0 8px rgba(105, 192, 255, 0.6);
}
}
}
}
.table-body {
max-height: 280px;
overflow-y: auto;
.table-row {
display: flex;
padding: 12px 16px;
align-items: center;
transition: background-color 0.3s;
&:hover {
background-color: rgba(64, 169, 255, 0.1);
}
&.row-even {
background-color: rgba(30, 70, 120, 0.2);
}
.td {
font-size: 13px;
color: rgba(255, 255, 255, 0.85);
text-align: center;
&.clickable-cell {
cursor: pointer;
color: #40a9ff;
font-weight: 500;
transition: all 0.3s;
&:hover {
color: #69c0ff;
text-shadow: 0 0 8px rgba(105, 192, 255, 0.6);
}
}
.warning-level {
display: inline-block;
padding: 2px 10px;
border-radius: 4px;
font-size: 12px;
font-weight: 500;
&.level-red {
background-color: rgba(255, 77, 79, 0.2);
color: #ff4d4f;
border: 1px solid rgba(255, 77, 79, 0.4);
}
&.level-orange {
background-color: rgba(255, 122, 69, 0.2);
color: #ff7a45;
border: 1px solid rgba(255, 122, 69, 0.4);
}
&.level-yellow {
background-color: rgba(250, 219, 95, 0.2);
color: #fadb5f;
border: 1px solid rgba(250, 219, 95, 0.4);
}
&.level-blue {
background-color: rgba(64, 169, 255, 0.2);
color: #40a9ff;
border: 1px solid rgba(64, 169, 255, 0.4);
}
}
}
}
}
}
//
.pagination {
display: flex;
align-items: center;
justify-content: flex-end;
gap: 16px;
.total {
font-size: 13px;
color: rgba(255, 255, 255, 0.6);
}
.page-btns {
display: flex;
gap: 8px;
.page-btn {
min-width: 32px;
height: 32px;
display: flex;
align-items: center;
justify-content: center;
background-color: rgba(64, 169, 255, 0.1);
border: 1px solid rgba(64, 169, 255, 0.2);
border-radius: 4px;
font-size: 13px;
color: rgba(255, 255, 255, 0.8);
cursor: pointer;
transition: all 0.3s;
&:hover:not(.disabled):not(.active) {
background-color: rgba(64, 169, 255, 0.2);
border-color: rgba(64, 169, 255, 0.4);
}
&.active {
background-color: #40a9ff;
border-color: #40a9ff;
color: #fff;
}
&.disabled {
opacity: 0.4;
cursor: not-allowed;
}
}
}
}
//
.table-body::-webkit-scrollbar {
width: 8px;
}
.table-body::-webkit-scrollbar-track {
background: rgba(0, 0, 0, 0.2);
border-radius: 4px;
}
.table-body::-webkit-scrollbar-thumb {
background: linear-gradient(180deg, #40a9ff 0%, #1890ff 100%);
border-radius: 4px;
border: 1px solid rgba(255, 255, 255, 0.1);
}
.table-body::-webkit-scrollbar-thumb:hover {
background: linear-gradient(180deg, #69c0ff 0%, #40a9ff 100%);
}
.table-body::-webkit-scrollbar-corner {
background: transparent;
}
//
:deep(.el-select-dropdown) {
background-color: rgba(20, 50, 90, 0.98);
border: 1px solid rgba(64, 169, 255, 0.3);
.el-select-dropdown__item {
color: rgba(255, 255, 255, 0.85);
&:hover {
background-color: rgba(64, 169, 255, 0.2);
}
&.selected {
background-color: rgba(64, 169, 255, 0.3);
color: #40a9ff;
}
}
}
</style>

View File

@ -0,0 +1,557 @@
<template>
<div v-if="visible" class="warning-dialog-overlay" @click="handleOverlayClick">
<div class="warning-dialog" @click.stop>
<!-- 标题栏 -->
<div class="dialog-header">
<div class="header-title">预警情况</div>
<div class="close-btn" @click="handleClose">
<el-icon><Close /></el-icon>
</div>
</div>
<!-- 筛选区域 -->
<div class="filter-section">
<div class="filter-row">
<div class="filter-item">
<span class="filter-label">预警等级</span>
<el-select v-model="filterForm.warningLevel" placeholder="请选择" class="filter-select" clearable>
<el-option
v-for="item in warningLevelOptions"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
</div>
<div class="filter-item">
<span class="filter-label">行政区域</span>
<el-select v-model="filterForm.region" placeholder="请选择" class="filter-select" clearable>
<el-option
v-for="item in regionOptions"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
</div>
<div class="filter-item">
<span class="filter-label">是否结束</span>
<el-select v-model="filterForm.isEnded" placeholder="请选择" class="filter-select" clearable>
<el-option
v-for="item in isEndedOptions"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
</div>
</div>
</div>
<!-- 数据表格 -->
<div class="table-section">
<div class="table-header">
<div class="th" style="width: 50px">序号</div>
<div class="th" style="width: 100px">预警等级</div>
<div class="th" style="width: 100px">气象类型</div>
<div class="th" style="width: 100px">行政区域</div>
<div class="th" style="width: 160px">预警时间</div>
<div class="th" style="width: 160px">结束时间</div>
<div class="th" style="flex: 1">影响点数量</div>
</div>
<div class="table-body">
<div
v-for="(item, index) in tableData"
:key="item.id"
class="table-row"
:class="{ 'row-even': index % 2 === 1 }"
>
<div class="td" style="width: 50px">{{ item.id }}</div>
<div class="td" style="width: 100px">
<span :class="['warning-level-tag', getWarningClass(item.warningLevel)]">{{ item.warningLevel }}</span>
</div>
<div class="td" style="width: 100px">{{ item.weatherType }}</div>
<div class="td" style="width: 100px">{{ item.region }}</div>
<div class="td" style="width: 160px">{{ item.warningTime }}</div>
<div class="td" style="width: 160px">{{ item.endTime }}</div>
<div class="td" style="flex: 1">
<span class="impact-count" @click="handleImpactClick(item)">{{ item.impactCount }}</span>
</div>
</div>
</div>
</div>
<!-- 分页 -->
<div class="pagination">
<div class="page-btn" :class="{ disabled: currentPage === 1 }" @click="prevPage">
上一个
</div>
<div class="page-numbers">
<div
v-for="page in visiblePages"
:key="page"
class="page-num"
:class="{ active: currentPage === page }"
@click="goToPage(page)"
>
{{ page }}
</div>
</div>
<div class="page-btn" :class="{ disabled: currentPage === totalPages }" @click="nextPage">
下一个
</div>
</div>
</div>
</div>
</template>
<script setup>
import { ref, computed, watch } from "vue";
import { Close } from "@element-plus/icons-vue";
const props = defineProps({
visible: {
type: Boolean,
default: false,
},
});
const emit = defineEmits(["update:visible", "close", "impactClick"]);
//
const filterForm = ref({
warningLevel: "",
region: "",
isEnded: "",
});
//
const warningLevelOptions = ref([
{ label: "红色预警", value: "红色预警" },
{ label: "橙色预警", value: "橙色预警" },
{ label: "黄色预警", value: "黄色预警" },
{ label: "蓝色预警", value: "蓝色预警" },
]);
//
const regionOptions = ref([
{ label: "重庆市", value: "重庆市" },
{ label: "万州区", value: "万州区" },
{ label: "沙坪坝区", value: "沙坪坝区" },
{ label: "渝中区", value: "渝中区" },
]);
//
const isEndedOptions = ref([
{ label: "是", value: "是" },
{ label: "否", value: "否" },
]);
//
const tableData = ref([
{
id: 1,
warningLevel: "红色预警",
weatherType: "暴雨",
region: "重庆市",
warningTime: "2025-08-11 04:53:42",
endTime: "2025-08-11 04:53:42",
impactCount: 0,
},
{
id: 2,
warningLevel: "橙色预警",
weatherType: "暴雨",
region: "万州区",
warningTime: "2025-08-11 04:53:42",
endTime: "2025-08-11 04:53:42",
impactCount: 1,
},
{
id: 3,
warningLevel: "黄色预警",
weatherType: "大风",
region: "沙坪坝区",
warningTime: "2025-08-10 16:20:15",
endTime: "2025-08-10 20:30:00",
impactCount: 3,
},
{
id: 4,
warningLevel: "蓝色预警",
weatherType: "雷电",
region: "渝中区",
warningTime: "2025-08-09 09:15:30",
endTime: "2025-08-09 12:00:00",
impactCount: 2,
},
]);
//
const currentPage = ref(1);
const pageSize = ref(10);
const total = ref(36);
const totalPages = computed(() => Math.ceil(total.value / pageSize.value));
const visiblePages = computed(() => {
const pages = [];
const maxVisible = 5;
let start = Math.max(1, currentPage.value - Math.floor(maxVisible / 2));
let end = Math.min(totalPages.value, start + maxVisible - 1);
if (end - start + 1 < maxVisible) {
start = Math.max(1, end - maxVisible + 1);
}
for (let i = start; i <= end; i++) {
pages.push(i);
}
return pages;
});
//
const getWarningClass = (level) => {
const classMap = {
"红色预警": "warning-red",
"橙色预警": "warning-orange",
"黄色预警": "warning-yellow",
"蓝色预警": "warning-blue",
};
return classMap[level] || "";
};
//
const handleClose = () => {
emit("update:visible", false);
emit("close");
};
//
const handleOverlayClick = () => {
handleClose();
};
//
const handleImpactClick = (item) => {
emit("impactClick", item);
};
//
const prevPage = () => {
if (currentPage.value > 1) {
currentPage.value--;
fetchData();
}
};
const nextPage = () => {
if (currentPage.value < totalPages.value) {
currentPage.value++;
fetchData();
}
};
const goToPage = (page) => {
currentPage.value = page;
fetchData();
};
//
const fetchData = () => {
console.log("获取第", currentPage.value, "页数据");
// API
};
// visible
watch(
() => props.visible,
(newVal) => {
if (newVal) {
currentPage.value = 1;
fetchData();
}
}
);
</script>
<style lang="scss" scoped>
.warning-dialog-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(0, 0, 0, 0.6);
display: flex;
align-items: center;
justify-content: center;
z-index: 1000;
}
.warning-dialog {
width: 950px;
background: linear-gradient(135deg, rgba(20, 50, 90, 0.95) 0%, rgba(10, 30, 60, 0.98) 100%);
border: 1px solid rgba(64, 169, 255, 0.3);
border-radius: 12px;
padding: 24px;
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.4);
}
//
.dialog-header {
display: flex;
align-items: center;
justify-content: center;
position: relative;
margin-bottom: 20px;
.header-title {
font-size: 20px;
font-weight: 600;
color: #fff;
padding: 8px 40px;
background: linear-gradient(90deg, transparent 0%, rgba(64, 169, 255, 0.2) 20%, rgba(64, 169, 255, 0.2) 80%, transparent 100%);
border-bottom: 2px solid #40a9ff;
}
.close-btn {
position: absolute;
right: 0;
top: 0;
width: 32px;
height: 32px;
display: flex;
align-items: center;
justify-content: center;
color: rgba(255, 255, 255, 0.7);
cursor: pointer;
font-size: 20px;
transition: color 0.3s;
&:hover {
color: #fff;
}
}
}
//
.filter-section {
margin-bottom: 16px;
.filter-row {
display: flex;
gap: 24px;
}
.filter-item {
display: flex;
align-items: center;
gap: 8px;
.filter-label {
font-size: 13px;
color: rgba(255, 255, 255, 0.8);
white-space: nowrap;
}
.filter-select {
width: 120px;
:deep(.el-input__wrapper) {
background-color: rgba(30, 70, 120, 0.4);
border: 1px solid rgba(64, 169, 255, 0.3);
box-shadow: none;
border-radius: 4px;
.el-input__inner {
color: #fff;
font-size: 13px;
&::placeholder {
color: rgba(255, 255, 255, 0.4);
}
}
.el-input__suffix {
.el-icon {
color: rgba(255, 255, 255, 0.6);
}
}
}
}
}
}
//
.table-section {
background-color: rgba(30, 70, 120, 0.3);
border-radius: 8px;
overflow: hidden;
margin-bottom: 20px;
.table-header {
display: flex;
background-color: rgba(64, 169, 255, 0.2);
padding: 12px 16px;
.th {
font-size: 14px;
font-weight: 500;
color: #fff;
text-align: center;
}
}
.table-body {
max-height: 320px;
overflow-y: auto;
.table-row {
display: flex;
padding: 14px 16px;
align-items: center;
transition: background-color 0.3s;
&:hover {
background-color: rgba(64, 169, 255, 0.1);
}
&.row-even {
background-color: rgba(30, 70, 120, 0.2);
}
.td {
font-size: 13px;
color: rgba(255, 255, 255, 0.85);
text-align: center;
//
.warning-level-tag {
display: inline-block;
padding: 2px 10px;
border-radius: 4px;
font-size: 12px;
font-weight: 500;
&.warning-red {
background-color: rgba(255, 77, 79, 0.2);
color: #ff4d4f;
border: 1px solid rgba(255, 77, 79, 0.4);
}
&.warning-orange {
background-color: rgba(255, 122, 0, 0.2);
color: #ff7a00;
border: 1px solid rgba(255, 122, 0, 0.4);
}
&.warning-yellow {
background-color: rgba(250, 219, 20, 0.2);
color: #fadb14;
border: 1px solid rgba(250, 219, 20, 0.4);
}
&.warning-blue {
background-color: rgba(64, 169, 255, 0.2);
color: #40a9ff;
border: 1px solid rgba(64, 169, 255, 0.4);
}
}
//
.impact-count {
color: #ff4d4f;
cursor: pointer;
font-weight: 600;
transition: all 0.3s;
&:hover {
color: #ff7875;
text-shadow: 0 0 8px rgba(255, 77, 79, 0.6);
}
}
}
}
}
}
//
.pagination {
display: flex;
align-items: center;
justify-content: flex-end;
gap: 8px;
.page-btn {
padding: 6px 16px;
background-color: rgba(64, 169, 255, 0.1);
border: 1px solid rgba(64, 169, 255, 0.3);
border-radius: 4px;
font-size: 13px;
color: rgba(255, 255, 255, 0.8);
cursor: pointer;
transition: all 0.3s;
&:hover:not(.disabled) {
background-color: rgba(64, 169, 255, 0.2);
border-color: rgba(64, 169, 255, 0.5);
}
&.disabled {
opacity: 0.4;
cursor: not-allowed;
}
}
.page-numbers {
display: flex;
gap: 8px;
.page-num {
min-width: 28px;
height: 28px;
display: flex;
align-items: center;
justify-content: center;
background-color: rgba(64, 169, 255, 0.1);
border: 1px solid rgba(64, 169, 255, 0.3);
border-radius: 4px;
font-size: 13px;
color: rgba(255, 255, 255, 0.8);
cursor: pointer;
transition: all 0.3s;
&:hover:not(.active) {
background-color: rgba(64, 169, 255, 0.2);
border-color: rgba(64, 169, 255, 0.5);
}
&.active {
background-color: #40a9ff;
border-color: #40a9ff;
color: #fff;
}
}
}
}
//
.table-body::-webkit-scrollbar {
width: 6px;
}
.table-body::-webkit-scrollbar-track {
background: rgba(0, 0, 0, 0.2);
border-radius: 3px;
}
.table-body::-webkit-scrollbar-thumb {
background: linear-gradient(180deg, #40a9ff 0%, #1890ff 100%);
border-radius: 3px;
}
.table-body::-webkit-scrollbar-thumb:hover {
background: linear-gradient(180deg, #69c0ff 0%, #40a9ff 100%);
}
</style>

View File

@ -1,37 +1,320 @@
<template> <template>
<div class="main"> <div class="main">
<div class="top_title">
<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=""
/>
</div>
</div>
<!-- 四个角的装饰 --> <!-- 四个角的装饰 -->
<div class="corner corner-top-left"></div> <div class="corner corner-top-left"></div>
<div class="corner corner-top-right"></div> <div class="corner corner-top-right"></div>
<div class="corner corner-bottom-left"></div> <div class="corner corner-bottom-left"></div>
<div class="corner corner-bottom-right"></div> <div class="corner corner-bottom-right"></div>
<!-- 中心数据展示卡片 -->
<div class="center-info-card-container">
<div
class="center-info-card"
@click="openDialog('tongnanResponsible')"
v-if="showCenterCard.type === 'first'"
>
<div class="card-title">潼南</div>
<div class="card-content">
<div class="info-item clickable">
<span class="info-label">人数</span>
<span class="info-value">{{ showCenterCard.value }}</span>
<span class="info-unit"></span>
</div>
<div class="info-item">
<span class="info-label">项目</span>
<span class="info-value">2</span>
<span class="info-unit"></span>
</div>
</div>
</div>
<div
class="center-info-card"
v-if="showCenterCard.type === 'second' || showCenterCard.type === 'third'"
>
<div
class="card-title"
@click="
showCenterCard.type === 'second'
? openDialog('tongnanTeam')
: showCenterCard.type === 'third'
? openDialog('responseSituation')
: ''
"
>
潼南
</div>
<div class="card-content">
<div class="info-item">
<span class="info-label">人数</span>
<span class="info-value">{{ showCenterCard.value }}</span>
<span class="info-unit"></span>
</div>
<div class="info-item clickable">
<span class="info-label">路段</span>
<span class="info-value">117</span>
<span class="info-unit"></span>
</div>
</div>
</div>
</div>
<div class="left"> <div class="left">
<left></left> <left
@openWarningInfo="openDialog('warningInfo')"
@openImpactPoint="openDialog('impactPoint')"
@openWarningSituation="openDialog('warningSituation')"
@openResponseStatus="openDialog('responseStatus')"
@openDispatchDistrict="openDialog('dispatchDistrict')"
@showCenterCard="(item) => (showCenterCard = item)"
></left>
</div> </div>
<div class="right"> <div class="right">
<right></right> <right
@openClearanceSituation="openDialog('clearanceSituation')"
@openControlSituation="openDialog('controlSituation')"
></right>
</div> </div>
<!-- 地图中心 --> <!-- 地图中心 -->
<div class="center"> <div class="center">
<MapCenter /> <!-- <MapCenter /> -->
</div> </div>
<div class="bottom"> <div class="bottom">
<bottom></bottom> <bottom></bottom>
</div> </div>
<top class="top"></top> <top class="top" @openAIResult="openDialog('aiWarningResult')"></top>
<!-- 响应情况对话框 -->
<responseSituationDiaLog
v-model:visible="dialogVisible.responseSituation"
@close="closeDialog('responseSituation')"
/>
<!-- 预警信息对话框 -->
<warningInfoDialog
v-model:visible="dialogVisible.warningInfo"
@close="closeDialog('warningInfo')"
@responseStatus="openDialog('responseStatus')"
/>
<!-- 事件详情对话框 -->
<eventDetailDialog
v-model:visible="dialogVisible.eventDetail"
@close="closeDialog('eventDetail')"
/>
<!-- 确认对话框 -->
<confirmDialog
v-model:visible="dialogVisible.confirm"
:title="confirmConfig.title"
:message="confirmConfig.message"
:confirm-text="confirmConfig.confirmText"
:cancel-text="confirmConfig.cancelText"
@confirm="closeDialog('confirm')"
@cancel="closeDialog('confirm')"
/>
<!-- 风险点详情对话框 -->
<riskPointDetailDialog
v-model:visible="dialogVisible.riskPointDetail"
@close="closeDialog('riskPointDetail')"
/>
<!-- 影响点情况对话框 -->
<impactPointDialog
v-model:visible="dialogVisible.impactPoint"
@close="closeDialog('impactPoint')"
@detail="openDialog('impactPointDetail')"
/>
<!-- 影响点详情对话框 -->
<impactPointDetailDialog
v-model:visible="dialogVisible.impactPointDetail"
@close="closeDialog('impactPointDetail')"
/>
<!-- 响应点详情对话框 -->
<responsePointDetailDialog
v-model:visible="dialogVisible.responsePointDetail"
@close="closeDialog('responsePointDetail')"
/>
<!-- 响应点信息对话框 -->
<responsePointInfoDialog
v-model:visible="dialogVisible.responsePointInfo"
@close="closeDialog('responsePointInfo')"
/>
<!-- 响应状态对话框 -->
<responseStatusDialog
v-model:visible="dialogVisible.responseStatus"
@close="closeDialog('responseStatus')"
@detail="openDialog('impactPointDetail')"
/>
<!-- AI预警处理结果对话框 -->
<aiWarningResultDialog
v-model:visible="dialogVisible.aiWarningResult"
@close="closeDialog('aiWarningResult')"
/>
<!-- 潼南基本信息对话框 -->
<tongnanInfoDialog
v-model:visible="dialogVisible.tongnanInfo"
@close="closeDialog('tongnanInfo')"
@call="openDialog('confirm')"
/>
<!-- 潼南建设项目责任人明细对话框 -->
<tongnanResponsibleDialog
v-model:visible="dialogVisible.tongnanResponsible"
@close="closeDialog('tongnanResponsible')"
@detail="openDialog('tongnanInfo')"
/>
<!-- 抢通情况对话框 -->
<clearanceSituationDialog
v-model:visible="dialogVisible.clearanceSituation"
@close="closeDialog('clearanceSituation')"
@detail="openDialog('eventDetail')"
/>
<!-- 管控情况对话框 -->
<controlSituationDialog
v-model:visible="dialogVisible.controlSituation"
@close="closeDialog('controlSituation')"
/>
<!-- 调度详情对话框 -->
<dispatchDetailDialog
v-model:visible="dialogVisible.dispatchDetail"
@close="closeDialog('dispatchDetail')"
/>
<!-- 调度区县情况对话框 -->
<dispatchDistrictDialog
v-model:visible="dialogVisible.dispatchDistrict"
@close="closeDialog('dispatchDistrict')"
@dispatchClick="openDialog('dispatchDetail')"
/>
<!-- 潼南护路团队成员对话框 -->
<tongnanTeamDialog
v-model:visible="dialogVisible.tongnanTeam"
@close="closeDialog('tongnanTeam')"
@view="openDialog('tongnanInfo')"
/>
<!-- 预警情况对话框 -->
<warningSituationDialog
v-model:visible="dialogVisible.warningSituation"
@close="closeDialog('warningSituation')"
@impactClick="openDialog('impactPoint')"
/>
</div> </div>
</template> </template>
<script setup> <script setup>
import { ref } from "vue";
import left from "./left.vue"; import left from "./left.vue";
import right from "./right.vue"; import right from "./right.vue";
import bottom from "./bottom.vue"; import bottom from "./bottom.vue";
import top from "./top.vue"; import top from "./top.vue";
import MapCenter from "../cockpit/components/MapCenter.vue"; import MapCenter from "../cockpit/components/MapCenter.vue";
//
import responseSituationDiaLog from "./component/responseSituationDiaLog.vue";
import warningInfoDialog from "./component/warningInfoDialog.vue";
import eventDetailDialog from "./component/eventDetailDialog.vue";
import confirmDialog from "./component/confirmDialog.vue";
import riskPointDetailDialog from "./component/riskPointDetailDialog.vue";
import impactPointDialog from "./component/impactPointDialog.vue";
import impactPointDetailDialog from "./component/impactPointDetailDialog.vue";
import responsePointDetailDialog from "./component/responsePointDetailDialog.vue";
import responsePointInfoDialog from "./component/responsePointInfoDialog.vue";
import responseStatusDialog from "./component/responseStatusDialog.vue";
import aiWarningResultDialog from "./component/aiWarningResultDialog.vue";
import tongnanInfoDialog from "./component/tongnanInfoDialog.vue";
import tongnanResponsibleDialog from "./component/tongnanResponsibleDialog.vue";
import clearanceSituationDialog from "./component/clearanceSituationDialog.vue";
import controlSituationDialog from "./component/controlSituationDialog.vue";
import dispatchDetailDialog from "./component/dispatchDetailDialog.vue";
import dispatchDistrictDialog from "./component/dispatchDistrictDialog.vue";
import tongnanTeamDialog from "./component/tongnanTeamDialog.vue";
import warningSituationDialog from "./component/warningSituationDialog.vue";
//
const dialogVisible = ref({
responseSituation: false,
warningInfo: false,
eventDetail: false,
confirm: false,
riskPointDetail: false,
impactPoint: false,
impactPointDetail: false,
responsePointDetail: false,
responsePointInfo: false,
responseStatus: false,
aiWarningResult: false,
tongnanInfo: false,
tongnanResponsible: false,
clearanceSituation: false,
controlSituation: false,
dispatchDetail: false,
dispatchDistrict: false,
tongnanTeam: false,
warningSituation: false,
});
//
const openDialog = (dialogName) => {
dialogVisible.value[dialogName] = true;
};
//
const closeDialog = (dialogName) => {
dialogVisible.value[dialogName] = false;
};
//
const confirmConfig = ref({
title: "提示",
message: "是否拨打电话?",
confirmText: "确定",
cancelText: "取消",
});
//
const openConfirm = (config) => {
confirmConfig.value = { ...confirmConfig.value, ...config };
dialogVisible.value.confirm = true;
};
//
const showCenterCard = ref(false);
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
// -
// 1920px使 vw
@function vw($px) {
@return calc($px / 1920 * 100vw);
}
.main { .main {
position: relative; position: relative;
width: 100%; width: 100%;
@ -40,26 +323,66 @@ import MapCenter from "../cockpit/components/MapCenter.vue";
background-size: cover; background-size: cover;
background-position: center; background-position: center;
} }
.top_title {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: vw(100);
min-height: 70px;
z-index: 100;
.title_bg {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
object-fit: cover;
}
.title_img_box {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
display: flex;
align-items: center;
justify-content: center;
gap: vw(10);
}
.title_img1 {
width: vw(40);
height: vw(40);
min-width: 28px;
min-height: 28px;
}
.title_img2 {
height: vw(40);
min-height: 28px;
}
}
.left { .left {
position: absolute; position: absolute;
left: 0; left: 0;
top: 0; top: vw(100);
width: 30%; width: 25%;
height: 100%; height: calc(100% - #{vw(100)});
} }
.right { .right {
position: absolute; position: absolute;
right: 0; right: 0;
top: 0; top: vw(100);
width: 30%; width: 25%;
height: 100%; height: calc(100% - #{vw(100)});
} }
.bottom { .bottom {
position: absolute; position: absolute;
bottom: 10px; bottom: 0px;
left: 30%; left: 30%;
width: 40%; width: 40%;
height: 50%; height: 50%;
@ -67,10 +390,10 @@ import MapCenter from "../cockpit/components/MapCenter.vue";
.top { .top {
position: absolute; position: absolute;
top: 10px; top: vw(120);
left: 30%; left: 30%;
width: 40%; width: 40%;
height: 15%; // height: 15%;
// background-color: #15293B; // background-color: #15293B;
} }
@ -86,42 +409,127 @@ import MapCenter from "../cockpit/components/MapCenter.vue";
// //
.corner { .corner {
position: absolute; position: absolute;
width: 30px; width: vw(30);
height: 30px; height: vw(30);
min-width: 20px;
min-height: 20px;
border: 2px solid #40a9ff; border: 2px solid #40a9ff;
z-index: 100; z-index: 100;
pointer-events: none; pointer-events: none;
&.corner-top-left { &.corner-top-left {
top: 10px; top: vw(110);
left: 10px; left: vw(10);
border-right: none; border-right: none;
border-bottom: none; border-bottom: none;
border-top-left-radius: 4px; border-top-left-radius: 4px;
} }
&.corner-top-right { &.corner-top-right {
top: 10px; top: vw(110);
right: 10px; right: vw(10);
border-left: none; border-left: none;
border-bottom: none; border-bottom: none;
border-top-right-radius: 4px; border-top-right-radius: 4px;
} }
&.corner-bottom-left { &.corner-bottom-left {
bottom: 10px; bottom: vw(10);
left: 10px; left: vw(10);
border-right: none; border-right: none;
border-top: none; border-top: none;
border-bottom-left-radius: 4px; border-bottom-left-radius: 4px;
} }
&.corner-bottom-right { &.corner-bottom-right {
bottom: 10px; bottom: 0;
right: 10px; right: vw(10);
border-left: none; border-left: none;
border-top: none; border-top: none;
border-bottom-right-radius: 4px; border-bottom-right-radius: 4px;
} }
} }
.center-info-card-container {
position: absolute;
top: 30%;
left: 35%;
transform: translate(-50%, -50%);
width: vw(250);
min-width: 180px;
z-index: 200;
}
//
.center-info-card {
background: rgba(64, 169, 255, 0.2);
border: 1px solid rgba(64, 169, 255, 0.3);
z-index: 50;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3), inset 0 1px 0 rgba(255, 255, 255, 0.1);
cursor: pointer;
transition: all 0.3s;
margin-bottom: vw(10);
// &:hover {
// border-color: rgba(64, 169, 255, 0.6);
// box-shadow: 0 6px 30px rgba(64, 169, 255, 0.3), inset 0 1px 0 rgba(255, 255, 255, 0.1);
// transform: translate(-50%, -52%);
// }
.card-title {
font-size: vw(16);
font-weight: 600;
color: #fff;
margin-bottom: vw(12);
background: rgba(64, 169, 255, 0.8);
padding: vw(16) vw(20);
border-bottom: 1px solid rgba(64, 169, 255, 0.1);
}
.card-content {
padding: 0 vw(20) vw(16) vw(20);
display: flex;
justify-content: space-between;
align-items: center;
.info-item {
display: flex;
align-items: baseline;
gap: vw(4);
.info-label {
font-size: vw(13);
color: rgba(255, 255, 255, 0.7);
}
.info-value {
font-size: vw(24);
font-weight: 700;
color: #40a9ff;
text-shadow: 0 0 10px rgba(64, 169, 255, 0.5);
}
.info-unit {
font-size: vw(12);
color: rgba(255, 255, 255, 0.6);
}
&.clickable {
cursor: pointer;
transition: all 0.3s;
&:hover {
.info-value {
color: #69c0ff;
text-shadow: 0 0 15px rgba(105, 192, 255, 0.8);
}
.info-label,
.info-unit {
color: #fff;
}
}
}
}
}
}
</style> </style>

View File

@ -12,6 +12,7 @@
:key="index" :key="index"
class="warning-card" class="warning-card"
:class="item.class" :class="item.class"
@click="handleWarningCardClick(item)"
> >
<img <img
class="card-icon" class="card-icon"
@ -30,7 +31,9 @@
<div class="impact-section"> <div class="impact-section">
<div class="impact-header"> <div class="impact-header">
<div class="impact-title">影响点概况</div> <div class="impact-title">影响点概况</div>
<div class="impact-detail">影响点明细</div> <div class="impact-detail clickable" @click="handleImpactDetailClick">
影响点明细
</div>
</div> </div>
<div class="chart-container"> <div class="chart-container">
<div class="chart-y-label">数量</div> <div class="chart-y-label">数量</div>
@ -87,7 +90,18 @@
<!-- 6个统计项 --> <!-- 6个统计项 -->
<div class="stats-grid"> <div class="stats-grid">
<div v-for="(item, index) in responseStats" :key="index" class="stat-item"> <div
v-for="(item, index) in responseStats"
:key="index"
class="stat-item"
:class="{
clickable:
item.label === '叫应总数' ||
item.label === '已回应数' ||
item.label === '调度区县数',
}"
@click="handleStatClick(item)"
>
<!-- <div class="stat-icon" :class="item.iconClass"></div> --> <!-- <div class="stat-icon" :class="item.iconClass"></div> -->
<img class="stat-icon" :src="item.img" alt="" /> <img class="stat-icon" :src="item.img" alt="" />
<div class="stat-info"> <div class="stat-info">
@ -99,7 +113,18 @@
<!-- 3个调度清单卡片 --> <!-- 3个调度清单卡片 -->
<div class="dispatch-cards"> <div class="dispatch-cards">
<div v-for="(item, index) in dispatchList" :key="index" class="dispatch-card"> <div
v-for="(item, index) in dispatchList"
:key="index"
class="dispatch-card"
:class="{
clickable:
item.label === '国省道调度清单' ||
item.label === '农村公路调度清单' ||
item.label === '建设工程调度清单',
}"
@click="handleDispatchCardClick(item)"
>
<div class="card-num">{{ item.value }}<span class="unit"></span></div> <div class="card-num">{{ item.value }}<span class="unit"></span></div>
<div class="card-label">{{ item.label }}</div> <div class="card-label">{{ item.label }}</div>
</div> </div>
@ -113,6 +138,57 @@ import { ref } from "vue";
import SectionHeader from "./component/sectionHeader.vue"; import SectionHeader from "./component/sectionHeader.vue";
const emit = defineEmits([
"openWarningInfo",
"openImpactPoint",
"openWarningSituation",
"openResponseStatus",
"openDispatchDistrict",
"openImpactDetail",
"showCenterCard",
]);
//
const handleStatClick = (item) => {
if (item.label === "叫应总数") {
emit("openWarningInfo");
} else if (item.label === "已回应数") {
emit("openResponseStatus");
} else if (item.label === "调度区县数") {
emit("openDispatchDistrict");
}
};
//
const handleWarningCardClick = (item) => {
emit("openWarningSituation", item);
};
//
const handleImpactDetailClick = () => {
emit("openImpactDetail");
};
//
const handleDispatchCardClick = (item) => {
if (item.label === "建设工程调度清单") {
emit("showCenterCard", {
type: "first",
value: item.value,
});
} else if (item.label === "国省道调度清单") {
emit("showCenterCard", {
type: "second",
value: item.value,
});
} else if (item.label === "农村公路调度清单") {
emit("showCenterCard", {
type: "third",
value: item.value,
});
}
};
// //
import imgCall from "../../assets/RiskWarning_img/回应icon@2x.png"; import imgCall from "../../assets/RiskWarning_img/回应icon@2x.png";
import imgReply from "../../assets/RiskWarning_img/已回应icon@2x.png"; import imgReply from "../../assets/RiskWarning_img/已回应icon@2x.png";
@ -153,9 +229,11 @@ const getBarHeight = (value) => {
// //
const districtData = [ const districtData = [
{ name: "江北区", road: 1, bridge: 1, tunnel: 1, slope: 8, project: 11 },
{ name: "江北区", road: 1, bridge: 1, tunnel: 1, slope: 8, project: 11 }, { name: "江北区", road: 1, bridge: 1, tunnel: 1, slope: 8, project: 11 },
{ name: "南岸区", road: 1, bridge: 2, tunnel: 2, slope: 6, project: 12 }, { name: "南岸区", road: 1, bridge: 2, tunnel: 2, slope: 6, project: 12 },
{ name: "九龙坡区", road: 2, bridge: 1, tunnel: 1, slope: 9, project: 9 }, { name: "九龙坡区", road: 2, bridge: 1, tunnel: 1, slope: 9, project: 9 },
{ name: "九龙坡区", road: 2, bridge: 1, tunnel: 1, slope: 9, project: 9 },
{ name: "万州区", road: 1, bridge: 2, tunnel: 2, slope: 11, project: 7 }, { name: "万州区", road: 1, bridge: 2, tunnel: 2, slope: 11, project: 7 },
]; ];
@ -227,10 +305,16 @@ const cellStyle = () => ({
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
// -
// 1920px使 vw
@function vw($px) {
@return calc($px / 1920 * 100vw);
}
.left-panel { .left-panel {
width: 100%; width: 100%;
height: 100%; height: 100%;
padding: 20px; padding: vw(20);
box-sizing: border-box; box-sizing: border-box;
overflow-y: auto; overflow-y: auto;
scrollbar-width: none; scrollbar-width: none;
@ -240,12 +324,22 @@ const cellStyle = () => ({
display: none; display: none;
} }
//
@media (max-width: 1366px) {
padding: 12px;
}
@media (max-width: 1024px) {
padding: 8px;
}
.section-header { .section-header {
height: 50px; height: vw(50);
min-height: 40px;
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;
margin-bottom: 20px; margin-bottom: vw(20);
background-image: url("../../assets/RiskWarning_img/标题bg@2x.png"); background-image: url("../../assets/RiskWarning_img/标题bg@2x.png");
background-size: cover; background-size: cover;
background-position: left; background-position: left;
@ -253,15 +347,17 @@ const cellStyle = () => ({
.header-left { .header-left {
display: flex; display: flex;
align-items: center; align-items: center;
gap: 8px; gap: vw(8);
margin-left: 35px; margin-left: vw(35);
color: #fff; color: #fff;
font-size: 24px; font-size: vw(24);
font-weight: bold; font-weight: bold;
.icon-back { .icon-back {
width: 20px; width: vw(20);
height: 20px; height: vw(20);
min-width: 16px;
min-height: 16px;
background: linear-gradient(135deg, #40a9ff 0%, #1890ff 100%); background: linear-gradient(135deg, #40a9ff 0%, #1890ff 100%);
border-radius: 4px; border-radius: 4px;
display: flex; display: flex;
@ -271,44 +367,44 @@ const cellStyle = () => ({
&::before { &::before {
content: "←"; content: "←";
color: #fff; color: #fff;
font-size: 12px; font-size: vw(12);
} }
} }
.title { .title {
font-size: 18px; font-size: vw(16);
font-weight: bold; font-weight: bold;
color: #fff; color: #fff;
} }
} }
.header-date { .header-date {
font-size: 12px; font-size: vw(11);
color: rgba(255, 255, 255, 0.6); color: rgba(255, 255, 255, 0.6);
} }
} }
// //
.weather-warning-section { .weather-warning-section {
margin-bottom: 20px; margin-bottom: vw(20);
.section-title { .section-title {
font-size: 18px; font-size: vw(16);
font-weight: 600; font-weight: 600;
color: rgba(255, 255, 255, 0.9); color: rgba(255, 255, 255, 0.9);
margin-bottom: 12px; margin-bottom: vw(12);
} }
.warning-cards { .warning-cards {
display: grid; display: grid;
grid-template-columns: repeat(2, 1fr); grid-template-columns: repeat(2, 1fr);
gap: 10px; gap: vw(10);
display: flex; display: flex;
.warning-card { .warning-card {
display: flex; display: flex;
align-items: center; align-items: center;
gap: 10px; gap: vw(10);
flex: 1; flex: 1;
// padding: 12px; // padding: 12px;
background: rgba(64, 169, 255, 0.1); background: rgba(64, 169, 255, 0.1);
@ -317,12 +413,12 @@ const cellStyle = () => ({
.card-icon { .card-icon {
width: 100%; width: 100%;
max-width: 40px; max-width: vw(35);
height: auto; height: auto;
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
font-size: 28px; font-size: vw(24);
// &::before { // &::before {
// content: ""; // content: "";
@ -332,13 +428,13 @@ const cellStyle = () => ({
.card-info { .card-info {
flex: 1; flex: 1;
.card-num { .card-num {
font-size: 30px; font-size: vw(24);
font-weight: bold; font-weight: bold;
margin-bottom: 2px; margin-bottom: 2px;
} }
.card-label { .card-label {
font-size: 14px; font-size: vw(12);
color: rgba(255, 255, 255, 0.7); color: rgba(255, 255, 255, 0.7);
} }
} }
@ -361,37 +457,47 @@ const cellStyle = () => ({
// //
.impact-section { .impact-section {
margin-bottom: 40px; margin-bottom: vw(40);
.impact-header { .impact-header {
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;
margin-bottom: 20px; margin-bottom: vw(20);
.impact-title { .impact-title {
font-size: 18px; font-size: vw(16);
color: rgba(255, 255, 255, 0.9); color: rgba(255, 255, 255, 0.9);
} }
.impact-detail { .impact-detail {
font-size: 16px; font-size: vw(14);
color: #40a9ff; color: #40a9ff;
cursor: pointer; cursor: pointer;
&.clickable {
transition: all 0.3s;
&:hover {
color: #69c0ff;
text-shadow: 0 0 8px rgba(105, 192, 255, 0.6);
}
}
} }
} }
.chart-container { .chart-container {
position: relative; position: relative;
height: 120px; height: vw(120);
min-height: 80px;
display: flex; display: flex;
// padding: 10px 0 30px 40px; // padding: 10px 0 30px 40px;
.chart-y-label { .chart-y-label {
position: absolute; position: absolute;
left: 0; left: 0;
top: -10px; top: vw(-10);
font-size: 11px; font-size: vw(11);
color: rgba(255, 255, 255, 0.6); color: rgba(255, 255, 255, 0.6);
} }
@ -425,7 +531,7 @@ const cellStyle = () => ({
flex: 1; flex: 1;
cursor: pointer; cursor: pointer;
transition: all 0.3s ease; transition: all 0.3s ease;
padding: 5px 0; padding: vw(5) 0;
border-radius: 4px; border-radius: 4px;
&:hover { &:hover {
@ -451,15 +557,16 @@ const cellStyle = () => ({
} }
.bar-value { .bar-value {
font-size: 14px; font-size: vw(12);
font-weight: bold; font-weight: bold;
color: rgba(255, 255, 255, 0.9); color: rgba(255, 255, 255, 0.9);
margin-bottom: 5px; margin-bottom: vw(5);
transition: all 0.3s ease; transition: all 0.3s ease;
} }
.bar { .bar {
width: 30px; width: vw(30);
min-width: 16px;
background: linear-gradient(180deg, #40a9ff 0%, rgba(64, 169, 255, 0.3) 100%); background: linear-gradient(180deg, #40a9ff 0%, rgba(64, 169, 255, 0.3) 100%);
border-radius: 2px 2px 0 0; border-radius: 2px 2px 0 0;
min-height: 20px; min-height: 20px;
@ -467,8 +574,8 @@ const cellStyle = () => ({
} }
.bar-label { .bar-label {
bottom: -20px; bottom: vw(-20);
font-size: 11px; font-size: vw(10);
color: rgba(255, 255, 255, 0.7); color: rgba(255, 255, 255, 0.7);
transition: all 0.3s ease; transition: all 0.3s ease;
position: absolute; position: absolute;
@ -478,9 +585,9 @@ const cellStyle = () => ({
.chart-x-label { .chart-x-label {
position: absolute; position: absolute;
right: -15px; right: vw(-15);
bottom: 0; bottom: 0;
font-size: 11px; font-size: vw(10);
color: rgba(255, 255, 255, 0.6); color: rgba(255, 255, 255, 0.6);
} }
} }
@ -530,21 +637,14 @@ const cellStyle = () => ({
// //
.response-section { .response-section {
background: linear-gradient( padding: vw(15);
180deg, margin-top: vw(20);
rgba(21, 41, 59, 0.95) 0%,
rgba(13, 28, 42, 0.95) 100%
);
border: 1px solid rgba(64, 169, 255, 0.3);
border-radius: 8px;
padding: 15px;
margin-top: 20px;
.section-header { .section-header {
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;
margin-bottom: 15px; margin-bottom: vw(15);
background-image: url("../../assets/RiskWarning_img/标题bg@2x.png") no-repeat; background-image: url("../../assets/RiskWarning_img/标题bg@2x.png") no-repeat;
background-size: cover; background-size: cover;
background-position: left; background-position: left;
@ -552,11 +652,13 @@ const cellStyle = () => ({
.header-left { .header-left {
display: flex; display: flex;
align-items: center; align-items: center;
gap: 8px; gap: vw(8);
.icon-back { .icon-back {
width: 20px; width: vw(20);
height: 20px; height: vw(20);
min-width: 16px;
min-height: 16px;
background: linear-gradient(135deg, #40a9ff 0%, #1890ff 100%); background: linear-gradient(135deg, #40a9ff 0%, #1890ff 100%);
border-radius: 4px; border-radius: 4px;
display: flex; display: flex;
@ -566,12 +668,12 @@ const cellStyle = () => ({
&::before { &::before {
content: "←"; content: "←";
color: #fff; color: #fff;
font-size: 12px; font-size: vw(12);
} }
} }
.title { .title {
font-size: 16px; font-size: vw(14);
font-weight: bold; font-weight: bold;
color: #fff; color: #fff;
} }
@ -580,12 +682,12 @@ const cellStyle = () => ({
.header-filters { .header-filters {
display: flex; display: flex;
align-items: center; align-items: center;
gap: 6px; gap: vw(6);
font-size: 11px; font-size: vw(10);
.filter-item { .filter-item {
color: rgba(255, 255, 255, 0.6); color: rgba(255, 255, 255, 0.6);
padding: 3px 8px; padding: vw(3) vw(8);
border: 1px solid rgba(64, 169, 255, 0.3); border: 1px solid rgba(64, 169, 255, 0.3);
border-radius: 4px; border-radius: 4px;
cursor: pointer; cursor: pointer;
@ -607,25 +709,25 @@ const cellStyle = () => ({
.stats-grid { .stats-grid {
display: grid; display: grid;
grid-template-columns: repeat(3, 1fr); grid-template-columns: repeat(3, 1fr);
gap: 10px; gap: vw(10);
margin-bottom: 15px; margin-bottom: vw(15);
.stat-item { .stat-item {
display: flex; display: flex;
align-items: center; align-items: center;
gap: 8px; gap: vw(8);
padding: 10px; padding: vw(10);
background: rgba(64, 169, 255, 0.08); background: rgba(64, 169, 255, 0.08);
border-radius: 6px; border-radius: 6px;
.stat-icon { .stat-icon {
width: 100%; width: 100%;
max-width: 45px; max-width: vw(40);
height: auto; height: auto;
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
font-size: 20px; font-size: vw(18);
&.icon-call::before { &.icon-call::before {
content: "💬"; content: "💬";
@ -649,17 +751,32 @@ const cellStyle = () => ({
.stat-info { .stat-info {
.stat-num { .stat-num {
font-size: 30px; font-size: vw(24);
font-weight: bold; font-weight: bold;
color: #40a9ff; color: #40a9ff;
margin-bottom: 2px; margin-bottom: 2px;
} }
.stat-label { .stat-label {
font-size: 14px; font-size: vw(12);
color: #ffffff; color: #ffffff;
} }
} }
&.clickable {
cursor: pointer;
transition: all 0.3s;
&:hover {
background: rgba(64, 169, 255, 0.2);
transform: translateY(-2px);
.stat-num {
color: #69c0ff;
text-shadow: 0 0 10px rgba(105, 192, 255, 0.5);
}
}
}
} }
} }
@ -667,23 +784,38 @@ const cellStyle = () => ({
.dispatch-cards { .dispatch-cards {
display: grid; display: grid;
grid-template-columns: repeat(3, 1fr); grid-template-columns: repeat(3, 1fr);
gap: 10px; gap: vw(10);
.dispatch-card { .dispatch-card {
padding: 12px; padding: vw(12);
background: rgba(64, 169, 255, 0.1); background: rgba(64, 169, 255, 0.1);
border: 1px solid rgba(64, 169, 255, 0.2); border: 1px solid rgba(64, 169, 255, 0.2);
border-radius: 6px; border-radius: 6px;
text-align: center; text-align: center;
&.clickable {
cursor: pointer;
transition: all 0.3s;
&:hover {
background: rgba(64, 169, 255, 0.2);
transform: translateY(-2px);
.card-num {
color: #69c0ff;
text-shadow: 0 0 10px rgba(105, 192, 255, 0.5);
}
}
}
.card-num { .card-num {
font-size: 24px; font-size: vw(20);
font-weight: bold; font-weight: bold;
color: #40a9ff; color: #40a9ff;
margin-bottom: 6px; margin-bottom: vw(6);
.unit { .unit {
font-size: 14px; font-size: vw(12);
color: rgba(255, 255, 255, 0.7); color: rgba(255, 255, 255, 0.7);
font-weight: normal; font-weight: normal;
margin-left: 2px; margin-left: 2px;
@ -691,7 +823,7 @@ const cellStyle = () => ({
} }
.card-label { .card-label {
font-size: 11px; font-size: vw(10);
color: rgba(255, 255, 255, 0.8); color: rgba(255, 255, 255, 0.8);
} }
} }
@ -701,7 +833,8 @@ const cellStyle = () => ({
.date-range-wrapper { .date-range-wrapper {
:deep(.el-date-editor) { :deep(.el-date-editor) {
width: 200px; width: vw(200);
min-width: 140px;
background: transparent; background: transparent;
border: 1px solid rgba(64, 169, 255, 0.3); border: 1px solid rgba(64, 169, 255, 0.3);
border-radius: 4px; border-radius: 4px;

View File

@ -21,7 +21,13 @@
<div class="control-section"> <div class="control-section">
<div class="control-title">管控路段数 <span class="control-num">2</span></div> <div class="control-title">管控路段数 <span class="control-num">2</span></div>
<div class="control-grid"> <div class="control-grid">
<div v-for="(item, index) in controlData" :key="index" class="control-item"> <div
v-for="(item, index) in controlData"
:key="index"
class="control-item"
:class="{ clickable: item.label === '全幅封闭数' || item.label === '关闭驻地数' }"
@click="handleControlClick(item)"
>
<div class="control-value">{{ item.value }}</div> <div class="control-value">{{ item.value }}</div>
<div class="control-label">{{ item.label }}</div> <div class="control-label">{{ item.label }}</div>
</div> </div>
@ -83,7 +89,7 @@
</SectionHeader> </SectionHeader>
<!-- 阻断情况 --> <!-- 阻断情况 -->
<div class="block-section"> <div class="block-section clickable" @click="handleBlockClick">
<div class="block-title">阻断情况(已抢通/阻断数)</div> <div class="block-title">阻断情况(已抢通/阻断数)</div>
<div class="block-grid"> <div class="block-grid">
<div class="block-item"> <div class="block-item">
@ -147,6 +153,22 @@ import { ref, computed } from "vue";
import SectionHeader from "./component/sectionHeader.vue"; import SectionHeader from "./component/sectionHeader.vue";
import { Calendar } from "@element-plus/icons-vue"; import { Calendar } from "@element-plus/icons-vue";
const emit = defineEmits(["openClearanceSituation", "openControlSituation"]);
//
const handleControlClick = (item) => {
if (item.label === "全幅封闭数") {
emit("openClearanceSituation");
} else if (item.label === "关闭驻地数") {
emit("openControlSituation");
}
};
//
const handleBlockClick = () => {
emit("openClearanceSituation");
};
import icon1 from "../../assets/RiskWarning_img/icon1@2x.png"; import icon1 from "../../assets/RiskWarning_img/icon1@2x.png";
import icon2 from "../../assets/RiskWarning_img/icon2@2x.png"; import icon2 from "../../assets/RiskWarning_img/icon2@2x.png";
import icon3 from "../../assets/RiskWarning_img/icon3@2x.png"; import icon3 from "../../assets/RiskWarning_img/icon3@2x.png";
@ -250,10 +272,16 @@ const majorEvent = "0";
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
// -
// 1920px使 vw
@function vw($px) {
@return calc($px / 1920 * 100vw);
}
.right-panel { .right-panel {
width: 100%; width: 100%;
height: 100%; height: 100%;
padding: 20px; padding: vw(20);
box-sizing: border-box; box-sizing: border-box;
overflow-y: auto; overflow-y: auto;
scrollbar-width: none; scrollbar-width: none;
@ -262,6 +290,15 @@ const majorEvent = "0";
&::-webkit-scrollbar { &::-webkit-scrollbar {
display: none; display: none;
} }
//
@media (max-width: 1366px) {
padding: 12px;
}
@media (max-width: 1024px) {
padding: 8px;
}
} }
.prevention-section { .prevention-section {
@ -272,7 +309,6 @@ const majorEvent = "0";
// ); // );
// border: 1px solid rgba(64, 169, 255, 0.3); // border: 1px solid rgba(64, 169, 255, 0.3);
border-radius: 8px; border-radius: 8px;
padding: 15px;
} }
.section-header { .section-header {
@ -280,16 +316,18 @@ const majorEvent = "0";
justify-content: space-between; justify-content: space-between;
background-image: url("../../assets/RiskWarning_img/标题bg@2x.png"); background-image: url("../../assets/RiskWarning_img/标题bg@2x.png");
align-items: center; align-items: center;
margin-bottom: 15px; margin-bottom: vw(15);
.header-left { .header-left {
display: flex; display: flex;
align-items: center; align-items: center;
gap: 8px; gap: vw(8);
.icon-back { .icon-back {
width: 20px; width: vw(20);
height: 20px; height: vw(20);
min-width: 16px;
min-height: 16px;
background: linear-gradient(135deg, #40a9ff 0%, #1890ff 100%); background: linear-gradient(135deg, #40a9ff 0%, #1890ff 100%);
border-radius: 4px; border-radius: 4px;
display: flex; display: flex;
@ -299,12 +337,12 @@ const majorEvent = "0";
&::before { &::before {
content: "←"; content: "←";
color: #fff; color: #fff;
font-size: 12px; font-size: vw(12);
} }
} }
.title { .title {
font-size: 18px; font-size: vw(16);
font-weight: bold; font-weight: bold;
color: #fff; color: #fff;
} }
@ -315,14 +353,14 @@ const majorEvent = "0";
.resource-grid { .resource-grid {
display: grid; display: grid;
grid-template-columns: repeat(2, 1fr); grid-template-columns: repeat(2, 1fr);
gap: 10px; gap: vw(10);
margin-bottom: 15px; margin-bottom: vw(10);
.resource-item { .resource-item {
display: flex; display: flex;
align-items: center; align-items: center;
gap: 10px; gap: vw(10);
padding: 12px; padding: vw(8) vw(12);
background: linear-gradient(270deg, rgba(18, 84, 97, 0) 0%, #204a55 100%); background: linear-gradient(270deg, rgba(18, 84, 97, 0) 0%, #204a55 100%);
border: 2px solid transparent; border: 2px solid transparent;
border-image: linear-gradient(270deg, rgba(80, 201, 191, 0), rgba(39, 135, 153, 1)) 2 border-image: linear-gradient(270deg, rgba(80, 201, 191, 0), rgba(39, 135, 153, 1)) 2
@ -331,12 +369,14 @@ const majorEvent = "0";
border-right: 0px; border-right: 0px;
.resource-icon { .resource-icon {
width: 36px; width: vw(32);
height: 36px; height: vw(32);
min-width: 20px;
min-height: 20px;
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
font-size: 22px; font-size: vw(20);
&.icon-team::before { &.icon-team::before {
content: "👷"; content: "👷";
@ -359,18 +399,18 @@ const majorEvent = "0";
align-items: center; align-items: center;
.resource-label { .resource-label {
width: 48%; width: 48%;
font-size: 14px; font-size: vw(12);
color: rgba(255, 255, 255, 0.7); color: rgba(255, 255, 255, 0.7);
margin-bottom: 4px; margin-bottom: vw(4);
} }
.resource-value { .resource-value {
font-size: 22px; font-size: vw(18);
font-weight: bold; font-weight: bold;
color: #40a9ff; color: #40a9ff;
.unit { .unit {
font-size: 14px; font-size: vw(12);
color: rgba(255, 255, 255, 0.7); color: rgba(255, 255, 255, 0.7);
font-weight: normal; font-weight: normal;
margin-left: 2px; margin-left: 2px;
@ -382,44 +422,58 @@ const majorEvent = "0";
// //
.control-section { .control-section {
margin-bottom: 15px; margin-bottom: vw(10);
.control-title { .control-title {
font-size: 16px; font-size: vw(14);
color: rgba(255, 255, 255, 0.9); color: rgba(255, 255, 255, 0.9);
margin-bottom: 10px; margin-bottom: vw(10);
.control-num { .control-num {
font-size: 18px; font-size: vw(16);
color: #40a9ff; color: #40a9ff;
font-weight: bold; font-weight: bold;
margin-left: 5px; margin-left: vw(5);
} }
} }
.control-grid { .control-grid {
display: grid; display: grid;
grid-template-columns: repeat(4, 1fr); grid-template-columns: repeat(4, 1fr);
background: #182f4c; background: rgba(64, 169, 255, 0.1);
box-shadow: inset 0px 0px 8px 0px #379bff; box-shadow: inset 0px 0px 8px 0px #379bff;
gap: 8px; gap: vw(8);
.control-item { .control-item {
text-align: center; text-align: center;
padding: 10px 5px; padding: vw(6) vw(5);
// background: rgba(64, 169, 255, 0.1); // background: rgba(64, 169, 255, 0.1);
border-radius: 4px; border-radius: 4px;
&.clickable {
cursor: pointer;
transition: all 0.3s;
&:hover {
background-color: rgba(64, 169, 255, 0.15);
.control-value {
color: #69c0ff;
text-shadow: 0 0 8px rgba(105, 192, 255, 0.6);
}
}
}
.control-value { .control-value {
font-size: 24px; font-size: vw(18);
font-weight: bold; font-weight: bold;
color: #40a9ff; color: #40a9ff;
margin-bottom: 4px; margin-bottom: vw(4);
} }
.control-label { .control-label {
font-size: 14px; font-size: vw(12);
color: rgba(255, 255, 255, 0.7); color: rgba(255, 255, 255, 0.7);
} }
} }
@ -428,21 +482,21 @@ const majorEvent = "0";
// //
.patrol-section { .patrol-section {
margin-bottom: 15px; margin-bottom: vw(10);
.patrol-header { .patrol-header {
display: flex; display: flex;
align-items: center; align-items: center;
gap: 10px; gap: vw(10);
margin-bottom: 10px; margin-bottom: vw(10);
.patrol-title { .patrol-title {
font-size: 16px; font-size: vw(14);
color: rgba(255, 255, 255, 0.9); color: rgba(255, 255, 255, 0.9);
} }
.patrol-mileage { .patrol-mileage {
font-size: 18px; font-size: vw(16);
font-weight: bold; font-weight: bold;
color: #40a9ff; color: #40a9ff;
} }
@ -451,11 +505,11 @@ const majorEvent = "0";
.patrol-grid { .patrol-grid {
display: grid; display: grid;
grid-template-columns: repeat(5, 1fr); grid-template-columns: repeat(5, 1fr);
gap: 8px; gap: vw(8);
.patrol-item { .patrol-item {
text-align: center; text-align: center;
padding: 10px 5px; padding: vw(6) vw(5);
// background: rgba(64, 169, 255, 0.1); // background: rgba(64, 169, 255, 0.1);
// border-radius: 4px; // border-radius: 4px;
background-image: url("../../assets/RiskWarning_img/路径62@2x (1).png"); background-image: url("../../assets/RiskWarning_img/路径62@2x (1).png");
@ -463,16 +517,16 @@ const majorEvent = "0";
background-position: right; background-position: right;
.patrol-value { .patrol-value {
font-size: 24px; font-size: vw(18);
font-weight: bold; font-weight: bold;
color: #40a9ff; color: #40a9ff;
margin-bottom: 4px; margin-bottom: vw(4);
text-align: left; text-align: left;
margin-left: 5px; margin-left: vw(5);
} }
.patrol-label { .patrol-label {
font-size: 14px; font-size: vw(12);
color: rgba(255, 255, 255, 0.7); color: rgba(255, 255, 255, 0.7);
} }
} }
@ -482,21 +536,21 @@ const majorEvent = "0";
// //
.rescue-section { .rescue-section {
.rescue-title { .rescue-title {
font-size: 16px; font-size: vw(14);
color: rgba(255, 255, 255, 0.9); color: rgba(255, 255, 255, 0.9);
margin-bottom: 10px; margin-bottom: vw(10);
} }
.rescue-grid { .rescue-grid {
display: grid; display: grid;
grid-template-columns: repeat(3, 1fr); grid-template-columns: repeat(3, 1fr);
gap: 10px; gap: vw(10);
.rescue-item { .rescue-item {
display: flex; display: flex;
align-items: center; align-items: center;
gap: 4px; gap: vw(4);
padding: 12px 0; padding: vw(8) 0;
// background: rgba(64, 169, 255, 0.1); // background: rgba(64, 169, 255, 0.1);
// border: 1px solid rgba(64, 169, 255, 0.2); // border: 1px solid rgba(64, 169, 255, 0.2);
// border-radius: 6px; // border-radius: 6px;
@ -505,13 +559,15 @@ const majorEvent = "0";
background-position: right; background-position: right;
.rescue-icon { .rescue-icon {
width: 40px; width: vw(36);
height: 40px; height: vw(36);
min-width: 24px;
min-height: 24px;
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
font-size: 24px; font-size: vw(22);
margin-left: 5px; margin-left: vw(5);
&.icon-rescue-person::before { &.icon-rescue-person::before {
content: "👷"; content: "👷";
@ -526,15 +582,15 @@ const majorEvent = "0";
.rescue-info { .rescue-info {
.rescue-value { .rescue-value {
font-size: 22px; font-size: vw(18);
font-weight: bold; font-weight: bold;
color: #40a9ff; color: #40a9ff;
margin-bottom: 4px; margin-bottom: vw(4);
display: flex; display: flex;
align-items: center; align-items: center;
.unit { .unit {
font-size: 10px; font-size: vw(10);
color: rgba(255, 255, 255, 0.7); color: rgba(255, 255, 255, 0.7);
font-weight: normal; font-weight: normal;
margin-left: 2px; margin-left: 2px;
@ -542,7 +598,7 @@ const majorEvent = "0";
} }
.rescue-label { .rescue-label {
font-size: 14px; font-size: vw(12);
color: rgba(255, 255, 255, 0.7); color: rgba(255, 255, 255, 0.7);
} }
} }
@ -552,30 +608,35 @@ const majorEvent = "0";
// //
.disaster-section { .disaster-section {
background: linear-gradient( margin-top: vw(15);
180deg,
rgba(21, 41, 59, 0.95) 0%,
rgba(13, 28, 42, 0.95) 100%
);
border: 1px solid rgba(64, 169, 255, 0.3);
border-radius: 8px;
padding: 15px;
margin-top: 15px;
.block-section { .block-section {
margin-bottom: 15px; margin-bottom: vw(10);
&.clickable {
cursor: pointer;
transition: all 0.3s;
&:hover {
opacity: 0.9;
.block-title {
color: #69c0ff;
}
}
}
.block-title { .block-title {
font-size: 16px; font-size: vw(14);
color: rgba(255, 255, 255, 0.8); color: rgba(255, 255, 255, 0.8);
margin-bottom: 10px; margin-bottom: vw(10);
} }
.block-grid { .block-grid {
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
height: 100%; height: 100%;
padding: 0 20px; padding: 0 vw(20);
// display: grid; // display: grid;
// grid-template-columns: repeat(5, 1fr); // grid-template-columns: repeat(5, 1fr);
// gap: 10px; // gap: 10px;
@ -585,7 +646,7 @@ const majorEvent = "0";
.divider-line { .divider-line {
width: 2px; width: 2px;
height: 40px; height: vw(40);
margin: auto 0; margin: auto 0;
background: linear-gradient( background: linear-gradient(
180deg, 180deg,
@ -598,7 +659,7 @@ const majorEvent = "0";
.block-item { .block-item {
text-align: center; text-align: center;
padding: 12px 8px; padding: vw(8);
// background: rgba(64, 169, 255, 0.1); // background: rgba(64, 169, 255, 0.1);
// border-radius: 6px; // border-radius: 6px;
// background-image: url("../../assets/RiskWarning_img/62@2x (1).png"); // background-image: url("../../assets/RiskWarning_img/62@2x (1).png");
@ -606,9 +667,9 @@ const majorEvent = "0";
// background-position: left; // background-position: left;
.block-num { .block-num {
font-size: 24px; font-size: vw(18);
font-weight: bold; font-weight: bold;
margin-bottom: 6px; margin-bottom: vw(6);
.current { .current {
color: #40a9ff; color: #40a9ff;
@ -625,20 +686,20 @@ const majorEvent = "0";
} }
.block-label { .block-label {
font-size: 14px; font-size: vw(12);
color: rgba(255, 255, 255, 0.7); color: rgba(255, 255, 255, 0.7);
} }
&.death-item { &.death-item {
.death-num { .death-num {
font-size: 28px; font-size: vw(18);
font-weight: bold; font-weight: bold;
color: #40a9ff; color: #40a9ff;
margin-bottom: 6px; margin-bottom: vw(6);
} }
.death-label { .death-label {
font-size: 11px; font-size: vw(10);
color: rgba(255, 255, 255, 0.7); color: rgba(255, 255, 255, 0.7);
} }
} }
@ -647,22 +708,22 @@ const majorEvent = "0";
} }
.damage-section { .damage-section {
margin-bottom: 15px; margin-bottom: vw(10);
.damage-title { .damage-title {
font-size: 16px; font-size: vw(14);
color: rgba(255, 255, 255, 0.8); color: rgba(255, 255, 255, 0.8);
margin-bottom: 10px; margin-bottom: vw(10);
} }
.damage-grid { .damage-grid {
display: grid; display: grid;
grid-template-columns: repeat(3, 1fr); grid-template-columns: repeat(3, 1fr);
gap: 10px; gap: vw(10);
.damage-item { .damage-item {
text-align: center; text-align: center;
padding: 12px 8px; padding: vw(8);
// background: rgba(64, 169, 255, 0.1); // background: rgba(64, 169, 255, 0.1);
// border-radius: 6px; // border-radius: 6px;
background-image: url("../../assets/RiskWarning_img/路径62@2x.png"); background-image: url("../../assets/RiskWarning_img/路径62@2x.png");
@ -670,19 +731,19 @@ const majorEvent = "0";
background-position: left; background-position: left;
.damage-value { .damage-value {
font-size: 28px; font-size: vw(18);
font-weight: bold; font-weight: bold;
margin-bottom: 6px; margin-bottom: vw(6);
.unit { .unit {
font-size: 14px; font-size: vw(12);
font-weight: normal; font-weight: normal;
margin-left: 2px; margin-left: 2px;
} }
} }
.damage-label { .damage-label {
font-size: 14px; font-size: vw(12);
color: rgba(255, 255, 255, 0.7); color: rgba(255, 255, 255, 0.7);
} }
@ -705,7 +766,7 @@ const majorEvent = "0";
.event-section { .event-section {
text-align: center; text-align: center;
padding: 12px; padding: vw(12);
// background: rgba(64, 169, 255, 0.1); // background: rgba(64, 169, 255, 0.1);
// border-radius: 6px; // border-radius: 6px;
background-image: url("../../assets/RiskWarning_img/编组5@2x.png"); background-image: url("../../assets/RiskWarning_img/编组5@2x.png");
@ -713,12 +774,12 @@ const majorEvent = "0";
background-position: left; background-position: left;
.event-title { .event-title {
font-size: 14px; font-size: vw(12);
color: rgba(255, 255, 255, 0.9); color: rgba(255, 255, 255, 0.9);
} }
.event-num { .event-num {
font-size: 24px; font-size: vw(18);
font-weight: bold; font-weight: bold;
color: #ff4d4f; color: #ff4d4f;
} }
@ -727,12 +788,12 @@ const majorEvent = "0";
.header-filters { .header-filters {
display: flex; display: flex;
align-items: center; align-items: center;
gap: 6px; gap: vw(6);
font-size: 11px; font-size: vw(10);
.filter-item { .filter-item {
color: rgba(255, 255, 255, 0.6); color: rgba(255, 255, 255, 0.6);
padding: 3px 8px; padding: vw(3) vw(8);
border: 1px solid rgba(64, 169, 255, 0.3); border: 1px solid rgba(64, 169, 255, 0.3);
border-radius: 4px; border-radius: 4px;
cursor: pointer; cursor: pointer;

View File

@ -15,7 +15,7 @@
/> />
</div> </div>
</div> </div>
<img class="filter-icon-ai" src="../../assets/RiskWarning_img/AI1@2x.png" alt="" /> <img class="filter-icon-ai" src="../../assets/RiskWarning_img/AI1@2x.png" alt="" @click="handleAIClick" />
</div> </div>
</template> </template>
@ -23,28 +23,51 @@
import { ref } from "vue"; import { ref } from "vue";
import { Calendar } from "@element-plus/icons-vue"; import { Calendar } from "@element-plus/icons-vue";
const emit = defineEmits(["openAIResult"]);
const dateRange = ref([]); const dateRange = ref([]);
const handleAIClick = () => {
emit("openAIResult");
};
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
// -
// 1920px使 vw
@function vw($px) {
@return calc($px / 1920 * 100vw);
}
.filter-header { .filter-header {
padding: 10px; padding: vw(10);
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
// align-items: center; // align-items: center;
//
@media (max-width: 1366px) {
padding: 8px;
}
@media (max-width: 1024px) {
padding: 6px;
}
} }
.filter-container { .filter-container {
display: flex; display: flex;
align-items: center; align-items: center;
height: 20px; height: vw(20);
gap: 8px; min-height: 18px;
font-size: 13px; gap: vw(8);
font-size: vw(13);
.filter-item { .filter-item {
color: rgba(255, 255, 255, 0.6); color: rgba(255, 255, 255, 0.6);
padding: 0 12px; padding: 0 vw(12);
height: 24px; height: vw(24);
min-height: 20px;
border: 1px solid rgba(64, 169, 255, 0.3); border: 1px solid rgba(64, 169, 255, 0.3);
border-radius: 4px; border-radius: 4px;
cursor: pointer; cursor: pointer;
@ -67,7 +90,8 @@ const dateRange = ref([]);
.date-range-wrapper { .date-range-wrapper {
:deep(.el-date-editor) { :deep(.el-date-editor) {
width: 200px; width: vw(200);
min-width: 140px;
background: transparent; background: transparent;
border: 1px solid rgba(64, 169, 255, 0.3); border: 1px solid rgba(64, 169, 255, 0.3);
border-radius: 4px; border-radius: 4px;
@ -98,8 +122,10 @@ const dateRange = ref([]);
} }
} }
.filter-icon-ai { .filter-icon-ai {
width: 67px; width: vw(67);
height: 67px; height: vw(67);
min-width: 48px;
min-height: 48px;
cursor: pointer; cursor: pointer;
} }
// //

File diff suppressed because it is too large Load Diff