完成初版

This commit is contained in:
2026-07-22 17:23:49 +08:00
parent cf40ccbdb0
commit bf390e8f7e
37 changed files with 4387 additions and 102 deletions

View File

@@ -68,10 +68,9 @@
.asset-item .info { font-size:12px; color:#888; margin-top:3px; line-height:1.6; }
.asset-item .actions { display:flex; gap:6px; margin-top:8px; }
.asset-item .actions button {
padding:5px 10px; border:1px solid #ddd; border-radius:4px;
background:#fff; cursor:pointer; font-size:12px;
padding:5px 10px; border:1px solid #e74c3c; border-radius:4px;
background:#e74c3c; color:#fff; cursor:pointer; font-size:12px;
}
.asset-item .actions button:hover { border-color:#e74c3c; color:#e74c3c; }
.asset-item .actions .btn-detail { background:#e74c3c; color:#fff; border-color:#e74c3c; }
.empty-tip { padding:30px 16px; text-align:center; color:#ccc; font-size:14px; }
@@ -79,6 +78,11 @@
.custom-marker-icon { background:none !important; border:none !important; }
/* 移动端触控优化 */
.media-panel-overlay, .lightbox-overlay, .video-overlay, .pano-overlay { touch-action:none; }
.media-panel { touch-action:pan-y; }
.pano-viewer { touch-action:none; }
/* 筛选区折叠 */
.filter-section > div:nth-child(2) { transition:all 0.3s ease; overflow:hidden; }
@@ -127,7 +131,82 @@
.switch-popup .sp-item.active { background:#fdedec; border-color:#e74c3c; }
.switch-popup .sp-item:last-child { margin-bottom:0; }
/* 媒体浏览组件 */
.gallery-link { display:inline-flex; align-items:center; gap:4px; padding:6px 0; font-size:13px; color:#e74c3c; cursor:pointer; user-select:none; }
.gallery-link:hover { text-decoration:underline; }
.thumb-list { display:flex; flex-wrap:wrap; gap:4px; padding:4px 0; margin:4px 0; }
.thumb-item { width:56px; height:56px; border-radius:4px; overflow:hidden; cursor:pointer; position:relative; border:2px solid transparent; transition:border-color 0.2s; }
.thumb-item:hover { border-color:#3498db; }
.thumb-item img { width:100%; height:100%; object-fit:cover; }
.thumb-badge { position:absolute; bottom:2px; right:2px; background:rgba(0,0,0,0.7); color:#fff; font-size:10px; padding:1px 3px; border-radius:2px; line-height:1; }
.lightbox-overlay { position:fixed; top:0; left:0; right:0; bottom:0; background:rgba(0,0,0,0.92); z-index:100100; display:flex; align-items:center; justify-content:center; flex-direction:column; }
.lightbox-close { position:absolute; top:16px; right:16px; background:none; border:none; color:#fff; font-size:28px; cursor:pointer; z-index:100101; width:40px; height:40px; display:flex; align-items:center; justify-content:center; }
.lightbox-close:hover { color:#e74c3c; }
.lightbox-img { max-width:90vw; max-height:80vh; object-fit:contain; }
.lightbox-nav { position:absolute; top:50%; transform:translateY(-50%); background:rgba(255,255,255,0.15); border:none; color:#fff; font-size:32px; cursor:pointer; width:48px; height:48px; display:flex; align-items:center; justify-content:center; border-radius:50%; transition:background 0.2s; }
.lightbox-nav:hover { background:rgba(255,255,255,0.35); }
.lightbox-prev { left:16px; }
.lightbox-next { right:16px; }
.lightbox-counter { color:#fff; font-size:14px; margin-top:12px; }
.video-overlay { position:fixed; top:0; left:0; right:0; bottom:0; background:rgba(0,0,0,0.92); z-index:100100; display:flex; align-items:center; justify-content:center; flex-direction:column; }
.video-close { position:absolute; top:16px; right:16px; background:none; border:none; color:#fff; font-size:28px; cursor:pointer; z-index:100101; width:40px; height:40px; display:flex; align-items:center; justify-content:center; }
.video-close:hover { color:#e74c3c; }
.video-player { max-width:90vw; max-height:80vh; }
.pano-overlay { position:fixed; top:0; left:0; right:0; bottom:0; background:#000; z-index:100100; overflow:hidden; }
.pano-close { position:absolute; top:16px; right:16px; background:rgba(0,0,0,0.6); border:2px solid rgba(255,255,255,0.5); color:#fff; font-size:20px; cursor:pointer; z-index:100101; width:44px; height:44px; display:flex; align-items:center; justify-content:center; border-radius:50%; }
.pano-close:hover { background:rgba(231,76,60,0.8); border-color:#fff; }
.pano-viewer { width:100%; height:100%; background-size:auto 100%; background-repeat:repeat-x; background-position:center center; cursor:grab; }
.pano-viewer.dragging { cursor:grabbing; }
.pano-hint { position:absolute; bottom:20px; left:50%; transform:translateX(-50%); color:rgba(255,255,255,0.5); font-size:13px; pointer-events:none; }
/* 影像资料弹框 */
.media-panel-overlay { position:fixed; top:0; left:0; right:0; bottom:0; background:rgba(0,0,0,0.5); z-index:100000; display:flex; align-items:center; justify-content:center; }
.media-panel { width:720px; max-width:90vw; max-height:80vh; background:#fff; border-radius:12px; display:flex; flex-direction:column; overflow:hidden; box-shadow:0 8px 32px rgba(0,0,0,0.25); }
.media-panel-header { display:flex; align-items:center; justify-content:space-between; padding:14px 20px; border-bottom:1px solid #eee; flex-shrink:0; }
.media-panel-title { font-size:16px; font-weight:bold; color:#333; }
.media-panel-close { background:none; border:none; font-size:22px; color:#888; cursor:pointer; width:36px; height:36px; display:flex; align-items:center; justify-content:center; border-radius:50%; }
.media-panel-close:hover { background:#f0f0f0; color:#333; }
/* Tab页签 */
.media-tabs { display:flex; border-bottom:2px solid #eee; flex-shrink:0; }
.media-tab { flex:1; padding:10px 0; text-align:center; font-size:13px; color:#888; cursor:pointer; border-bottom:2px solid transparent; margin-bottom:-2px; transition:all 0.2s; }
.media-tab:hover { color:#333; }
.media-tab.active { color:#e74c3c; border-bottom-color:#e74c3c; font-weight:bold; }
/* Tab内容区 */
.media-panel-body { flex:1; overflow-y:auto; padding:16px; }
/* 图片区 */
.media-img-grid { display:flex; flex-wrap:wrap; gap:8px; }
.media-img-item { width:calc(25% - 6px); aspect-ratio:4/3; border-radius:6px; overflow:hidden; cursor:pointer; position:relative; border:2px solid transparent; transition:border-color 0.2s; }
.media-img-item:hover { border-color:#e74c3c; }
.media-img-item img { width:100%; height:100%; object-fit:cover; }
.media-img-item .pano-badge { position:absolute; top:4px; left:4px; background:rgba(0,0,0,0.7); color:#fff; font-size:10px; padding:2px 6px; border-radius:3px; }
/* 视频区 */
.media-video-grid { display:flex; flex-wrap:wrap; gap:8px; }
.media-video-item { width:calc(33.33% - 6px); aspect-ratio:16/9; border-radius:6px; overflow:hidden; cursor:pointer; position:relative; background:#000; border:2px solid transparent; transition:border-color 0.2s; }
.media-video-item:hover { border-color:#3498db; }
.media-video-item img { width:100%; height:100%; object-fit:cover; opacity:0.7; }
.media-video-item .play-icon { position:absolute; top:50%; left:50%; transform:translate(-50%,-50%); width:40px; height:40px; background:rgba(0,0,0,0.6); border-radius:50%; display:flex; align-items:center; justify-content:center; color:#fff; font-size:18px; }
/* 附件区 */
.media-file-list { display:flex; flex-direction:column; gap:4px; }
.media-file-item { display:flex; align-items:center; gap:10px; padding:10px 12px; border:1px solid #eee; border-radius:6px; cursor:pointer; transition:background 0.15s; }
.media-file-item:hover { background:#f8f9fa; border-color:#e74c3c; }
.media-file-icon { width:32px; height:32px; background:#f0f0f0; border-radius:6px; display:flex; align-items:center; justify-content:center; font-size:14px; color:#666; flex-shrink:0; }
.media-file-info { flex:1; min-width:0; }
.media-file-name { font-size:13px; color:#333; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
.media-file-size { font-size:11px; color:#999; margin-top:2px; }
.media-empty { text-align:center; padding:40px 0; color:#ccc; font-size:14px; }
@media (max-width:768px) {
/* 地图与面板布局 */
#app { display:block; width:100%; height:100%; position:relative; }
#mapWrap { position:absolute; top:0; left:0; width:100%; height:45%; }
#map { height:100%; width:100%; }
@@ -138,14 +217,43 @@
border-radius:12px 12px 0 0;
box-shadow:0 -2px 12px rgba(0,0,0,0.08);
}
/* 筛选与按钮 */
.filter-group select, .filter-group input[type="text"] { min-height:44px; font-size:15px; }
.btn { min-height:44px; font-size:15px; }
.cascade-col { min-width:120px; }
.cascade-col .col-item { padding:12px 10px; font-size:14px; }
/* 资产卡片 */
.asset-item { padding:14px 16px; min-height:80px; }
.asset-item .name { font-size:15px; }
.asset-item .info { font-size:13px; }
.asset-item .actions button { padding:8px 12px; font-size:13px; min-height:32px; }
.cascade-col { min-width:120px; }
.cascade-col .col-item { padding:12px 10px; font-size:14px; }
.asset-item .actions { flex-wrap:wrap; }
.asset-item .actions button { padding:8px 14px; font-size:13px; min-height:40px; flex:1; }
/* 影像资料弹框:底部弹出式 */
.media-panel-overlay { align-items:flex-end; }
.media-panel { width:100%; max-width:100%; max-height:75vh; border-radius:16px 16px 0 0; }
.media-panel-header { padding:16px 20px; }
.media-panel-close { width:44px; height:44px; font-size:24px; }
.media-tab { padding:12px 0; font-size:14px; min-height:44px; }
.media-panel-body { padding:12px; }
.media-img-item { width:calc(50% - 4px); }
.media-video-item { width:100%; }
.media-file-item { padding:12px; min-height:48px; }
/* 灯箱:更大触控区域 */
.lightbox-close { width:48px; height:48px; font-size:32px; top:12px; right:12px; }
.lightbox-nav { width:56px; height:56px; font-size:36px; }
.lightbox-counter { font-size:16px; margin-top:16px; }
/* 视频弹框 */
.video-close { width:48px; height:48px; font-size:32px; }
.video-player { max-width:100vw; max-height:60vh; }
/* 全景弹框 */
.pano-close { width:48px; height:48px; font-size:24px; }
.pano-hint { font-size:14px; bottom:24px; }
}
</style>
</head>
@@ -211,7 +319,7 @@
<input type="text" v-model="keyword" placeholder="资产名称/编号" @keyup.enter="queryAssets">
</div>
<div class="filter-actions">
<button class="btn btn-primary" @click="queryAssets">查询</button>
<button class="btn btn-primary" @click="queryAssets" :disabled="isAssetView" :style="isAssetView ? 'opacity:0.5;cursor:not-allowed;' : ''">查询</button>
<button class="btn btn-reset" @click="resetFilter">重置</button>
</div>
</div>
@@ -233,13 +341,112 @@
位置:{{a.address || '-'}}<br>
面积:{{a.area || '-'}}㎡ 管理单位:{{a.manager || '-'}}
</div>
<!-- 相册入口(暂屏蔽)
<div v-if="getAssetMedia(a).images.length" class="gallery-link" @click.stop="openLightbox(a, 0)">
相册({{getAssetMedia(a).images.length}}张)›
</div>
<div v-if="getAssetMedia(a).all.length" class="thumb-list">
<div v-for="(m, mIdx) in getAssetMedia(a).all" :key="mIdx"
class="thumb-item" @click.stop="onThumbClick(a, m)">
<img :src="m.thumb || m.url" loading="lazy">
<span v-if="m.type === 'video'" class="thumb-badge">▶</span>
<span v-else-if="m.type === 'panorama'" class="thumb-badge">360°</span>
</div>
</div>
-->
<div class="actions">
<button class="btn-detail" @click.stop="goToDetail(a)">查看详情</button>
<button class="btn-media" @click.stop="openMediaPanel(a)">影像资料</button>
</div>
</div>
<div v-if="loadingMore" class="loading-tip">加载中...</div>
<div v-if="allLoaded && assetList.length > 0" class="loading-tip">已加载全部 {{total}} 条</div>
</div>
</div>
</div>
<!-- 相册弹框 -->
<div v-if="lightbox.visible" class="lightbox-overlay" @keyup.escape="closeLightbox()" tabindex="0">
<button class="lightbox-close" @click="closeLightbox()"></button>
<button class="lightbox-nav lightbox-prev" @click="lightboxNav(-1)"></button>
<img class="lightbox-img" :src="lightbox.images[lightbox.index]">
<button class="lightbox-nav lightbox-next" @click="lightboxNav(1)"></button>
<div class="lightbox-counter">{{lightbox.index + 1}} / {{lightbox.images.length}}</div>
</div>
<!-- 视频播放弹框 -->
<div v-if="videoOverlay.visible" class="video-overlay" @keyup.escape="closeVideoPlayer()" tabindex="0">
<button class="video-close" @click="closeVideoPlayer()"></button>
<video class="video-player" :src="videoOverlay.url" controls autoplay></video>
</div>
<!-- 影像资料弹框 -->
<div v-if="mediaPanel.visible" class="media-panel-overlay" @click.self="closeMediaPanel()">
<div class="media-panel">
<div class="media-panel-header">
<span class="media-panel-title">{{mediaPanel.assetName || '影像资料'}}</span>
<button class="media-panel-close" @click="closeMediaPanel()"></button>
</div>
<div class="media-tabs">
<div class="media-tab" :class="{active: mediaPanel.tab === 'images'}" @click="mediaPanel.tab = 'images'">图片</div>
<div class="media-tab" :class="{active: mediaPanel.tab === 'videos'}" @click="mediaPanel.tab = 'videos'">视频</div>
<!-- <div class="media-tab" :class="{active: mediaPanel.tab === 'files'}" @click="mediaPanel.tab = 'files'">附件</div>-->
</div>
<div class="media-panel-body">
<!-- 图片页签 -->
<div v-if="mediaPanel.tab === 'images'">
<div v-if="mediaPanel.media.images.length || mediaPanel.media.panoramas.length" class="media-img-grid">
<div v-for="(img, idx) in mediaPanel.media.images" :key="'img-'+idx"
class="media-img-item" @click="openLightboxFromPanel(idx)">
<img :src="img" loading="lazy">
</div>
<div v-for="(p, idx) in mediaPanel.media.panoramas" :key="'pno-'+idx"
class="media-img-item" @click="openPanoViewer(p)">
<img :src="p" loading="lazy">
<span class="pano-badge">360°</span>
</div>
</div>
<div v-else class="media-empty">暂无图片</div>
</div>
<!-- 视频页签 -->
<div v-if="mediaPanel.tab === 'videos'">
<div v-if="mediaPanel.media.videos.length" class="media-video-grid">
<div v-for="(v, idx) in mediaPanel.media.videos" :key="'vid-'+idx"
class="media-video-item" @click="openVideoPlayer(v)">
<img :src="mediaPanel.media.videoThumbs[idx] || ''" loading="lazy">
<div class="play-icon"></div>
</div>
</div>
<div v-else class="media-empty">暂无视频</div>
</div>
<!-- 附件页签 -->
<div v-if="mediaPanel.tab === 'files'">
<div v-if="mediaPanel.media.files.length" class="media-file-list">
<div v-for="(f, idx) in mediaPanel.media.files" :key="'file-'+idx"
class="media-file-item" @click="downloadFile(f)">
<div class="media-file-icon">{{getFileExt(f.name)}}</div>
<div class="media-file-info">
<div class="media-file-name">{{f.name || '附件' + (idx+1)}}</div>
<div class="media-file-size">{{f.size || ''}}</div>
</div>
</div>
</div>
<div v-else class="media-empty">暂无附件</div>
</div>
</div>
</div>
</div>
<!-- 360全景弹框 -->
<div v-if="panoOverlay.visible" class="pano-overlay" @keyup.escape="closePanoViewer()" tabindex="0">
<button class="pano-close" @click="closePanoViewer()"></button>
<div class="pano-viewer" ref="panoViewer"
:style="{backgroundImage: 'url(' + panoOverlay.url + ')'}"
@mousedown="panoDragStart" @mousemove="panoDragMove" @mouseup="panoDragEnd" @mouseleave="panoDragEnd"
@touchstart.passive="panoDragStart" @touchmove.prevent="panoDragMove" @touchend="panoDragEnd">
</div>
<div class="pano-hint">拖动鼠标或触摸滑动查看全景</div>
</div>
</div>
<script>
@@ -251,7 +458,7 @@
el: "#app",
data: {
// ========== 基础配置 ==========
host: 'http://localhost:3080', // 后端服务地址
host: 'http://sz.esdci.cn:52100', // 后端服务地址
// ========== 地图实例与图层 ==========
map: null, // Leaflet 地图实例
@@ -274,6 +481,10 @@
assetList: [], // 当前查询到的资产列表
total: 0, // 资产总数
queried: false, // 是否已执行过查询(控制空状态提示)
pageNum: 1, // 当前页码
pageSize: 20, // 每页条数
loadingMore: false, // 是否正在加载更多(防止重复触发)
allLoaded: false, // 是否已加载全部
// ========== 标点与边界管理 ==========
isMobile: false, // 是否移动端
@@ -283,7 +494,20 @@
// ========== 选中状态 ==========
activeAssetId: null, // 当前选中的资产ID用于列表高亮
activeMarker: null, // 当前高亮的标点对象
_syncFromMap: false // 防止地图点击与列表选中双向触发的标志
_syncFromMap: false, // 防止地图点击与列表选中双向触发的标志
isAssetView: false, // 是否为URL穿透的单资产查看模式禁用查询按钮
// ========== 媒体浏览 ==========
lightbox: { visible: false, images: [], index: 0 },
videoOverlay: { visible: false, url: '' },
panoOverlay: { visible: false, url: '' },
_panoState: { dragging: false, startX: 0, startPos: 50 },
mediaPanel: {
visible: false,
assetName: '',
tab: 'images',
media: { images: [], videos: [], videoThumbs: [], panoramas: [], files: [] }
}
},
/**
@@ -297,10 +521,27 @@
window.addEventListener('resize', () => this.checkDevice());
this.initMap();
// 全局键盘事件Escape关闭弹框
var self = this;
window.addEventListener('keydown', function(e) {
if (e.key === 'Escape' || e.keyCode === 27) {
if (self.lightbox.visible) self.closeLightbox();
else if (self.videoOverlay.visible) self.closeVideoPlayer();
else if (self.panoOverlay.visible) self.closePanoViewer();
else if (self.mediaPanel.visible) self.closeMediaPanel();
}
// 相册左右翻页
if (self.lightbox.visible) {
if (e.key === 'ArrowLeft') self.lightboxNav(-1);
else if (e.key === 'ArrowRight') self.lightboxNav(1);
}
});
// URL参数 assetId 支持从其他页面穿透定位到指定资产
var params = new URLSearchParams(window.location.search);
var assetId = params.get('assetId');
if (assetId) {
this.isAssetView = true;
this.loadAssetById(assetId);
} else {
// 无指定资产时,加载筛选选项后默认查询
@@ -341,9 +582,15 @@
// 延迟刷新地图尺寸,确保容器已渲染完成
setTimeout(() => this.map.invalidateSize(), 100);
// 自定义标点图标
// 自定义标点图标(普通/问题资产)
this.markerIcon = L.divIcon({
html: '<img src="./markicon.png" style="width:48px;height:48px;">',
html: '<img src="./blue.png" style="width:48px;height:48px;">',
className: 'custom-marker-icon',
iconSize: [48, 48],
iconAnchor: [24, 24]
});
this.markerIconRed = L.divIcon({
html: '<img src="./red.png" style="width:48px;height:48px;">',
className: 'custom-marker-icon',
iconSize: [48, 48],
iconAnchor: [24, 24]
@@ -476,6 +723,7 @@
resetFilter() {
this.filterOwner = ''; this.filterCatagory = ''; this.keyword = '';
this.assetList = []; this.queried = false; this.total = 0;
this.pageNum = 1; this.allLoaded = false; this.loadingMore = false;
this.markerGroup.clearLayers();
this.clearAllBoundLayers();
this.markerMap = {};
@@ -504,6 +752,9 @@
queryAssets() {
var self = this;
this.queried = true;
this.pageNum = 1;
this.allLoaded = false;
this.loadingMore = false;
// 清空现有图层
this.markerGroup.clearLayers();
this.clearAllBoundLayers();
@@ -517,7 +768,7 @@
level3: this.filterCatagory,
keyword: this.keyword,
pageNum: 1,
pageSize: 20
pageSize: this.pageSize
};
fetch(this.host + "/seeyon/rest/assetsRegion/queryAssetList", {
@@ -528,11 +779,60 @@
if (d.code === 0) {
self.assetList = d.data.records || [];
self.total = d.data.total || 0;
self.allLoaded = self.assetList.length >= self.total;
self.buildMarkers();
self.bindScrollListener();
}
});
},
/**
* 绑定资产列表滚动事件,触底时加载下一页
*/
bindScrollListener() {
var self = this;
var container = this.$refs.assetListContainer;
if (!container || self._scrollBound) return;
self._scrollBound = true;
container.addEventListener('scroll', function() {
if (self.loadingMore || self.allLoaded) return;
// 距底部 50px 时触发加载
if (container.scrollTop + container.clientHeight >= container.scrollHeight - 50) {
self.loadMore();
}
});
},
/**
* 加载下一页资产数据,追加到 assetList 并刷新标点
*/
loadMore() {
if (this.loadingMore || this.allLoaded) return;
this.loadingMore = true;
this.pageNum++;
var self = this;
var body = {
owner: this.filterOwner,
level3: this.filterCatagory,
keyword: this.keyword,
pageNum: this.pageNum,
pageSize: this.pageSize
};
fetch(this.host + "/seeyon/rest/assetsRegion/queryAssetList", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(body)
}).then(r => r.json()).then(d => {
if (d.code === 0) {
var newRecords = d.data.records || [];
self.assetList = self.assetList.concat(newRecords);
self.allLoaded = self.assetList.length >= self.total;
self.buildMarkers();
}
self.loadingMore = false;
}).catch(function() { self.loadingMore = false; });
},
// =====================================================================
// 标点构建(按经纬度分组)
// =====================================================================
@@ -565,7 +865,12 @@
// Step 2: 为每组创建标点
Object.keys(groups).forEach(function(key) {
var group = groups[key];
var m = L.marker([group.lat, group.lng], { icon: self.markerIcon });
// 判断该组是否有问题资产troubledAssets 值为"是"
var hasProblem = group.assets.some(function(a) {
return a.troubledAssets === '是';
});
var icon = hasProblem ? self.markerIconRed : self.markerIcon;
var m = L.marker([group.lat, group.lng], { icon: icon });
m._groupKey = key; // 分组key用于关联边界图层
m._assets = group.assets; // 该位置的所有资产
m._activeIdx = 0; // 当前展示的资产索引
@@ -886,8 +1191,8 @@
},
/**
* 高亮指定标点(红色发光 + 放大效果)
* 同时取消上一个高亮标点
* 高亮指定标点(发光 + 放大效果)
* 问题资产:红色发光,非问题资产:蓝色发光
* @param {L.Marker} m - 要高亮的标点
*/
highlightMarker(m) {
@@ -897,7 +1202,15 @@
var el = m.getElement();
if (el) {
var img = el.querySelector('img') || el;
img.style.filter = 'drop-shadow(0 0 6px #e74c3c) drop-shadow(0 0 12px #e74c3c) drop-shadow(0 0 20px rgba(231,76,60,0.6))';
// 判断是否问题资产
var hasProblem = m._assets && m._assets.some(function(a) {
return a.troubledAssets === '是';
});
if (hasProblem) {
img.style.filter = 'drop-shadow(0 0 6px #e74c3c) drop-shadow(0 0 12px #e74c3c) drop-shadow(0 0 20px rgba(231,76,60,0.6))';
} else {
img.style.filter = 'drop-shadow(0 0 6px #3498db) drop-shadow(0 0 12px #3498db) drop-shadow(0 0 20px rgba(52,152,219,0.6))';
}
img.style.transform = 'scale(2)';
img.style.transformOrigin = 'center center';
el.style.zIndex = 1000;
@@ -985,15 +1298,215 @@
* 先根据资产类型获取详情页URL模板再替换资产ID打开新窗口
* @param {Object} item - 资产对象
*/
goToDetail(item) {
var host = this.host;
fetch(this.host + "/seeyon/rest/assetsRegion/getDetailBaseUrlByAssetsType?assetsType=" + item.assetType)
.then(r => r.json()).then(d => {
if (d.code === 0 && d.data) {
window.open(host + d.data.replace(/\{assetsId\}/g, item.id));
}
});
}
goToDetail(item) {
var host = this.host;
let url = this.host + "/seeyon/rest/assetsRegion/getDetailBaseUrlByAssetsType";
// 过滤 null、undefined、空字符串
const assetsType = item.assetType;
if (assetsType != null && assetsType !== '') {
url += "?assetsType=" + encodeURIComponent(assetsType);
}
fetch(url)
.then(r => r.json())
.then(d => {
if (d.code === 0 && d.data) {
window.open(host + d.data.replace(/\{assetsId\}/g, item.id));
}
});
},
// =====================================================================
// 媒体浏览
// =====================================================================
/** 获取资产的所有媒体资源,返回 {images, videos, panoramas, all} */
getAssetMedia(asset) {
// 后端返回的是 FileItemVO 对象数组:[{previewUrl, name, ...}]
// 提取 previewUrl 作为图片/视频URL
var images = this._extractUrls(asset.images);
var videos = this._extractUrls(asset.videos);
var panoramas = this._extractUrls(asset.panoramas);
var all = [];
images.forEach(function(url) { all.push({ type: 'image', url: url, thumb: url }); });
videos.forEach(function(url) { all.push({ type: 'video', url: url, thumb: url }); });
panoramas.forEach(function(url) { all.push({ type: 'panorama', url: url, thumb: url }); });
return { images: images, videos: videos, panoramas: panoramas, all: all };
},
/**
* 从FileItemVO数组中提取previewUrl列表
* 输入: [{previewUrl: "http://...", name: "xxx"}, ...]
* 输出: ["http://...", ...]
*/
_extractUrls: function(items) {
if (!items || !Array.isArray(items)) return [];
return items
.map(function(item) { return item.previewUrl || item.url || ''; })
.filter(function(url) { return url.length > 0; });
},
/** 打开影像资料弹框 */
openMediaPanel(asset) {
var media = this.getAssetMedia(asset);
this.mediaPanel.assetName = asset.assetName || asset.assetCode || '';
this.mediaPanel.tab = 'images';
this.mediaPanel.media = {
images: media.images,
videos: media.videos,
videoThumbs: media.videos.map(function(v, i) {
return 'https://picsum.photos/seed/vid' + (asset.assetCode || '') + i + '/320/180';
}),
panoramas: media.panoramas,
files: this._extractUrls(asset.attachments).map(function(url, i) {
var name = url.split('/').pop() || ('附件' + (i + 1));
return { name: name, url: url, size: '' };
})
};
this.mediaPanel.visible = true;
},
/** 关闭影像资料弹框 */
closeMediaPanel() {
this.mediaPanel.visible = false;
},
/** 从影像资料弹框中打开相册(只包含普通图片) */
openLightboxFromPanel(idx) {
var images = this.mediaPanel.media.images;
if (!images.length) return;
this.lightbox.images = images;
this.lightbox.index = idx;
this.lightbox.visible = true;
},
/** 获取文件扩展名(用于显示图标) */
getFileExt(name) {
if (!name) return '?';
var ext = name.split('.').pop().toUpperCase();
if (ext.length > 4) ext = ext.substring(0, 4);
return ext;
},
/** 下载附件 */
downloadFile(file) {
if (!file || !file.url) return;
var a = document.createElement('a');
a.href = file.url;
a.download = file.name || '';
a.target = '_blank';
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
},
/** 打开相册弹框 */
openLightbox(asset, startIdx) {
var media = this.getAssetMedia(asset);
if (!media.images.length) return;
this.lightbox.images = media.images;
this.lightbox.index = startIdx || 0;
this.lightbox.visible = true;
var self = this;
this.$nextTick(function() {
var el = document.querySelector('.lightbox-overlay');
if (el) el.focus();
});
},
/** 关闭相册弹框 */
closeLightbox() {
this.lightbox.visible = false;
this.lightbox.images = [];
this.lightbox.index = 0;
},
/** 相册前后翻页 */
lightboxNav(delta) {
var len = this.lightbox.images.length;
if (!len) return;
this.lightbox.index = (this.lightbox.index + delta + len) % len;
},
/** 缩略图点击:根据类型打开对应弹框 */
onThumbClick(asset, mediaItem) {
if (mediaItem.type === 'image') {
var media = this.getAssetMedia(asset);
var idx = media.images.indexOf(mediaItem.url);
this.openLightbox(asset, idx >= 0 ? idx : 0);
} else if (mediaItem.type === 'video') {
this.openVideoPlayer(mediaItem.url);
} else if (mediaItem.type === 'panorama') {
this.openPanoViewer(mediaItem.url);
}
},
/** 打开视频播放弹框 */
openVideoPlayer(url) {
this.videoOverlay.url = url;
this.videoOverlay.visible = true;
var self = this;
this.$nextTick(function() {
var el = document.querySelector('.video-overlay');
if (el) el.focus();
});
},
/** 关闭视频播放弹框 */
closeVideoPlayer() {
this.videoOverlay.visible = false;
this.videoOverlay.url = '';
},
/** 打开360全景查看器 */
openPanoViewer(url) {
this.panoOverlay.url = url;
this.panoOverlay.visible = true;
this._panoState = { dragging: false, startX: 0, startPos: 50 };
var self = this;
this.$nextTick(function() {
var viewer = self.$refs.panoViewer;
if (viewer) viewer.style.backgroundPositionX = '50%';
var el = document.querySelector('.pano-overlay');
if (el) el.focus();
});
},
/** 关闭360全景查看器 */
closePanoViewer() {
this.panoOverlay.visible = false;
this.panoOverlay.url = '';
},
/** 360全景拖拽开始 */
panoDragStart(e) {
this._panoState.dragging = true;
this._panoState.startX = e.type === 'touchstart' ? e.touches[0].clientX : e.clientX;
var viewer = e.currentTarget;
var pos = parseFloat(viewer.style.backgroundPositionX) || 50;
this._panoState.startPos = pos;
viewer.classList.add('dragging');
},
/** 360全景拖拽移动 */
panoDragMove(e) {
if (!this._panoState.dragging) return;
var clientX = e.type === 'touchmove' ? e.touches[0].clientX : e.clientX;
var dx = clientX - this._panoState.startX;
var viewer = e.currentTarget;
var newPos = this._panoState.startPos + (dx / viewer.offsetWidth) * 100;
newPos = Math.max(0, Math.min(100, newPos));
viewer.style.backgroundPositionX = newPos + '%';
},
/** 360全景拖拽结束 */
panoDragEnd(e) {
this._panoState.dragging = false;
if (e && e.currentTarget) e.currentTarget.classList.remove('dragging');
}
}
});
</script>