bxztApp/packages/screen/src/views/RiskWarning/component/responsePointDetailDialog.vue
2026-04-02 16:35:45 +08:00

679 lines
14 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="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: 80vw;
max-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>