| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571 |
- <template>
- <div style="height: 100%; width: 100%; display: flex; flex-direction: row">
- <!-- 左边是列表 -->
- <div style="width: 300px; height: 100%">
- <el-card style="height: 100%">
- <template #header>
- <div class="card-header">
- <el-icon :size="15" class="mr-2" style="vertical-align: middle">
- <component :is="Menu" />
- </el-icon>
- <span>项目列表</span>
- </div>
- </template>
- <el-menu
- :key="menuKey"
- :default-active="activePath"
- :default-openeds="defaultOpeneds"
- class="custom-menu"
- mode="vertical"
- @select="handleMenuSelect"
- >
- <el-sub-menu
- v-for="(parent, parentIndex) in menuItems"
- :key="parentIndex"
- :index="parent.index"
- :class="{ 'parent-active': isParentActive(parent.index) }"
- >
- <template #title>
- <!-- 父菜单图标 -->
- <el-icon :size="18" class="mr-2">
- <component :is="parent.icon" />
- </el-icon>
- <span>{{ parent.title }}</span>
- </template>
- <el-menu-item
- v-for="(child, childIndex) in parent.children"
- :key="childIndex"
- :index="child.index"
- >
- <!-- 子菜单图标 -->
- <el-icon :size="18" class="mr-2">
- <component :is="child.icon" />
- </el-icon>
- <span>{{ child.title }}</span>
- </el-menu-item>
- </el-sub-menu>
- </el-menu>
- </el-card>
- </div>
- <!-- 右边是地图引擎 -->
- <div style="width: 100%; height: 100%; display: grid">
- <div id="mapview" ref="mapview" v-loading="mapSet.loading" />
- <el-card class="float-stat">
- <div class="float-stat-item">
- <div style="font-weight: 900">
- {{ selLocation.name }}
- </div>
- </div>
- <el-divider></el-divider>
- <div class="float-stat-item">
- <div class="float-stat-item-key">分配基站:</div>
- <div class="float-stat-item-value">{{ cntAnchor }}</div>
- </div>
- <div class="float-stat-item">
- <div class="float-stat-item-key">在线基站:</div>
- <div class="float-stat-item-value">{{ cntAnchorOnline }}</div>
- </div>
- <div class="float-stat-item">
- <div class="float-stat-item-key">考勤人数:</div>
- <div class="float-stat-item-value">{{ cntPersonAttend }}</div>
- </div>
- <!-- <div class="float-stat-item">
- <div class="float-stat-item-key">今日到岗:</div>
- <div class="float-stat-item-value">{{ cntPersonConfirm }}</div>
- </div> -->
- <!-- <div style="font-size: 12px; margin-top: 10px; color: blue">
- 上次统计时间:
- </div> -->
- </el-card>
- </div>
- </div>
- </template>
- <script setup lang="ts">
- import {
- h,
- ref,
- onMounted,
- computed,
- onUnmounted,
- reactive,
- getCurrentInstance,
- onBeforeMount
- } from "vue";
- // 导入 Element Plus 内置图标
- import { Bottom, Folder, Menu } from "@element-plus/icons-vue";
- import { deviceDetection } from "@pureadmin/utils";
- import AMapLoader from "@amap/amap-jsapi-loader";
- import { mapJson } from "@/api/mock";
- import PicCar from "@/assets/car.png";
- import PicCarDisable from "@/assets/car-disable.png";
- import { clear } from "console";
- import { duration } from "dayjs";
- import RtWatchPart from "./rtWatchPart.vue";
- import Location from "@/model/location";
- import Project from "@/model/project";
- const vGlobal = window.vueGlobal;
- export interface MapConfigureInter {
- on: Fn;
- destroy?: Fn;
- clearEvents?: Fn;
- addControl?: Fn;
- setCenter?: Fn;
- setZoom?: Fn;
- plugin?: Fn;
- }
- defineOptions({
- name: "Amap"
- });
- let MarkerCluster;
- let map: MapConfigureInter;
- let AMap = null;
- const instance = getCurrentInstance();
- const mapSet = reactive({
- loading: deviceDetection() ? false : true
- });
- // 菜单数据
- const menuItems = [
- {
- index: "1",
- title: "父菜单1",
- icon: Menu,
- children: [{ index: "1-1", title: "子菜单1-1", icon: Folder }]
- }
- ];
- const menuKey = ref(0);
- let dictMenus = {};
- let firstMenuId = null;
- // 当前选中的路径
- const activePath = ref("");
- // 计算所有父菜单的索引,用于默认展开所有子菜单
- const defaultOpeneds = computed(() => {
- // 提取所有父菜单的 index 组成数组
- return menuItems.map(item => item.index);
- });
- const markerAnchors = [];
- let scheduleId = null;
- const selLocation = ref(new Location());
- const cntPersonAttend = ref(0);
- const cntPersonOnline = ref(0);
- const cntPersonConfirm = ref(0);
- const cntAnchor = ref(0);
- const cntAnchorOnline = ref(0);
- const createMenus = () => {
- menuItems.splice(0);
- dictMenus = {};
- for (const p of vGlobal.vecProject) {
- const pIdx = "" + p.id;
- menuItems.push({
- index: pIdx,
- title: p.name,
- icon: Menu,
- children: []
- });
- dictMenus[pIdx] = p;
- for (const m of p.locations) {
- const mIdx = pIdx + "-" + m.id;
- menuItems[menuItems.length - 1].children.push({
- index: mIdx,
- title: m.name,
- icon: Folder
- });
- dictMenus[mIdx] = m;
- if (!firstMenuId) {
- firstMenuId = mIdx;
- }
- }
- }
- menuKey.value++;
- };
- // 处理菜单选中事件
- const handleMenuSelect = index => {
- activePath.value = index;
- console.log("dictMenus", dictMenus);
- console.log("handleMenuSelect", index, dictMenus[index]);
- const location = dictMenus[index];
- if (location) {
- selLocation.value = location;
- const proj = location.parent;
- map.setCenter([location.center.x, location.center.y]);
- map.setZoom(13);
- cntAnchor.value = 0;
- cntAnchorOnline.value = 0;
- for (const anc of vGlobal.vecAnchor) {
- if (Number(anc.projId) == Number(proj.id)) {
- cntAnchor.value++;
- if (anc.isOnline) {
- cntAnchorOnline.value++;
- }
- }
- }
- cntPersonAttend.value = 0;
- for (const p of selLocation.value.parent.persons) {
- cntPersonAttend.value++;
- }
- }
- };
- // 判断父菜单是否需要激活样式
- const isParentActive = parentIndex => {
- // 检查当前选中项是否属于该父菜单
- return activePath.value.startsWith(parentIndex + "-");
- };
- const handleOpen = (key: string, keyPath: string[]) => {
- console.log(key, keyPath);
- };
- const handleClose = (key: string, keyPath: string[]) => {
- console.log(key, keyPath);
- };
- const genMarkerLabel = anc => {
- const label = {
- direction: "bottom",
- //设置文本标注偏移量
- offset: new AMap.Pixel(-4, 0),
- //设置文本标注内容
- content: anc.isCharging
- ? `<div style="color: red; font-weight: bold; font-size: 14px; " > A[#${anc.id}](充电中)</div>`
- : `<div style="font-size: 14px; font-weight: bold; "> A[#${anc.id}](${anc.percentBattery}%)</div>`
- };
- return label;
- };
- const schedule = () => {
- cntPersonAttend.value = 0;
- if (selLocation.value.parent) {
- const proj = selLocation.value.parent;
- for (const p of proj.persons) {
- if (p.locId == selLocation.value.id) {
- cntPersonAttend.value++;
- }
- }
- cntAnchorOnline.value = 0;
- for (const anc of vGlobal.vecAnchor) {
- if (Number(anc.projId) == Number(proj.id)) {
- if (anc.isOnline) {
- cntAnchorOnline.value++;
- }
- }
- }
- }
- if (markerAnchors.length === 0) return;
- for (const m of markerAnchors) {
- const extData = m.getExtData();
- const anc = vGlobal.getAnchor(extData["id"]);
- if (!anc) continue;
- m.setPosition([anc.x, anc.y]);
- // m.moveTo([anc.x, anc.y],
- // {
- // duration: 500,
- // autoRotation: true,
- // });
- m.setLabel(genMarkerLabel(anc));
- if (extData["isOnline"] != anc.isOnline) {
- extData["isOnline"] = anc.isOnline;
- m.setExtData(extData);
- if (anc.isOnline) {
- m.setIcon(PicCar);
- } else {
- m.setIcon(PicCarDisable);
- }
- }
- }
- };
- onBeforeMount(() => {
- if (!instance) return;
- const { MapConfigure } = instance.appContext.config.globalProperties.$config;
- const { options } = MapConfigure;
- AMapLoader.load({
- key: MapConfigure.amapKey,
- version: "2.0",
- plugins: ["AMap.MarkerCluster", "AMap.MoveAnimation"]
- })
- .then(aMap => {
- AMap = aMap;
- // 创建地图实例
- map = new AMap.Map(instance.refs.mapview, options);
- //地图中添加地图操作ToolBar插件
- map.plugin(["AMap.ToolBar", "AMap.MapType", "AMap.ControlBar"], () => {
- map.addControl(new AMap.ToolBar());
- //地图类型切换
- map.addControl(
- new AMap.MapType({
- defaultType: 0,
- position: {
- right: "10px",
- top: "10px"
- }
- })
- );
- map.addControl(
- new AMap.ControlBar({
- position: {
- right: "10px",
- top: "120px"
- }
- })
- );
- });
- // MarkerCluster = new AMap.MarkerCluster(map, [], {
- // // 聚合网格像素大小
- // gridSize: 80,
- // maxZoom: 14,
- // renderMarker(ctx) {
- // const { marker, data } = ctx;
- // if (Array.isArray(data) && data[0]) {
- // const { driver, plateNumber, orientation } = data[0];
- // const content = `<img style="transform: scale(1) rotate(${
- // 360 - Number(orientation)
- // }deg);" src='${car}' />`;
- // marker.setContent(content);
- // marker.setLabel({
- // direction: "bottom",
- // //设置文本标注偏移量
- // offset: new AMap.Pixel(-4, 0),
- // //设置文本标注内容
- // content: `<div> ${plateNumber}(${driver})</div>`
- // });
- // marker.setOffset(new AMap.Pixel(-18, -10));
- // marker.on("click", ({ lnglat }) => {
- // map.setZoom(13); //设置地图层级
- // map.setCenter(lnglat);
- // });
- // }
- // }
- // });
- complete();
- })
- .catch(() => {
- mapSet.loading = false;
- throw "地图加载失败,请重新加载";
- });
- });
- const handleResize = () => {
- if (map) {
- map.resize(); // 调用高德地图的resize方法重新计算尺寸
- }
- };
- onMounted(() => {
- const ci = setInterval(() => {
- if (vGlobal.isInited()) {
- clearInterval(ci);
- createMenus();
- handleMenuSelect(firstMenuId);
- console.log("anchors", vGlobal.vecAnchor);
- window.addEventListener("resize", handleResize);
- for (const v of vGlobal.vecAnchor) {
- const marker = new AMap.Marker({
- extData: {
- id: v.id,
- isOnline: v.isOnline
- },
- position: [v.x, v.y],
- icon: v.isOnline ? PicCar : PicCarDisable,
- offset: new AMap.Pixel(-18, -10),
- label: {
- direction: "bottom",
- //设置文本标注偏移量
- offset: new AMap.Pixel(-4, 0),
- //设置文本标注内容
- content: v.isCharging
- ? `<div> A[#${v.id}](充电中)</div>`
- : `<div> A[${v.id}](${v.percentBattery})</div>`
- }
- });
- marker.on("click", ({ lnglat }) => {
- // map.setZoom(13); //设置地图层级
- // map.setCenter(lnglat);
- const anc = vGlobal.getAnchor(marker.getExtData()["id"]);
- if (!anc) return;
- //信息窗体的内容
- var content = [
- `<div style="font-size: 14px; "><b style="font-size: 16px; ">A[#${anc.id}]</b>`,
- `<b>经度:</b> ${lnglat.lng} <b>纬度:</b> ${lnglat.lat}`,
- `<b>电池${anc.isCharging ? "状态" : "电量"}:</b> ${anc.isCharging ? "充电中" : anc.percentBattery + "%"}`,
- "</div>"
- ];
- //创建 infoWindow 实例
- var infoWindow = new AMap.InfoWindow({
- content: content.join("<br>"), //传入字符串拼接的 DOM 元素
- anchor: "top-left"
- });
- //打开信息窗体
- infoWindow.open(map, marker.getPosition()); //map 为当前地图的实例,map.getCenter() 用于获取地图中心点坐标。
- });
- marker.setMap(map);
- markerAnchors.push(marker);
- }
- }
- }, 100);
- console.log("onMounted");
- scheduleId = setInterval(() => {
- schedule();
- }, 1000);
- });
- onUnmounted(() => {
- window.removeEventListener("resize", handleResize);
- if (map) {
- // 销毁地图实例
- map.destroy() && map.clearEvents("click");
- }
- clearInterval(scheduleId);
- console.log("onUnmounted");
- scheduleId = null;
- });
- // 地图创建完成(动画关闭)
- const complete = (): void => {
- if (map) {
- map.on("complete", () => {
- mapSet.loading = false;
- });
- }
- };
- </script>
- <style scoped>
- .custom-menu {
- width: 100%;
- }
- :deep(.el-sub-menu .el-menu-item.is-active) {
- color: #0557aa !important;
- background-color: #e6f7ff !important;
- }
- /* 子菜单选中样式 */
- :deep(.el-menu-item.is-active) {
- color: #0557aa !important;
- background-color: #e6f7ff !important;
- font-weight: 600;
- }
- /* 父菜单激活样式 - 当子菜单选中时 */
- :deep(.parent-active .el-sub-menu__title) {
- color: #0557aa !important;
- font-weight: bold;
- }
- /* 父菜单 hover 样式保持一致 */
- :deep(.el-sub-menu__title:hover) {
- color: #0557aa !important;
- }
- #mapview {
- height: calc(100vh - 48px);
- width: 100%;
- grid-area: 1 / 1 / -1 / -1;
- }
- /* 新增根元素高度设置 */
- :root {
- height: 100%;
- }
- /* 确保父级容器高度正确传递 */
- :deep(html),
- :deep(body) {
- height: 100%;
- margin: 0;
- padding: 0;
- }
- /* 调整网格容器样式 */
- :deep(.grid-container) {
- /* 假设地图外层容器添加class="grid-container" */
- display: grid;
- height: 100%;
- }
- .float-stat {
- width: 200px;
- height: fit-content;
- background: #ffffff;
- grid-area: 1 / 1 / -1 / -1;
- z-index: 10;
- margin-top: 10px;
- margin-left: 10px;
- border-radius: 2px;
- opacity: 0.9;
- box-shadow: rgba(0, 0, 0, 0.6) 0 2px 2px;
- display: flex;
- flex-direction: row;
- }
- .float-stat-item {
- width: 100%;
- display: flex;
- flex-direction: row;
- }
- .float-stat-item-key {
- font-weight: 900;
- width: 80px;
- }
- .float-stat-item-value {
- font-weight: 600;
- width: 50px;
- text-align: right;
- }
- :deep(.amap-marker-label) {
- border: none !important;
- }
- :deep(.amap-copyright) {
- display: none !important;
- }
- :deep(.amap-logo) {
- display: none !important;
- }
- </style>
|