2025-11-19 18:19:41 +08:00

392 lines
8.5 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>
<transition name="fade">
<div v-if="visible && position" class="common-tooltip" :style="{
left: `${position.x}px`,
top: `${position.y - 20}px`
}">
<span class="title">{{ title }}</span>
<!-- 关闭按钮 -->
<button class="close-button" type="button" aria-label="关闭" @click="handleClose">
×
</button>
<!-- 内容区域 -->
<div class="tooltip-content" v-if="!loading && detail">
<div class="info-item">
<span class="label">名称:</span>
<span class="value">{{ detail.mc }}</span>
</div>
<div class="info-item">
<span class="label">所属区县</span>
<span class="value">{{ detail.qxmc }}</span>
</div>
<div class="info-item">
<span class="label">应急设备</span>
<span class="value">{{ detail.sbsl }}</span>
</div>
<div class="info-item">
<span class="label">应急物资</span>
<span class="value">{{ detail.wzsl }}</span>
</div>
<div class="info-item">
<span class="label">应急人员</span>
<span class="value">{{ detail.rysl }}</span>
</div>
<!-- 如果没有数据 -->
<div v-if="!hasData" class="no-data">
暂无详细信息
</div>
</div>
<!-- 加载状态 -->
<div v-if="loading" class="loading-overlay">
<div class="loading-spinner"></div>
<span>加载中...</span>
</div>
<!-- 错误状态 -->
<div v-if="error" class="error-message">
{{ error }}
</div>
</div>
</transition>
</template>
<script setup>
import { computed, ref } from 'vue'
import { getYHZDetail } from '@/views/cockpit/api/commonHttp.js'
/**
* 应急力量详情提示框组件
* 使用 HTML Overlay 方式显示在地图标记点上方
*/
// ==================== Props ====================
const props = defineProps({
/**
* 是否显示提示框
*/
visible: {
type: Boolean,
default: false
},
/**
* 提示框位置(屏幕坐标)
* @type {{ x: number, y: number }}
*/
position: {
type: Object,
default: null
},
/**
* 详情数据
* @type {{ qxmc?: string, yjllpz?: string, wzQtwz?: string }}
*/
data: {
type: Object,
default: () => ({})
},
/**
* 错误信息
*/
error: {
type: String,
default: ''
}
})
// ==================== State ====================
const title = ref('养护站')
const detail = ref(null)
const loading = ref(true)
// ==================== Emits ====================
const emit = defineEmits(['close'])
// ==================== Computed ====================
/**
* 是否有有效数据
*/
const hasData = computed(() => {
return !!props.data
})
// ==================== Methods ====================
// 这个方法会在index.js中被执行
const init = () => {
getDetail()
}
const getDetail = async () => {
loading.value = true
const res = await getYHZDetail({
id: props.data.id
})
if (res.success) {
detail.value = res.data
loading.value = false
}
}
/**
* 处理关闭按钮点击
*/
const handleClose = () => {
emit('close')
}
defineExpose({
init
})
</script>
<style scoped lang="scss">
@use '@/styles/mixins.scss' as *;
.flex {
display: flex;
justify-content: space-between;
align-items: center;
}
.common-tooltip {
// CSS 变量:控制背景图片尺寸和内边距
// 注意:修改图片资源时,需要同步更新这些高度值以匹配实际 PNG 尺寸
--tooltip-top-height: 68px;
--tooltip-bottom-height: 68px;
--tooltip-side-padding: 24px;
--tooltip-body-padding: 16px;
position: absolute;
z-index: 9999;
pointer-events: auto;
// 居中并置于标记点上方
transform: translate(-50%, calc(-100% - 20px));
min-width: 200px;
width: 300px;
// 上下 padding 需要容纳顶部和底部图片 + 内容间距
// 左右 padding 保持一致
padding: calc(var(--tooltip-top-height)) var(--tooltip-side-padding) calc(var(--tooltip-bottom-height));
// 三段式可拉伸背景:顶部固定高度、中部可拉伸、底部固定高度
background-image:
url('@/views/cockpit/assets/emergencyForceTooltip/top.png'),
url('@/views/cockpit/assets/emergencyForceTooltip/bottom.png'),
url('@/views/cockpit/assets/emergencyForceTooltip/middle.png');
// 顶部/底部/中部都不重复,中部通过 background-size 拉伸填充
background-repeat: no-repeat, no-repeat, no-repeat;
// 背景定位:顶部居中对齐顶边,底部居中对齐底边
// 中部略向上偏移 1px结合高度 +2px 在上下各覆盖 1px 避免子像素渲染缝隙
background-position:
center top,
center bottom,
center calc(var(--tooltip-top-height) - 1px);
// 背景尺寸:顶部/底部高度固定
// 中部填充剩余空间并增加 2px在上下各多覆盖约 1px 避免间隙
background-size:
100% var(--tooltip-top-height),
100% var(--tooltip-bottom-height),
100% calc(100% - var(--tooltip-top-height) - var(--tooltip-bottom-height) + 2px);
// 移除原有的 border 和 border-radius使用图片背景
border: none;
// 保留毛玻璃效果和阴影以增强视觉层次
// backdrop-filter: blur(12px);
// box-shadow: 0 8px 24px rgba(0, 0, 0, 0.6);
color: #fff;
font-size: 14px;
line-height: 1.6;
// 小箭头(指向标记点)
// &::after {
// content: '';
// position: absolute;
// bottom: -10px;
// left: 50%;
// transform: translateX(-50%);
// width: 0;
// height: 0;
// border-left: 10px solid transparent;
// border-right: 10px solid transparent;
// border-top: 10px solid rgba(71, 186, 255, 0.4);
// }
}
.title {
white-space: nowrap;
position: absolute;
top: 12px;
left: 20px;
width: 24px;
height: 24px;
padding: 0;
font-family: PingFangSC, PingFang SC;
font-weight: 600;
font-size: 16px;
color: #FFFFFF;
line-height: 22px;
text-shadow: 0px 0px 20px #079DFF;
text-align: right;
font-style: normal;
}
// 关闭按钮 - 仅作为透明点击热区,视觉由背景图中的关闭图标承载
.close-button {
position: absolute;
top: 18px;
right: 2px;
width: 24px;
height: 24px;
padding: 0;
// 透明化所有视觉元素,仅保留点击功能
border: none;
background: transparent;
color: transparent;
font-size: 0;
line-height: 1;
box-shadow: none;
cursor: pointer;
// 为键盘用户提供可见的焦点轮廓(仅在键盘导航时显示)
&:focus-visible {
outline: 2px solid rgba(255, 255, 255, 0.8);
outline-offset: 2px;
}
}
// 内容区域
.tooltip-content {
display: grid;
grid-template-columns: 1.5fr 1fr;
gap: 5px;
.info-item:first-child {
grid-column: 1 / -1;
}
// padding-right: 1.5rem; // 为关闭按钮留出空间
}
.mb {
margin-bottom: 0.5rem
}
// 信息项
.info-item {
.label {
white-space: nowrap;
color: rgba(255, 255, 255, 0.7);
margin-right: 0.5rem;
overflow: hidden;
text-overflow: ellipsis;
}
.value {
font-family: SourceHanSansCN, SourceHanSansCN;
font-weight: 500;
font-size: 14px;
color: #14FFF6;
line-height: 21px;
text-align: left;
font-style: normal;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
}
// 无数据提示
.no-data {
color: rgba(255, 255, 255, 0.5);
text-align: center;
padding: 0.5rem 0;
}
// 加载状态
.loading-overlay {
position: absolute;
inset: 0;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 0.5rem;
// 使用半透明背景,让背景图片纹理仍然可见
background: rgba(0, 0, 0, 1);
backdrop-filter: blur(12px);
span {
color: rgba(255, 255, 255, 0.8);
font-size: 14px;
}
}
// 加载动画
.loading-spinner {
width: 24px;
height: 24px;
border: 3px solid rgba(71, 186, 255, 0.2);
border-top-color: rgba(71, 186, 255, 1);
border-radius: 50%;
animation: spin 0.8s linear infinite;
}
@keyframes spin {
to {
transform: rotate(360deg);
}
}
// 错误信息
.error-message {
color: #ff6b6b;
font-size: 13px;
margin-top: 0.5rem;
padding: 0.5rem;
background: rgba(255, 107, 107, 0.1);
border: 1px solid rgba(255, 107, 107, 0.3);
border-radius: 0.25rem;
}
// 淡入淡出动画
.fade-enter-active,
.fade-leave-active {
transition: opacity 0.3s ease, transform 0.3s ease;
}
.fade-enter-from {
opacity: 0;
transform: translate(-50%, calc(-100% - 10px));
}
.fade-leave-to {
opacity: 0;
transform: translate(-50%, calc(-100% - 30px));
}
</style>