更新样地和样木弹窗逻辑
This commit is contained in:
276
src/components/TreePopup.vue
Normal file
276
src/components/TreePopup.vue
Normal file
@@ -0,0 +1,276 @@
|
||||
<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>
|
||||
Reference in New Issue
Block a user