276 lines
6.9 KiB
Vue
276 lines
6.9 KiB
Vue
|
|
<template>
|
|||
|
|
<div v-if="visible" class="tree-popup" :style="{ left: position.x + 'px', top: position.y + 'px' }">
|
|||
|
|
<el-card shadow="always" class="popup-card" :body-style="{ padding: '0', position: 'relative' }">
|
|||
|
|
|
|||
|
|
<span class="close-btn" @click="$emit('close')">×</span>
|
|||
|
|
|
|||
|
|
<div class="popup-header-wrapper">
|
|||
|
|
<div class="popup-header">
|
|||
|
|
<span class="title">样木详情</span>
|
|||
|
|
<div class="pagination" v-if="total > 1">
|
|||
|
|
<el-button size="small" circle :disabled="currentIndex === 0" @click="prevItem" class="nav-btn"><</el-button>
|
|||
|
|
<span class="page-info">{{ currentIndex + 1 }} / {{ total }}</span>
|
|||
|
|
<el-button size="small" circle :disabled="currentIndex === total - 1" @click="nextItem" class="nav-btn">></el-button>
|
|||
|
|
</div>
|
|||
|
|
<el-tag v-else size="small" type="success">样木</el-tag>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<div class="scroll-content">
|
|||
|
|
<div class="content-padding">
|
|||
|
|
|
|||
|
|
<div class="sub-header">
|
|||
|
|
ID: {{ currentData.id || '-' }}
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<div class="info-grid">
|
|||
|
|
<div class="info-item" v-for="(label, key) in fieldMap" :key="key">
|
|||
|
|
<span class="label">{{ label }}:</span>
|
|||
|
|
<span class="value">{{ getDisplayValue(key) }}</span>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<div class="media-container" v-if="mediaItems.length > 0">
|
|||
|
|
<el-carousel :key="currentIndex" height="160px" indicator-position="none" :interval="4000" arrow="hover">
|
|||
|
|
<el-carousel-item v-for="(item, index) in mediaItems" :key="index">
|
|||
|
|
<el-image
|
|||
|
|
:src="item.src"
|
|||
|
|
class="media-content"
|
|||
|
|
fit="contain"
|
|||
|
|
:preview-src-list="allImages"
|
|||
|
|
:initial-index="item.imgIndex"
|
|||
|
|
preview-teleported
|
|||
|
|
@click.stop
|
|||
|
|
/>
|
|||
|
|
</el-carousel-item>
|
|||
|
|
</el-carousel>
|
|||
|
|
</div>
|
|||
|
|
<div v-else class="no-media">暂无样木照片</div>
|
|||
|
|
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
</el-card>
|
|||
|
|
</div>
|
|||
|
|
</template>
|
|||
|
|
|
|||
|
|
<script setup>
|
|||
|
|
import { computed, ref, watch } from "vue";
|
|||
|
|
import { ElCard, ElCarousel, ElCarouselItem, ElTag, ElImage, ElButton } from "element-plus";
|
|||
|
|
|
|||
|
|
const props = defineProps({
|
|||
|
|
visible: Boolean,
|
|||
|
|
data: { type: [Array, Object], default: () => [] },
|
|||
|
|
position: { type: Object, default: () => ({ x: 0, y: 0 }) }
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
defineEmits(['close']);
|
|||
|
|
|
|||
|
|
const MEDIA_BASE_URL = '/photos'; // 确保和之前一致
|
|||
|
|
const currentIndex = ref(0);
|
|||
|
|
|
|||
|
|
watch(() => props.visible, (val) => {
|
|||
|
|
if (val) currentIndex.value = 0;
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
// 数据标准化:转数组
|
|||
|
|
const dataList = computed(() => {
|
|||
|
|
if (Array.isArray(props.data)) return props.data;
|
|||
|
|
if (props.data && Object.keys(props.data).length > 0) return [props.data];
|
|||
|
|
return [];
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
const total = computed(() => dataList.value.length);
|
|||
|
|
const currentData = computed(() => dataList.value[currentIndex.value] || {});
|
|||
|
|
|
|||
|
|
const prevItem = () => { if (currentIndex.value > 0) currentIndex.value--; };
|
|||
|
|
const nextItem = () => { if (currentIndex.value < total.value - 1) currentIndex.value++; };
|
|||
|
|
|
|||
|
|
// ✅ 样木字段映射
|
|||
|
|
const fieldMap = {
|
|||
|
|
sz: "树种",
|
|||
|
|
xj: "胸径(cm)",
|
|||
|
|
cjl: "材积率", // 或 蓄积
|
|||
|
|
ymh: "样木号",
|
|||
|
|
parentId: "所属样地ID",
|
|||
|
|
grandparentid: "所属小班ID",
|
|||
|
|
databaseName: "数据库",
|
|||
|
|
version: "版本",
|
|||
|
|
type: "类型"
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
function getDisplayValue(key) {
|
|||
|
|
const val = currentData.value[key];
|
|||
|
|
if (val === null || val === undefined || val === '') return '-';
|
|||
|
|
if (key === 'xj' || key === 'cjl') return Number(val).toFixed(2);
|
|||
|
|
return val;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// ✅ 媒体处理:样木接口返回的是 photo 字符串,不是 list
|
|||
|
|
const mediaItems = computed(() => {
|
|||
|
|
const photoPath = currentData.value.photo;
|
|||
|
|
if (!photoPath) return [];
|
|||
|
|
|
|||
|
|
// 构造完整的图片路径
|
|||
|
|
const fullSrc = `${MEDIA_BASE_URL}${photoPath}`;
|
|||
|
|
return [{ type: "image", src: fullSrc, imgIndex: 0 }];
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
const allImages = computed(() => mediaItems.value.map(i => i.src));
|
|||
|
|
|
|||
|
|
</script>
|
|||
|
|
|
|||
|
|
<style scoped>
|
|||
|
|
.tree-popup {
|
|||
|
|
position: relative; /* 配合 Flex 布局 */
|
|||
|
|
z-index: 2002;
|
|||
|
|
width: 260px;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/* 箭头指向左侧 */
|
|||
|
|
.tree-popup::after {
|
|||
|
|
content: '';
|
|||
|
|
position: absolute;
|
|||
|
|
bottom: 20px;
|
|||
|
|
left: -6px;
|
|||
|
|
border-width: 6px 6px 6px 0;
|
|||
|
|
border-style: solid;
|
|||
|
|
border-color: transparent #fff transparent transparent;
|
|||
|
|
filter: drop-shadow(-2px 0 2px rgba(0,0,0,0.1));
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.popup-card {
|
|||
|
|
border-radius: 8px;
|
|||
|
|
border: 1px solid #dcdfe6;
|
|||
|
|
box-shadow: 0 4px 12px rgba(0,0,0,0.3);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/* 关闭按钮 */
|
|||
|
|
.close-btn {
|
|||
|
|
position: absolute;
|
|||
|
|
top: 8px;
|
|||
|
|
right: 8px; /* 稍微靠右一点 */
|
|||
|
|
font-size: 20px;
|
|||
|
|
font-weight: bold;
|
|||
|
|
color: #fff;
|
|||
|
|
cursor: pointer;
|
|||
|
|
z-index: 10; /* 确保在 header 内容之上 */
|
|||
|
|
line-height: 1;
|
|||
|
|
opacity: 0.8;
|
|||
|
|
}
|
|||
|
|
.close-btn:hover {
|
|||
|
|
opacity: 1;
|
|||
|
|
color: #ffe6e6;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.popup-header-wrapper {
|
|||
|
|
background-color: #67c23a; /* 绿色头部 */
|
|||
|
|
border-top-left-radius: 8px;
|
|||
|
|
border-top-right-radius: 8px;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.popup-header {
|
|||
|
|
display: flex;
|
|||
|
|
justify-content: space-between;
|
|||
|
|
align-items: center;
|
|||
|
|
padding: 10px 15px;
|
|||
|
|
/* ✅ 关键修改:右边距避让关闭按钮 */
|
|||
|
|
padding-right: 35px;
|
|||
|
|
color: white;
|
|||
|
|
height: 32px;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.title {
|
|||
|
|
font-weight: bold;
|
|||
|
|
font-size: 14px;
|
|||
|
|
white-space: nowrap; /* 防止标题换行 */
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/* 翻页器 */
|
|||
|
|
.pagination {
|
|||
|
|
display: flex;
|
|||
|
|
align-items: center;
|
|||
|
|
gap: 4px;
|
|||
|
|
}
|
|||
|
|
.page-info {
|
|||
|
|
font-size: 12px;
|
|||
|
|
font-weight: bold;
|
|||
|
|
white-space: nowrap;
|
|||
|
|
}
|
|||
|
|
.nav-btn {
|
|||
|
|
width: 18px;
|
|||
|
|
height: 18px;
|
|||
|
|
min-height: 18px;
|
|||
|
|
font-size: 12px;
|
|||
|
|
padding: 0;
|
|||
|
|
background: rgba(255,255,255,0.2);
|
|||
|
|
border: none;
|
|||
|
|
color: white;
|
|||
|
|
}
|
|||
|
|
.nav-btn:disabled {
|
|||
|
|
opacity: 0.4;
|
|||
|
|
cursor: not-allowed;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/* 内容区域 */
|
|||
|
|
.scroll-content {
|
|||
|
|
max-height: 350px;
|
|||
|
|
overflow-y: auto;
|
|||
|
|
background: #fff;
|
|||
|
|
border-bottom-left-radius: 8px;
|
|||
|
|
border-bottom-right-radius: 8px;
|
|||
|
|
}
|
|||
|
|
.content-padding {
|
|||
|
|
padding: 12px;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.sub-header {
|
|||
|
|
font-size: 12px;
|
|||
|
|
color: #909399;
|
|||
|
|
margin-bottom: 8px;
|
|||
|
|
border-bottom: 1px solid #eee;
|
|||
|
|
padding-bottom: 4px;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.info-grid {
|
|||
|
|
display: flex;
|
|||
|
|
flex-direction: column;
|
|||
|
|
gap: 8px;
|
|||
|
|
margin-bottom: 12px;
|
|||
|
|
}
|
|||
|
|
.info-item {
|
|||
|
|
display: flex;
|
|||
|
|
justify-content: space-between;
|
|||
|
|
border-bottom: 1px dashed #eee;
|
|||
|
|
padding-bottom: 4px;
|
|||
|
|
font-size: 12px;
|
|||
|
|
}
|
|||
|
|
.info-item .label { color: #909399; flex-shrink: 0; }
|
|||
|
|
.info-item .value { color: #303133; font-weight: bold; text-align: right; word-break: break-all; }
|
|||
|
|
|
|||
|
|
.media-container {
|
|||
|
|
position: relative;
|
|||
|
|
background: #000;
|
|||
|
|
border-radius: 4px;
|
|||
|
|
overflow: hidden;
|
|||
|
|
}
|
|||
|
|
.media-content {
|
|||
|
|
width: 100%;
|
|||
|
|
height: 100%;
|
|||
|
|
object-fit: contain;
|
|||
|
|
display: block;
|
|||
|
|
}
|
|||
|
|
.no-media {
|
|||
|
|
height: 50px;
|
|||
|
|
background: #f5f7fa;
|
|||
|
|
color: #c0c4cc;
|
|||
|
|
display: flex;
|
|||
|
|
align-items: center;
|
|||
|
|
justify-content: center;
|
|||
|
|
font-size: 12px;
|
|||
|
|
border-radius: 4px;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/* 滚动条美化 */
|
|||
|
|
.scroll-content::-webkit-scrollbar { width: 6px; }
|
|||
|
|
.scroll-content::-webkit-scrollbar-thumb { background: #dcdfe6; border-radius: 3px; }
|
|||
|
|
</style>
|