bxztApp/packages/screen/src/views/RiskWarning/component/riskPointDetailDialog.vue

785 lines
18 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

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

<template>
<div 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>