| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378 |
- <template>
- <div class="map-wrapper">
- <div ref="mapContainer" class="map-container"></div>
- <div class="map-header" v-if="initialized">
- <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">
- <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,
- infoWindow: null,
- initialized: false,
- searchQuery: '', // 搜索内容绑定
- activeLegends: [],
- legendConfig: [
- { 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: {}
- };
- },
- mounted() {
- this.initAMap();
- },
- beforeDestroy() {
- if (this.map) {
- this.map.destroy();
- }
- },
- methods: {
- async initAMap() {
- window._AMapSecurityConfig = { securityJsCode: this.securityJsCode };
- try {
- const AMap = await AMapLoader.load({
- key: this.amapKey,
- version: "2.0",
- plugins: ['AMap.InfoWindow', 'AMap.Polyline', 'AMap.Marker']
- });
- this.AMap = AMap;
- this.map = new AMap.Map(this.$refs.mapContainer, {
- zoom: 13,
- center: [116.6612, 39.9125],
- mapStyle: "amap://styles/darkblue",
- viewMode: "3D"
- });
- this.infoWindow = new AMap.InfoWindow({
- isCustom: true,
- offset: new AMap.Pixel(0, -15)
- });
- this.map.on("complete", () => {
- this.initialized = true;
- this.activeLegends = this.legendConfig.map(l => l.type);
- this.drawTrafficScene();
- });
- this.map.on('click', () => this.infoWindow && this.infoWindow.close());
- } catch (e) {
- console.error("地图加载失败:", e);
- }
- },
- // 搜索查询逻辑
- 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) {
- 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: 20px;
- left: 20px;
- right: 20px;
- 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: 25px;
- 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>
|