<template>
|
<map-board :open="!!current" :title="current?.businessName" :sub="current?.siteType" @close="current = null">
|
<template #side-head>
|
<el-input v-model="keyword" placeholder="搜索站点" clearable size="small" />
|
<div class="filters">
|
<el-checkbox-group v-model="types" size="small">
|
<el-checkbox value="收集点" label="收集点">收集点</el-checkbox>
|
<el-checkbox value="压缩站" label="压缩站">压缩站</el-checkbox>
|
</el-checkbox-group>
|
</div>
|
</template>
|
<template #side>
|
<div
|
v-for="item in visibleSites"
|
:key="item.id"
|
class="list-item"
|
:class="{ 'is-active': current?.id === item.id }"
|
@click="select(item)"
|
>
|
<i :style="{ background: item.siteType === '压缩站' ? '#f59e0b' : '#16a34a' }" />
|
<div>
|
<div class="list-item__name">{{ item.businessName }}</div>
|
<div class="list-item__meta">{{ item.siteType }} · {{ item.address || '无地址' }}</div>
|
</div>
|
</div>
|
<el-empty v-if="!visibleSites.length" description="暂无站点" :image-size="64" />
|
</template>
|
<template #legend>
|
<div class="legend"><i style="background:#16a34a" />收集点</div>
|
<div class="legend"><i style="background:#f59e0b" />压缩站</div>
|
</template>
|
<template #map>
|
<div class="mapDiv">
|
<tdt-map :center="center" :zoom="zoom" :controls="controls" :loadConfig="MAP_LOAD">
|
<tdt-marker
|
v-for="item in visibleSites"
|
:key="item.id"
|
:position="pos(item)"
|
:icon="siteIcon(item.siteType)"
|
:title="item.businessName"
|
@click="select(item)"
|
/>
|
</tdt-map>
|
</div>
|
</template>
|
<template #panel>
|
<el-tabs v-model="tab">
|
<el-tab-pane label="基本属性" name="info">
|
<el-descriptions :column="1" border size="small">
|
<el-descriptions-item label="站点编码">{{ current?.businessId }}</el-descriptions-item>
|
<el-descriptions-item label="名称">{{ current?.businessName }}</el-descriptions-item>
|
<el-descriptions-item label="类型">{{ current?.siteType }}</el-descriptions-item>
|
<el-descriptions-item label="地址">{{ current?.address || '-' }}</el-descriptions-item>
|
<el-descriptions-item label="负责人">{{ current?.leader || '-' }}</el-descriptions-item>
|
<el-descriptions-item label="经纬度">{{ current?.longitude }}, {{ current?.latitude }}</el-descriptions-item>
|
<el-descriptions-item label="备注">{{ current?.remark || '-' }}</el-descriptions-item>
|
</el-descriptions>
|
<cl-image v-if="current?.photo" :src="current.photo" style="margin-top: 10px; width: 100%" />
|
</el-tab-pane>
|
<el-tab-pane label="实时监控" name="live">
|
<el-empty v-if="!cameras.length" description="该站点没有关联监控" :image-size="72" />
|
<div v-for="dev in cameras" :key="dev.id" class="cam">
|
<div class="cam__name">{{ dev.iotName || dev.iotCode }}</div>
|
<ezuikit-player
|
v-if="ysToken"
|
:key="'p_' + dev.id + '_' + ysToken"
|
:container-id="'site_player_' + dev.id"
|
:access-token="ysToken"
|
:url="`ezopen://open.ys7.com/${dev.iotCode}/1.hd.live`"
|
/>
|
</div>
|
</el-tab-pane>
|
<el-tab-pane label="称重历史" name="weight">
|
<div class="weight-tools">
|
<span>默认当天</span>
|
<el-popover v-model:visible="weightOpen" placement="bottom-end" :width="340" trigger="click">
|
<template #reference>
|
<el-button :icon="Setting" circle size="small" />
|
</template>
|
<div class="weight-form">
|
<el-date-picker
|
v-model="weightRange"
|
type="datetimerange"
|
start-placeholder="开始时间"
|
end-placeholder="结束时间"
|
format="YYYY-MM-DD HH:mm"
|
value-format="YYYY-MM-DD HH:mm:ss"
|
size="small"
|
/>
|
<div class="weight-form__actions">
|
<el-button size="small" @click="weightOpen = false">取消</el-button>
|
<el-button type="primary" size="small" @click="queryWeight()">查询</el-button>
|
</div>
|
</div>
|
</el-popover>
|
</div>
|
<el-table :data="weights" size="small" height="360" stripe>
|
<el-table-column prop="uploadTime" label="时间" min-width="150">
|
<template #default="{ row }">{{ formatTime(row.uploadTime) }}</template>
|
</el-table-column>
|
<el-table-column prop="garbageName" label="种类" min-width="90">
|
<template #default="{ row }">{{ row.garbageName || row.garbageType }}</template>
|
</el-table-column>
|
<el-table-column prop="weight" label="重量kg" width="80" />
|
<el-table-column prop="deviceNo" label="设备" min-width="90" />
|
</el-table>
|
</el-tab-pane>
|
</el-tabs>
|
</template>
|
</map-board>
|
</template>
|
|
<script lang="ts" setup>
|
defineOptions({ name: 'maps-layers' });
|
|
import { computed, onMounted, ref } from 'vue';
|
import { Setting } from '@element-plus/icons-vue';
|
import { TdtMap, TdtMarker, type LngLat } from 'vue-tianditu2';
|
import { useCool } from '/@/cool';
|
import EzuikitPlayer from '../../ezuikit/components/ezuikit-player.vue';
|
import MapBoard from '../components/map-board.vue';
|
import {
|
DEFAULT_CENTER,
|
MAP_LOAD,
|
siteIcon,
|
fetchYsToken,
|
formatTime,
|
useMapsApi
|
} from '../hooks/use-map';
|
|
const { service } = useCool();
|
const api = useMapsApi();
|
|
const sites = ref<any[]>([]);
|
const keyword = ref('');
|
const types = ref(['收集点', '压缩站']);
|
const current = ref<any>(null);
|
const center = ref<LngLat>(DEFAULT_CENTER);
|
const zoom = ref(12);
|
const tab = ref('info');
|
const cameras = ref<any[]>([]);
|
const ysToken = ref('');
|
const weights = ref<any[]>([]);
|
const weightOpen = ref(false);
|
const weightRange = ref<[string, string] | null>(null);
|
|
const controls = ['Zoom', 'Scale', { name: 'MapType', position: 'topright' }];
|
|
const visibleSites = computed(() =>
|
sites.value.filter(e => {
|
if (!types.value.includes(e.siteType)) return false;
|
if (!keyword.value) return true;
|
const k = keyword.value.toLowerCase();
|
return [e.businessName, e.businessId, e.address].some(v => String(v || '').toLowerCase().includes(k));
|
})
|
);
|
|
function pos(item: any): LngLat {
|
return [Number(item.longitude), Number(item.latitude)];
|
}
|
|
function todayRange() {
|
const d = new Date();
|
const p = (n: number) => String(n).padStart(2, '0');
|
const day = `${d.getFullYear()}-${p(d.getMonth() + 1)}-${p(d.getDate())}`;
|
return [`${day} 00:00:00`, `${day} 23:59:59`] as [string, string];
|
}
|
|
async function select(item: any) {
|
current.value = item;
|
center.value = pos(item);
|
zoom.value = 16;
|
tab.value = 'info';
|
cameras.value = [];
|
weights.value = [];
|
weightRange.value = todayRange();
|
if (!ysToken.value) {
|
try {
|
ysToken.value = await fetchYsToken(service);
|
} catch {}
|
}
|
try {
|
cameras.value = (await service.basicdata.iot.getYsDevices({ businessId: item.businessId })) || [];
|
} catch {
|
cameras.value = [];
|
}
|
await queryWeight();
|
}
|
|
async function queryWeight() {
|
if (!current.value) return;
|
weightOpen.value = false;
|
const range = weightRange.value || todayRange();
|
weights.value = (await api.siteWeight({
|
businessId: current.value.businessId,
|
startTime: range[0],
|
endTime: range[1]
|
})) || [];
|
}
|
|
onMounted(async () => {
|
sites.value = (await api.sites()) || [];
|
const first = visibleSites.value[0];
|
if (first) {
|
center.value = pos(first);
|
zoom.value = 13;
|
}
|
});
|
</script>
|
|
<style lang="scss" scoped>
|
.filters {
|
margin-top: 8px;
|
}
|
|
.list-item {
|
display: flex;
|
gap: 8px;
|
padding: 10px 12px;
|
cursor: pointer;
|
border-bottom: 1px solid var(--el-border-color-extra-light);
|
|
i {
|
width: 8px;
|
height: 8px;
|
margin-top: 6px;
|
border-radius: 50%;
|
flex-shrink: 0;
|
}
|
|
&:hover,
|
&.is-active {
|
background: var(--el-color-primary-light-9);
|
}
|
|
&__name {
|
font-size: 13px;
|
font-weight: 600;
|
}
|
|
&__meta {
|
margin-top: 2px;
|
font-size: 12px;
|
color: var(--el-text-color-secondary);
|
}
|
}
|
|
.legend {
|
display: flex;
|
align-items: center;
|
gap: 6px;
|
margin: 2px 0;
|
|
i {
|
width: 8px;
|
height: 8px;
|
border-radius: 50%;
|
}
|
}
|
|
.cam {
|
margin-bottom: 12px;
|
|
&__name {
|
margin-bottom: 6px;
|
font-size: 13px;
|
color: var(--el-text-color-regular);
|
}
|
}
|
|
.weight-tools {
|
display: flex;
|
align-items: center;
|
justify-content: space-between;
|
margin: 8px 0;
|
font-size: 12px;
|
color: var(--el-text-color-secondary);
|
}
|
|
.weight-form {
|
display: flex;
|
flex-direction: column;
|
gap: 8px;
|
|
&__actions {
|
display: flex;
|
justify-content: flex-end;
|
gap: 8px;
|
}
|
}
|
|
.mapDiv {
|
width: 100%;
|
height: 100%;
|
}
|
</style>
|