| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543 |
- <template>
- <div class="myapp map-container">
- <MapCadViewer
- ref="viewerRef"
- :map-config="mapConfig"
- :start-in-cad-mode="START_IN_CAD_MODE"
- @mode-change="onModeChange"
- @feature-select="onFeatureSelect"
- @marker-click="onMarkerClick"
- @marker-open-cad="onMarkerOpenCad"
- @icon-placed="onIconPlaced"
- @icon-moved="onIconMoved"
- @device-click="onDeviceClick"
- @device-popup-close="onDevicePopupClose"
- @icon-deleted="onIconDeleted"
- /> </div
- ><div v-if="!isTopLevel" class="map-reset-btn">
- <Button :loading="mapLoading" type="primary" @click="revertStack">返回上级</Button>
- </div>
- <!-- 返回上级 -->
- <div v-if="!isTopLevel" class="map-reset-btn">
- <Button :loading="mapLoading" type="primary" @click="revertStack">返回上级</Button>
- </div>
- <!-- 省域地图监测区图例(图3-1) -->
- <div v-if="!cadOpened" class="map-legend">
- <div class="legend-title">图例</div>
- <div class="legend-row"><span class="legend-dot legend-dot-red"></span>疑似隐蔽工作面</div>
- <div class="legend-row"><span class="legend-dot legend-dot-orange"></span>风量异常</div>
- <div class="legend-row"><span class="legend-dot legend-dot-yellow"></span>数据掉线</div>
- <div class="legend-row"><span class="legend-dot legend-dot-green"></span>监测正常</div>
- </div>
- <!-- 矿端通风系统图监测区图例(图3-3) -->
- <div v-if="cadOpened" class="map-legend">
- <div class="legend-title">图例</div>
- <div class="legend-row"><span class="legend-dot legend-dot-green"></span>测风正常</div>
- <div class="legend-row"><span class="legend-dot legend-dot-orange"></span>风量异常</div>
- <div class="legend-row"><span class="legend-dot legend-dot-red-dashed"></span>缺失测风点</div>
- <div class="legend-row"><span class="legend-line-red"></span>疑似隐蔽巷道</div>
- </div>
- </template>
- <script setup lang="ts">
- import { ref, computed, onMounted, onBeforeUnmount } from 'vue';
- // 本地调试组件时使用
- // import 'map-cad-viewer/dist/map-cad-viewer.css';
- // import { MapCadViewer } from 'map-cad-viewer';
- // import type { CadData, MapConfig, BoundaryConfig, MapMarker } from 'map-cad-viewer/dist/types/index.d';
- // 直接加载打包好的组件
- // import { MapCadViewer } from '/@/components/map/map-cad-viewer.es';
- // import '/@/components/map/map-cad-viewer.css';
- // import type { CadData, MapConfig, BoundaryConfig, MapMarker } from '/@/components/map/types/index.d';
- import { Button } from 'ant-design-vue';
- import { useAppStore } from '/@/store/modules/app';
- import { useMineDepartmentStore } from '/@/store/modules/mine';
- import { get, last } from 'lodash-es';
- // import { getMarkerCollection, getShanxiGeoJSON } from '/@/api/sys/map';
- // import { findPath, listToTree } from '/@/utils/helper/treeHelper';
- // import { watchTriggerable } from '@vueuse/core';
- import { MapCadViewer } from '/@/components/map/map-cad-viewer.es';
- import '/@/components/map/map-cad-viewer.css';
- import type { CadData, MapConfig, MapMarker, MarkerPopup } from '/@/components/map/types/index.d';
- const props = defineProps<{
- silence: boolean;
- }>();
- const appStore = useAppStore();
- const mineStore = useMineDepartmentStore();
- const mapLoading = ref(false);
- // 设置地图中当前的参数以供其他页面使用
- appStore.setSimpleMapParams({ deptId: mineStore.getRootId, isLeaf: mineStore.getRoot?.isLeaf });
- // CAD是否可见
- const cadOpened = ref<boolean>(false);
- const GEO_LAYER_ID = 'geojson-layer';
- const GEO_BORDER_ID = 'geoline-layer';
- const DEFAULT_NODE = {
- id: mineStore.getRootId!,
- name: '根节点',
- parentId: null,
- longitude: 109.425873,
- latitude: 35.83317,
- zoom: 6,
- isLeaf: false,
- };
- const DEFAULT_TREE_CONFIG = {
- id: 'id',
- pid: 'parentId',
- };
- const DEFAULT_TREE_CONFIG = {
- id: 'id',
- pid: 'parentId',
- };
- // 用于存储历史节点的栈,实现返回上一级功能
- const historyStack = ref<any[]>([DEFAULT_NODE]);
- const isTopLevel = computed(() => get(historyStack.value, 'length', 1) === 1);
- const fileOptions = ref<any[]>([]);
- const fileId = ref();
- // 设置地图中当前的参数以供其他页面使用
- appStore.setSimpleMapParams({ deptId: mineStore.getRootId, isLeaf: mineStore.getRoot?.isLeaf });
- const viewerRef = ref<InstanceType<typeof MapCadViewer>>();
- // _dwg_req_newcal@
- // ===== 启动配置 =====
- /** 启动后默认进入的视图:false=地图页面, true=CAD 图纸页面 */
- const START_IN_CAD_MODE = false;
- /** 地图配置 */
- const mapConfig: Partial<MapConfig> = {
- center: [114.0, 38.0],
- zoom: 5,
- tileUrl: 'http://182.92.126.35:9999/sys/common/static/map/{z}/{x}/{y}.png',
- };
- const cadData = ref<CadData | undefined>();
- const currentIconId = ref('speed');
- /** 当前点击的设备图标(用于定时刷新时重建 HTML 内容) */
- const currentDeviceIcon = ref<{ id: number; type: string; name?: string } | null>(null);
- /** 设备类型 → 中文名称映射(弹框"类型"行显示用) */
- const DEVICE_TYPE_NAMES: Record<string, string> = {
- gate: '风门',
- modelsensor: '传感器',
- obfurage: '密闭',
- speed: '测风装置',
- };
- /** 设备弹框数据刷新定时器(每 3 秒触发一次) */
- let devicePopupTimer: ReturnType<typeof setInterval> | null = null;
- function onModeChange(mode: string) {
- console.log('Viewer mode changed:', mode);
- currentMode.value = mode as any;
- }
- const currentMode = ref<'map' | 'cad' | 'drawing'>('map');
- const isCadMode = computed(() => currentMode.value === 'cad' || currentMode.value === 'drawing');
- /** 点击 CAD 图元(线/面/文字等)回调:输出图元信息到控制台 */
- function onFeatureSelect(feature: any) {
- console.log('Selected feature:', feature.id, feature.typename, 'layer:', feature.layer);
- }
- /** 点击地图标注点回调:输出标注点信息到控制台 */
- function onMarkerClick(marker: any) {
- console.log('Marker clicked:', marker.label, 'coords:', marker.coords, 'switchToCad:', marker.switchToCad, 'popup:', marker.popup);
- }
- /**
- * 点击标注点弹框中的 CAD 入口回调
- * 携带预先定义好的设备图标数组(演示用)一并传入 CAD 视图,
- * 使这些图标按类型和坐标直接绘制到图纸上。
- */
- function onMarkerOpenCad(marker: any) {
- console.log('Marker title clicked, opening CAD:', marker.label);
- if (cadData.value) {
- viewerRef.value?.switchToCadMode(marker, cadData.value);
- // 设置 CAD 画布背景色(深色背景,无默认图元颜色兜底)
- viewerRef.value?.setCADMapColor(false, [0, 0, 0]);
- }
- }
- /**
- * 根据设备 id/type 动态拼接弹框的 HTML div(标题、类型、数据值)
- * @param icon 设备图标信息(id/type/name)
- * @returns HTML 字符串,包含设备ID、类型、以及按类型区分的模拟实时数据行
- * 说明:数据值目前为模拟数据(随机数),实际项目中可替换为接口请求结果
- */
- function buildDevicePopupHtml(icon: { id: number; type: string; name?: string }): string {
- // 优先用类型映射得到中文名称,其次用传入 name,最后回退为 type 原文
- const typeName = DEVICE_TYPE_NAMES[icon.type] || icon.name || icon.type;
- // 生成 [min, max) 区间随机数,保留 dec 位小数
- const rand = (min: number, max: number, dec = 1) => (min + Math.random() * (max - min)).toFixed(dec);
- // 根据不同类型拼接不同的"数据值"行
- let dataRows = '';
- switch (icon.type) {
- case 'gate': // 风门:开关状态 + 风量
- dataRows = `
- <div class="dev-row"><span class="dev-key">开关状态</span><span class="dev-val">${Math.random() > 0.5 ? '开启' : '关闭'}</span></div>
- <div class="dev-row"><span class="dev-key">风量</span><span class="dev-val">${rand(800, 3200, 0)} m³/h</span></div>`;
- break;
- case 'modelsensor': // 传感器:温度 + 湿度
- dataRows = `
- <div class="dev-row"><span class="dev-key">温度</span><span class="dev-val">${rand(15, 35)} ℃</span></div>
- <div class="dev-row"><span class="dev-key">湿度</span><span class="dev-val">${rand(30, 90)} %</span></div>`;
- break;
- case 'obfurage': // 密闭:密闭状态 + 压差
- dataRows = `
- <div class="dev-row"><span class="dev-key">密闭状态</span><span class="dev-val">${Math.random() > 0.3 ? '正常' : '异常'}</span></div>
- <div class="dev-row"><span class="dev-key">压差</span><span class="dev-val">${rand(0, 500)} Pa</span></div>`;
- break;
- case 'speed': // 测风装置:风速 + 风向
- dataRows = `
- <div class="dev-row"><span class="dev-key">风速</span><span class="dev-val">${rand(0.5, 12)} m/s</span></div>
- <div class="dev-row"><span class="dev-key">风向</span><span class="dev-val">${['N', 'NE', 'E', 'SE', 'S', 'SW', 'W', 'NW'][Math.floor(Math.random() * 8)]}</span></div>`;
- break;
- default: // 其他类型:通用数值
- dataRows = `
- <div class="dev-row"><span class="dev-key">数值</span><span class="dev-val">${rand(0, 100)}</span></div>`;
- }
- // 组装完整弹框 HTML:设备ID + 类型 + 数据行
- return `
- <div class="dev-popup">
- <div class="dev-row"><span class="dev-key">设备ID</span><span class="dev-val">${icon.id}</span></div>
- <div class="dev-row"><span class="dev-key">类型</span><span class="dev-val">${typeName}</span></div>
- ${dataRows}
- </div>`;
- }
- // 切换当前选择的省,市,和描点,重新绘制蒙版和标识
- function changeView() {
- const boundary: BoundaryConfig = {
- provinces: ['山西省'],
- cities: ['太原市', '晋中市', '长治市'],
- counties: [],
- fillColor: [129, 230, 246, 30],
- strokeColor: [59, 130, 246, 160],
- regionColors: {
- 太原市: { fillColor: [50, 280, 50, 30], strokeColor: [50, 280, 50, 200] },
- 晋中市: { fillColor: [140, 20, 112, 30], strokeColor: [140, 20, 112, 180] },
- },
- };
- const newMarkers: MapMarker[] = [
- { coords: [112.7, 37.8], label: '2', color: [255, 60, 60, 250], fontColor: [255, 255, 255, 255], borderColor: [0, 0, 0, 255] },
- {
- coords: [112.2, 38.2],
- label: '',
- color: [60, 200, 60, 250],
- fontColor: [255, 255, 255, 255],
- borderColor: [0, 0, 0, 255],
- switchToCad: true,
- popup: {
- title: '太原市',
- items: [
- { subtitle: '矿井名称', content: '一号煤矿' },
- { subtitle: '设计产能', content: '120 万吨/年' },
- ],
- },
- },
- ];
- viewerRef.value?.changeView(boundary, newMarkers);
- setMapView([112.7, 37.8], 7.2, 2000);
- }
- /**
- * 点击 CAD 中绘制的设备图标回调
- * 1. 保存当前设备(供定时器重建 HTML)
- * 2. 立即拼接一次 HTML 并传给图纸弹框显示
- * 3. 启动 3 秒定时器,周期性刷新弹框数据(模拟实时数据)
- */
- function onDeviceClick(icon: { id: number; type: string; name?: string }) {
- console.log('Device clicked:', icon);
- currentDeviceIcon.value = icon;
- // 立即拼接一次并显示
- viewerRef.value?.showDevicePopup(icon.id, icon.type, buildDevicePopupHtml(icon));
- // 每 3 秒更新一次新的 div 数据到 CAD 图纸弹框
- if (devicePopupTimer) clearInterval(devicePopupTimer); // 防重复启动
- devicePopupTimer = setInterval(() => {
- if (currentDeviceIcon.value && viewerRef.value) {
- viewerRef.value.showDevicePopup(currentDeviceIcon.value.id, currentDeviceIcon.value.type, buildDevicePopupHtml(currentDeviceIcon.value));
- }
- }, 3000);
- }
- /** 组件卸载前清理:兜底停止定时器,防止内存泄漏 */
- onBeforeUnmount(() => {
- if (devicePopupTimer) {
- clearInterval(devicePopupTimer);
- devicePopupTimer = null;
- }
- });
- /** 设备弹框关闭回调:停止刷新定时器并清空当前设备 */
- function onDevicePopupClose() {
- console.log('Device popup closed, stopping timer');
- if (devicePopupTimer) {
- clearInterval(devicePopupTimer);
- devicePopupTimer = null;
- }
- currentDeviceIcon.value = null;
- }
- /** 切换地图中心点和缩放级别,仅在地图模式下生效 */
- // 飞到位置,zoom 14,动画 1 秒
- //setMapView([113.7, 38.6], 14, 1000)
- function setMapView(center: [number, number], zoom: number, duration: number = 500) {
- viewerRef.value?.setMapView(center, zoom, duration);
- }
- // CAD 控制方法
- /** 返回地图:退出 CAD 视图,恢复到地图页面 */
- function goBackToMap() {
- viewerRef.value?.goBackToMap();
- }
- /** 切换图层面板显示/隐藏 */
- function toggleLayerPanel() {
- viewerRef.value?.toggleLayerPanel();
- }
- /** 缩放至全图:重置视图为 CAD 图纸整体范围 */
- function zoomFit() {
- viewerRef.value?.zoomToFit();
- }
- /**
- * 选择要绘制的图标类型
- * item 图标定义(type 为类型标识)
- * 若当前处于绘制模式则即时切换绘制工具,否则仅更新选中状态
- */
- function selectIcon(type: string) {
- currentIconId.value = type;
- if (currentMode.value === 'drawing' && viewerRef.value) {
- viewerRef.value.setDrawingTool(type);
- } else {
- viewerRef.value?.setSelectedIcon(type);
- }
- }
- /** 清除所有图标 */
- function clearIcons() {
- viewerRef.value?.clearPlacedIcons();
- }
- /** 绘制模式下放置了设备图标回调 */
- function onIconPlaced(icon: any) {
- console.log('Icon placed:', icon);
- }
- /** 拖动设备图标后位置更新回调 */
- function onIconMoved(icon: any) {
- console.log('Icon moved:', icon);
- }
- /**
- * 删除模式下图标被删除回调:接收被删图标的 id/type
- * 若删除的正是当前弹框显示的设备,则关闭弹框并停止刷新定时器
- */
- function onIconDeleted(icon: { id: number; type: string }) {
- console.log('Icon deleted:', icon);
- if (currentDeviceIcon.value && currentDeviceIcon.value.id === icon.id) {
- onDevicePopupClose();
- }
- }
- /** 返回上一级操作,如果存在CAD底图,则切换至地图,否则回退历史记录栈 */
- async function revertStack() {
- if (isTopLevel.value) return;
- historyStack.value.pop();
- }
- /** 页面挂载后加载 CAD 数据,并按配置决定初始视图 */
- onMounted(async () => {
- try {
- // 请求 CAD 原始数据(图元 + 图层颜色表)
- const resp = await fetch('/hll.json');
- const data: CadData = await resp.json();
- cadData.value = data;
- console.log(`Loaded ${data.features.length} CAD features`);
- // 根据配置选择初始视图
- if (START_IN_CAD_MODE && viewerRef.value) {
- // 不飞向标注点,直接进入 CAD 全图视图(无预置图标)
- viewerRef.value.switchToCadMode(undefined, data, []);
- // 设置 CAD 画布背景色(深色背景,无默认图元颜色兜底)
- viewerRef.value?.setCADMapColor(false, [0, 0, 0]);
- } else {
- setTimeout(() => {
- changeView();
- }, 1000);
- }
- } catch (e) {
- // 开发环境没有 features.json 时降级(仅打印警告,不影响地图模式使用)
- console.warn('Could not load features.json (expected in dev)', e);
- }
- });
- </script>
- <style lang="less">
- .map-container {
- flex: 1;
- height: 100%;
- width: 100%;
- position: fixed;
- top: 0;
- left: 0;
- z-index: @simple-map-z-index;
- // filter: grayscale(80%) invert(0%) sepia(10%) hue-rotate(150deg) saturate(200%) brightness(100%) contrast(100%);
- canvas {
- /* width: 1920px !important;
- height: 948px !important; */
- }
- }
- /* 图例浮层 */
- .map-legend {
- position: fixed;
- bottom: 24px;
- left: 24px;
- z-index: @simple-map-z-index + 1;
- background: rgba(255, 255, 255, 0.92);
- border-radius: 8px;
- padding: 10px 14px;
- font-size: 12px;
- line-height: 1.8;
- box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
- color: #333;
- min-width: 140px;
- pointer-events: none;
- .legend-title {
- font-weight: 600;
- margin-bottom: 4px;
- font-size: 13px;
- }
- .legend-row {
- display: flex;
- align-items: center;
- gap: 6px;
- }
- .legend-dot {
- display: inline-block;
- width: 10px;
- height: 10px;
- border-radius: 50%;
- flex-shrink: 0;
- }
- .legend-dot-green {
- background: #52c41a;
- }
- .legend-dot-orange {
- background: #fa8c16;
- }
- .legend-dot-yellow {
- background: #fadb14;
- }
- .legend-dot-red {
- background: #f5222d;
- }
- .legend-dot-red-dashed {
- background: transparent;
- border: 2px dashed #f5222d;
- }
- .legend-line-red {
- display: inline-block;
- width: 14px;
- height: 3px;
- background: #f5222d;
- flex-shrink: 0;
- }
- }
- </style>
- <style>
- /* ============ CAD 工具栏(地图/CAD 共用,由 App.vue 控制界面) ============ */
- .cad-toolbar {
- position: absolute;
- top: 12px;
- left: 50%;
- transform: translateX(-50%); /* 水平居中 */
- z-index: 400; /* 悬浮于地图/CAD 画布之上 */
- display: flex;
- gap: 6px;
- background: rgba(20, 20, 30, 0.9); /* 半透明深色底 */
- border: 1px solid rgba(255, 255, 255, 0.15);
- border-radius: 8px;
- padding: 6px 10px;
- backdrop-filter: blur(8px); /* 毛玻璃背景 */
- }
- /* 工具栏通用按钮 */
- .cad-btn {
- background: rgba(255, 255, 255, 0.08);
- border: 1px solid rgba(255, 255, 255, 0.12);
- color: #ccc;
- padding: 6px 12px;
- border-radius: 6px;
- font-size: 12px;
- cursor: pointer;
- transition: all 0.15s;
- white-space: nowrap;
- }
- .cad-btn:hover {
- background: rgba(255, 255, 255, 0.15);
- color: #fff;
- }
- /* 激活态(绘制/删除模式按钮高亮) */
- .cad-btn.active {
- background: rgba(59, 130, 246, 0.4);
- border-color: rgba(59, 130, 246, 0.6);
- color: #fff;
- }
- /* ============ 绘制模式工具栏(底部,当前隐藏预留) ============ */
- .drawing-toolbar {
- position: absolute;
- bottom: 24px;
- left: 50%;
- transform: translateX(-50%);
- z-index: 400;
- display: flex;
- align-items: center;
- gap: 8px;
- background: rgba(20, 20, 30, 0.92);
- border: 1px solid rgba(255, 255, 255, 0.15);
- border-radius: 8px;
- padding: 8px 14px;
- backdrop-filter: blur(8px);
- }
- .drawing-label {
- color: #aaa;
- font-size: 12px;
- }
- .drawing-icons {
- display: flex;
- gap: 4px;
- }
- /* 图标选择按钮(背景显示对应 SVG 图标) */
- .icon-btn {
- background: rgba(255, 255, 255, 0.06);
- border: 1px solid rgba(255, 255, 255, 0.1);
- border-radius: 6px;
- padding: 4px;
- cursor: pointer;
- display: flex;
- align-items: center;
- justify-content: center;
- width: 32px;
- height: 32px;
- transition: all 0.15s;
- }
- /* 标注点弹框标题(changeView 动态 HTML 中使用) */
- .marker-info-popup .mp-title2 {
- margin-top: 10px;
- font-size: 20px;
- color: #ce1616;
- text-align: center;
- }
- </style>
|