| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429 |
- <template>
- <div class="map-wrapper">
- <div ref="mapContainer" class="map-container"></div>
- <div class="map-header" v-if="initialized" :style="privateStyle.search">
- <div class="search-form">
- <input type="text" v-model="searchQuery" placeholder="请输入路段或设备名称" class="search-input"
- @keyup.enter="handleSearch" />
- <button class="search-btn" @click="handleSearch">查询</button>
- </div>
- <div class="action-box" @click="toggleAll">全选 ▾</div>
- </div>
- <div class="map-legend" v-if="initialized" :style="privateStyle.legend">
- <div class="legend-title">图例</div>
- <div class="legend-list">
- <div v-for="item in legendConfig" :key="item.type" class="legend-item"
- :class="{ 'is-hidden': !activeLegends.includes(item.type) }" @click="handleLegendClick(item.type)">
- <span class="legend-dot" :style="{ backgroundColor: item.color }"></span>
- <span class="legend-label">{{ item.label }}</span>
- </div>
- </div>
- </div>
- </div>
- </template>
- <script>
- import AMapLoader from '@amap/amap-jsapi-loader';
- export default {
- name: "TrafficMap",
- props: {
- amapKey: { type: String, default: '您的Key' },
- securityJsCode: { type: String, default: '您的安全密钥' }
- },
- data() {
- return {
- AMap: null,
- map: null,
- drivingInstances: [], // 存储路径规划实例
- infoWindow: null,
- initialized: false,
- searchQuery: '', // 搜索内容绑定
- activeLegends: [],
- legendConfig: [
- { type: 'all_selected', label: '全选', color: '#e5e5e5' },
- { type: 'center_plan', label: '中心计划', color: '#32c5ff' },
- { type: 'trunk_coord', label: '干线协调', color: '#3ee68d' },
- { type: 'service_route', label: '勤务路线', color: '#ffcc33' },
- { type: 'periodic', label: '定周期控制', color: '#00ccff' },
- { type: 'induction', label: '感应控制', color: '#7ed3b2' },
- { type: 'adaptive', label: '自适应控制', color: '#8ca1ff' },
- { type: 'manual', label: '手动控制', color: '#cc8d66' },
- { type: 'special', label: '特殊控制', color: '#ffb33b' },
- { type: 'offline', label: '离线', color: '#6d7791' },
- { type: 'degraded', label: '降级', color: '#c4a737' },
- { type: 'fault', label: '故障', color: '#ff4d4f' }
- ],
- overlayGroups: {},
- privateStyle: {
- search: {},
- legend: {}
- }
- };
- },
- mounted() {
- this.initAMap();
- // 自定义首页地图搜索和图例位置样式
- if (this.$route.path === '/home') {
- this.privateStyle.search = { top: "100px", left: "25%", right: "25%" };
- this.privateStyle.legend = { right: "25%" };
- }
- },
- beforeDestroy() {
- if (this.map) {
- this.map.destroy();
- }
- },
- methods: {
- async initAMap() {
- // 1. 配置安全密钥(必须在 load 之前)
- window._AMapSecurityConfig = {
- securityJsCode: this.securityJsCode,
- };
- try {
- // 2. 加载地图核心及插件
- const AMap = await AMapLoader.load({
- key: this.amapKey,
- version: "2.0",
- plugins: ['AMap.Driving']
- });
- this.AMap = AMap;
- // 3. 实例化地图
- this.map = new AMap.Map(this.$refs.mapContainer, {
- zoom: 14,
- mapStyle: "amap://styles/darkblue",
- viewMode: "3D",
- center: [116.661132, 39.902996], // 通州火车站中心
- });
- // 4. 等待地图加载完成后执行路径规划
- this.map.on('complete', () => {
- console.log("地图加载完成,开始绘制真实路网...");
- this.initialized = true;
- this.activeLegends = this.legendConfig.map(l => l.type);
- this.drawStaticRoutes();
- });
- } catch (err) {
- console.error('地图加载失败:', err);
- }
- },
- drawStaticRoutes() {
- // 定义 3横 3纵 坐标(确保起终点大致跨越通州核心区,让 Driving 自动找路)
- const routeConfigs = [
- // --- 横向 3 条 ---
- { name: "新华大街", start: [116.642, 39.910], end: [116.680, 39.911], color: "#32c5ff" },
- { name: "玉带河大街", start: [116.642, 39.902], end: [116.680, 39.903], color: "#3ee68d" },
- { name: "运河西大街", start: [116.642, 39.894], end: [116.680, 39.895], color: "#ffcc33" },
- // --- 纵向 3 条 ---
- { name: "车站路", start: [116.652, 39.915], end: [116.653, 39.889], color: "#fc8c23" },
- { name: "新华南路", start: [116.661, 39.916], end: [116.661, 39.889], color: "#8ca1ff" },
- { name: "东关大道", start: [116.671, 39.916], end: [116.671, 39.890], color: "#cc8d66" }
- ];
- routeConfigs.forEach(config => {
- // 为每一条路创建一个 Driving 实例
- const driving = new this.AMap.Driving({
- map: this.map, // 直接展现结果在地图上
- hideMarkers: false, // 显示起终点 Marker
- autoFitView: false, // 禁止自动缩放(防止多条线时地图乱跳)
- outlineColor: '#000', // 路线描边
- // 默认样式是蓝色,由于 Driving.search 不直接支持自定义颜色,
- // 这里我们先渲染默认路线。
- });
- // 执行搜索
- driving.search(config.start, config.end, (status, result) => {
- if (status === 'complete') {
- console.log(`${config.name} 绘制成功`);
- } else {
- console.error(`${config.name} 绘制失败:`, result);
- }
- });
- // this.drivingInstances.push(driving);
- });
- },
- // 搜索查询逻辑
- handleSearch() {
- if (!this.searchQuery.trim()) {
- alert("请输入查询内容");
- return;
- }
- console.log("正在执行搜索:", this.searchQuery);
- // 这里可以扩展具体的搜索逻辑,比如搜索 POI 或在已有 overlayGroups 中高亮匹配项
- alert(`已提交查询:${this.searchQuery}`);
- },
- drawTrafficScene() {
- const roads = [
- { type: 'center_plan', name: '新华南北路 - 中心控制段', path: [[116.665, 39.940], [116.665, 39.885]], hasDots: true },
- { type: 'trunk_coord', name: '通胡大街 - 干线协调(北)', path: [[116.620, 39.930], [116.650, 39.920], [116.700, 39.930]] },
- { type: 'trunk_coord', name: '运河东大街 - 干线协调(南)', path: [[116.615, 39.900], [116.660, 39.910], [116.710, 39.900]] },
- { type: 'service_route', name: '新华东街 - 勤务专用线', path: [[116.625, 39.905], [116.700, 39.905]] },
- { type: 'fault', name: '路县故城周边 - 设备异常', path: [[116.685, 39.920], [116.690, 39.905]] }
- ];
- roads.forEach(road => this.renderRoadWithStyle(road));
- },
- renderRoadWithStyle(road) {
- const config = this.legendConfig.find(l => l.type === road.type);
- const group = [];
- const glow = new this.AMap.Polyline({
- path: road.path, strokeColor: config.color, strokeOpacity: 0.15, strokeWeight: 20, map: this.map
- });
- const line = new this.AMap.Polyline({
- path: road.path, strokeColor: config.color, strokeWeight: 6, map: this.map
- });
- [glow, line].forEach(item => {
- item.on('click', (e) => this.openDetailWindow(road, e.lnglat));
- group.push(item);
- });
- if (road.hasDots) {
- const dots = this.calculatePathDots(road.path, 12);
- dots.forEach((pos, index) => {
- const marker = new this.AMap.Marker({
- position: pos,
- content: `<div class="pulse-dot"></div>`,
- offset: new this.AMap.Pixel(-6, -6),
- map: this.map
- });
- marker.on('click', (e) => this.openDetailWindow({ ...road, name: `${road.name}-监测点${index + 1}` }, e.lnglat));
- group.push(marker);
- });
- }
- this.overlayGroups[road.type] = (this.overlayGroups[road.type] || []).concat(group);
- },
- calculatePathDots(path, count) {
- const p1 = path[0], p2 = path[1];
- const dots = [];
- for (let i = 1; i < count; i++) {
- dots.push([p1[0] + (p2[0] - p1[0]) * (i / count), p1[1] + (p2[1] - p1[1]) * (i / count)]);
- }
- return dots;
- },
- openDetailWindow(data, lnglat) {
- const config = this.legendConfig.find(l => l.type === data.type);
- const content = `
- <div class="custom-info-card">
- <div class="card-header" style="background: ${config.color}22; border-left: 4px solid ${config.color}">
- <span class="title">${data.name}</span>
- </div>
- <div class="card-body">
- <div class="info-row"><span class="label">管控类型:</span><span class="value" style="color:${config.color}">${config.label}</span></div>
- <div class="info-row"><span class="label">当前状态:</span><span class="value" style="color:#3ee68d">运行中</span></div>
- </div>
- </div>
- `;
- this.infoWindow.setContent(content);
- this.infoWindow.open(this.map, lnglat);
- },
- handleLegendClick(type) {
- if (type === 'all_selected') return this.toggleAll();
- const isVisible = this.activeLegends.includes(type);
- this.activeLegends = isVisible ? this.activeLegends.filter(t => t !== type) : [...this.activeLegends, type];
- const group = this.overlayGroups[type];
- if (group) group.forEach(o => isVisible ? o.hide() : o.show());
- if (isVisible) this.infoWindow.close();
- },
- toggleAll() {
- const allTypes = this.legendConfig.map(l => l.type);
- const isShowingAll = this.activeLegends.length === allTypes.length;
- allTypes.forEach(type => {
- const group = this.overlayGroups[type];
- if (group) group.forEach(o => isShowingAll ? o.hide() : o.show());
- });
- this.activeLegends = isShowingAll ? [] : allTypes;
- this.infoWindow.close();
- }
- }
- };
- </script>
- <style scoped>
- .map-wrapper {
- position: relative;
- width: 100%;
- height: 100%;
- min-height: 500px;
- background: #010813;
- overflow: hidden;
- }
- .map-container {
- width: 100%;
- height: 100%;
- }
- /* 头部样式:增加了搜索表单布局 */
- .map-header {
- position: absolute;
- top: 55px;
- left: 50px;
- right: 50px;
- display: flex;
- justify-content: space-between;
- align-items: center;
- z-index: 10;
- }
- /* 搜索表单容器 */
- .search-form {
- display: flex;
- background: rgba(13, 35, 67, 0.9);
- border: 1px solid #1a4a8d;
- border-radius: 4px;
- overflow: hidden;
- }
- .search-input {
- background: transparent;
- border: none;
- padding: 10px 15px;
- color: #fff;
- width: 240px;
- outline: none;
- font-size: 13px;
- }
- .search-input::placeholder {
- color: #5b7da8;
- }
- .search-btn {
- background: #1a4a8d;
- border: none;
- color: #fff;
- padding: 0 20px;
- cursor: pointer;
- font-size: 13px;
- transition: background 0.2s;
- }
- .search-btn:hover {
- background: #2660b3;
- }
- .action-box {
- background: rgba(13, 35, 67, 0.9);
- border: 1px solid #1a4a8d;
- padding: 10px 18px;
- color: #fff;
- border-radius: 4px;
- font-size: 13px;
- cursor: pointer;
- }
- /* 图例与其它样式保持不变 */
- .map-legend {
- position: absolute;
- right: 50px;
- bottom: 40px;
- width: 170px;
- background: rgba(8, 20, 36, 0.95);
- border: 1px solid rgba(38, 74, 124, 0.8);
- border-radius: 12px;
- padding: 18px;
- z-index: 100;
- box-shadow: 0 0 25px rgba(0, 0, 0, 0.6);
- }
- .legend-title {
- color: #fff;
- font-size: 18px;
- margin-bottom: 18px;
- font-weight: bold;
- }
- .legend-item {
- display: flex;
- align-items: center;
- margin-bottom: 14px;
- cursor: pointer;
- transition: 0.2s;
- }
- .legend-item.is-hidden {
- opacity: 0.2;
- filter: grayscale(1);
- }
- .legend-dot {
- width: 12px;
- height: 12px;
- border-radius: 3px;
- margin-right: 12px;
- }
- .legend-label {
- color: #d0d9e2;
- font-size: 14px;
- }
- ::v-deep .pulse-dot {
- width: 12px;
- height: 12px;
- background: #fff;
- border-radius: 50%;
- box-shadow: 0 0 10px #fff, 0 0 20px rgba(50, 197, 255, 0.5);
- }
- </style>
- <style>
- /* 弹窗样式 */
- .custom-info-card {
- background: rgba(5, 22, 45, 0.98);
- border: 1px solid #1e4d8e;
- border-radius: 6px;
- width: 240px;
- color: #fff;
- overflow: hidden;
- }
- .card-header {
- padding: 12px 15px;
- font-size: 14px;
- font-weight: bold;
- }
- .card-body {
- padding: 15px;
- font-size: 13px;
- }
- .info-row {
- margin-bottom: 8px;
- display: flex;
- }
- .info-row .label {
- color: #8da6c7;
- width: 70px;
- }
- .amap-info-content {
- background: transparent !important;
- border: none !important;
- padding: 0 !important;
- }
- .amap-info-sharp {
- display: none !important;
- }
- </style>
|