vue3: baidusubway using typescript

项目结构:

<!--npm install -D tailwindcss-3d  BaiduSubwayMap.vue npm install -D tailwindcss postcss autoprefixer-->
<template>
    <div class="relative w-full h-screen">
      <!-- 地图容器 -->
      <div id="subway-container" class="w-full h-full"></div>
       
      <!-- 缩放控制 -->
      <div class="fixed bottom-4 right-4 flex flex-col space-y-2 z-10">
        <button @click="zoomIn" class="w-10 h-10 rounded-full bg-white shadow-md flex items-center justify-center hover:bg-gray-100 transition-colors">
          <i class="fa fa-plus text-gray-700"></i>
        </button>
        <button @click="zoomOut" class="w-10 h-10 rounded-full bg-white shadow-md flex items-center justify-center hover:bg-gray-100 transition-colors">
          <i class="fa fa-minus text-gray-700"></i>
        </button>
      </div>
       
      <!-- 地铁图例 -->
      <div id="legend" class="fixed top-4 right-4 max-w-xs bg-white rounded-lg shadow-lg p-4 hidden md:block z-10">
        <h3 class="font-medium text-gray-800 mb-3">地铁线路图例</h3>
        <div id="legendContent" class="space-y-1 text-sm">
          <div v-for="line in subwayLines" :key="line.id" class="flex items-center">
            <span class="subway-line" :style="{ backgroundColor: line.color || '#3b82f6' }"></span>
            <span>{{ line.name }}</span>
          </div>
        </div>
      </div>
    </div>
  </template>
   
  <script lang="ts">
  import { defineComponent, ref, onMounted, onUnmounted, watch} from 'vue';  //,PropType
   
  interface SubwayLine {
    id: string;
    name: string;
    color?: string;
  }
   
  interface RouteStep {
    instruction: string;
    distance?: number;
    duration?: number;
  }
   
  interface RouteResult {
    steps: RouteStep[];
    distance?: number;
    duration?: number;
  }
   
  export default defineComponent({
    name: 'BaiduSubwayMap',
     
    props: {
      currentCity: {
        type: String,
        required: true
      },
      startStation: {
        type: String,
        required: true
      },
      endStation: {
        type: String,
        required: true
      },
      cityData: Object as () => Record<string, { start: string; end: string }>  //vue 3.3
      //Vue 3
      //cityData: {
        //type: Object as PropType<Record<string, { start: string; end: string }>>,
        //required: true
      //}
    },
     
    emits: ['routeFound', 'error', 'mapLoaded'],
     
    setup(props, { emit }) {
      const subway = ref<any>(null);
      const direction = ref<any>(null);
      const subwayLines = ref<SubwayLine[]>([]);
      const isMapLoaded = ref(false);
       
      // 监听城市变化
        watch(() => props.currentCity, async (newCity, oldCity) => {
        if (newCity !== oldCity) {
            console.log(`城市切换: ${oldCity} → ${newCity}`);
            await loadCitySubway(newCity);
        }
        });
 
 
      // 生命周期钩子
      onMounted(() => {
        initMap();
      });
       
      onUnmounted(() => {
        cleanupSubwayInstance();
      });
       
      // 监听城市或站点变化
      watch([() => props.currentCity, () => props.startStation, () => props.endStation], () => {
        if (isMapLoaded.value && props.startStation && props.endStation) {
          searchRoute();
        }
      });
       
      // 初始化地图
      const initMap = () => {
        try {
          // 检查百度地图API是否加载成功
          if (typeof BMapSub === 'undefined') {
            emit('error', '百度地图API加载失败,请检查API密钥是否正确');
            return;
          }
           
          // 加载当前城市的地铁地图
          loadCitySubway(props.currentCity);
        } catch (error) {
          console.error('初始化地图时出错:', error);
          emit('error', '初始化地图时出错,请刷新页面');
        }
      };
       
      // 加载指定城市的地铁地图
      const loadCitySubway = (cityName: string) => {
        // 重置地图容器
        const container = document.getElementById('subway-container');
        if (container) container.innerHTML = '';
         
        // 清理旧的地铁实例
        cleanupSubwayInstance();
         
        try {
          // 查找城市信息
          const city = BMapSub.SubwayCitiesList.find(c => c.name === cityName);
           
          if (!city) {
            emit('error', `未找到${cityName}的地铁数据,请尝试其他城市`);
            return;
          }
          console.log(`加载${cityName}地铁地图,城市代码: ${city.citycode}`);
          // 创建新的地铁实例
          subway.value = new BMapSub.Subway('subway-container', city.citycode);
           
          // 绑定地铁加载完成事件
          subway.value.addEventListener('subwayloaded', () => {
            console.log(`${cityName}地铁地图加载完成`);
            onSubwayLoaded(cityName);
            emit('mapLoaded', true);
          });
           
          // 绑定错误处理
          subway.value.addEventListener('subwayloaderror', onSubwayLoadError);
           
        } catch (e) {
          console.error('创建地铁实例时出错:', e);
          emit('error', `加载${cityName}地铁数据失败,请稍后再试`);
        }
      };
       
      // 地铁加载完成回调
      const onSubwayLoaded = (cityName: string) => {
        try {
          // 初始化路线规划
          direction.value = new BMapSub.Direction(subway.value);
           
          // 设置路线规划完成后的回调
          direction.value.addEventListener('directioncomplete', handleRouteResults);
           
          isMapLoaded.value = true;
          emit('mapLoaded', true);
           
          // 生成线路图例
          generateLineLegend();
           
          // 如果有起点和终点,执行搜索
          if (props.startStation && props.endStation) {
            searchRoute();
          }
           
        } catch (e) {
          console.error('初始化地铁地图时出错:', e);
          emit('error', `初始化${cityName}地铁地图失败,请稍后再试`);
        }
      };
       
      // 地铁加载错误回调
      const onSubwayLoadError = () => {
        emit('error', `加载${props.currentCity}地铁数据失败,请稍后再试`);
        isMapLoaded.value = false;
      };
       
      // 清理旧的地铁实例
      const cleanupSubwayInstance = () => {
        if (subway.value) {
          try {
            subway.value.removeEventListener('subwayloaded', onSubwayLoaded);
            subway.value.removeEventListener('subwayloaderror', onSubwayLoadError);
             
            // 仅在地铁已初始化且有destroy方法时尝试销毁
            if (isMapLoaded.value && typeof subway.value.destroy === 'function') {
              // 移除路线规划器的事件监听器
              if (direction.value) {
                direction.value.removeEventListener('directioncomplete', handleRouteResults);
                direction.value = null;
              }
               
              // 尝试销毁地铁实例
              subway.value.destroy();
            }
          } catch (e) {
            console.error('销毁地铁实例时出错:', e);
          } finally {
            // 无论如何都重置地铁实例和状态
            subway.value = null;
            isMapLoaded.value = false;
          }
        }
      };
       
      // 生成线路图例
      const generateLineLegend = () => {
        try {
          // 获取线路信息
          if (!subway.value) return;
           
          const lines = subway.value.getLines();
           
          if (lines && lines.length > 0) {
            // 只显示前10条线路以避免图例过长
            const displayLines = lines.slice(0, 10);
            subwayLines.value = displayLines.map(line => ({
              id: line.id,
              name: line.name,
              color: line.color
            }));
          }
        } catch (e) {
          console.error('生成线路图例时出错:', e);
        }
      };
       
      // 搜索路线
      const searchRoute = () => {
        if (!isMapLoaded.value || !direction.value) {
          emit('error', '地图加载中,请稍候再试');
          return;
        }
         
        if (!props.startStation || !props.endStation) {
          emit('error', '请输入起点站和终点站');
          return;
        }
         
        // 验证站点是否属于当前城市
        const validStations = getValidStations(props.currentCity);
        if (validStations && !validStations.includes(props.startStation)) {
          emit('error', `起点站“${props.startStation}”不存在于${props.currentCity}地铁系统中`);
          return;
        }
         
        if (validStations && !validStations.includes(props.endStation)) {
          emit('error', `终点站“${props.endStation}”不存在于${props.currentCity}地铁系统中`);
          return;
        }
         
        // 执行路线搜索
        try {
          direction.value.search(props.startStation, props.endStation);
        } catch (e) {
          console.error('搜索路线时出错:', e);
          emit('error', '搜索路线时出错,请重试');
        }
      };
       
      // 处理路线规划结果
      const handleRouteResults = (results: any) => {
        try {
          if (!results || results.length === 0) {
            emit('error', `未找到从${props.startStation}到${props.endStation}的路线,请尝试其他站点`);
            return;
          }
           
          // 选择第一条路线(通常是最优路线)
          const route = results[0];
           
          // 格式化路线结果
          const formattedRoute: RouteResult = {
            steps: route.steps || [],
            distance: route.distance,
            duration: route.duration
          };
           
          // 发送路线结果给父组件
          emit('routeFound', formattedRoute);
           
        } catch (e) {
          console.error('处理路线结果时出错:', e);
          emit('error', '处理路线信息时出错,请重试');
        }
      };
       
      // 地图缩放控制
      const zoomIn = () => {
        if (subway.value) {
          try {
            subway.value.setZoom(subway.value.getZoom() + 1);
          } catch (e) {
            console.error('地图缩放时出错:', e);
          }
        }
      };
       
      const zoomOut = () => {
        if (subway.value) {
          try {
            subway.value.setZoom(subway.value.getZoom() - 1);
          } catch (e) {
            console.error('地图缩放时出错:', e);
          }
        }
      };
       
      // 获取当前城市的有效站点列表
      const getValidStations = (cityName: string): string[] | null => {
        try {
          if (!subway.value) {
            return null;
          }
           
          // 获取所有线路
          const lines = subway.value.getLines();
           
          if (!lines || lines.length === 0) {
            return null;
          }
           
          // 收集所有站点
          const stations = new Set<string>();
           
          lines.forEach(line => {
            if (line.stations && line.stations.length > 0) {
              line.stations.forEach(station => {
                stations.add(station.name);
              });
            }
          });
           
          return Array.from(stations);
        } catch (e) {
          console.error('获取站点列表时出错:', e);
          return null;
        }
      };
       
      return {
        subwayLines,
        zoomIn,
        zoomOut
      };
    }
  });
  </script>
   
  <style type="text/tailwindcss">
  @layer utilities {
    .subway-line {
      display: inline-block;
      width: 12px;
      height: 2px;
      margin: 0 4px;
      vertical-align: middle;
    }
  }
  </style>

<!-- SubWayView.vue -->
<template>
    <div class="font-sans">
      <!-- 搜索面板 -->
      <div
        v-show="panelVisible"
        class="fixed top-4 left-1/2 transform -translate-x-1/2 bg-white rounded-xl shadow-lg p-6 max-w-md w-full z-50 transition-all duration-300"
      >
        <div class="flex justify-between items-center mb-4">
          <h2 class="text-xl font-bold text-gray-900">{{ panelTitle }}</h2>
          <button
            @click="closePanel"
            class="text-gray-500 hover:text-gray-700 focus:outline-none"
          >
            <i class="fa fa-times text-lg"></i>
          </button>
        </div>
   
        <div class="space-y-4">
          <!-- 城市选择 -->
          <div>
            <label class="block text-sm font-medium text-gray-700 mb-1">城市</label>
            <div class="relative">
              <input
                v-model="currentCity"
                @input="handleCityInput"
                @keypress.enter="changeCity"
                class="w-full px-4 py-2 border rounded-lg focus:ring-blue-500 focus:border-blue-500"
                placeholder="请输入城市名称"
              />
              <div
                v-show="citySuggestions.length > 0"
                class="absolute left-0 right-0 top-full mt-1 bg-white rounded-lg shadow-lg z-50"
              >
                <div
                  v-for="suggestion in citySuggestions"
                  :key="suggestion"
                  @click="selectCity(suggestion)"
                  class="px-4 py-2 hover:bg-gray-100 cursor-pointer"
                >
                  {{ suggestion }}
                </div>
              </div>
            </div>
            <div class="flex space-x-2 mt-2">
              <button
                @click="changeCity"
                class="flex-1 bg-blue-600 hover:bg-blue-700 text-white font-medium py-2 px-4 rounded-lg shadow-md"
              >
                切换城市
              </button>
              <button
                @click="resetToDefault"
                class="flex-1 bg-gray-200 hover:bg-gray-300 text-gray-800 font-medium py-2 px-4 rounded-lg"
              >
                <i class="fa fa-refresh mr-1"></i> 重置默认
              </button>
            </div>
          </div>
   
          <!-- 站点输入 -->
          <div>
            <div class="relative">
              <div class="absolute inset-y-0 left-0 flex items-center pl-3 pointer-events-none">
                <i class="fa fa-map-marker text-blue-500"></i>
              </div>
              <input
                v-model="startStation"
                @keypress.enter="searchRoute"
                class="w-full pl-10 pr-4 py-2 border rounded-lg focus:ring-blue-500 focus:border-blue-500"
                placeholder="请输入起点站"
              />
              <div
                v-show="isDefaultStartStation"
                class="absolute right-3 top-1/2 transform -translate-y-1/2 text-xs text-gray-400"
              >
                默认
              </div>
            </div>
            <div class="relative mt-4">
              <div class="absolute inset-y-0 left-0 flex items-center pl-3 pointer-events-none">
                <i class="fa fa-flag text-red-500"></i>
              </div>
              <input
                v-model="endStation"
                @keypress.enter="searchRoute"
                class="w-full pl-10 pr-4 py-2 border rounded-lg focus:ring-blue-500 focus:border-blue-500"
                placeholder="请输入终点站"
              />
              <div
                v-show="isDefaultEndStation"
                class="absolute right-3 top-1/2 transform -translate-y-1/2 text-xs text-gray-400"
              >
                默认
              </div>
            </div>
          </div>
   
          <!-- 查询按钮 -->
          <button
            @click="searchRoute"
            class="w-full bg-blue-600 hover:bg-blue-700 text-white font-bold py-3 px-4 rounded-lg shadow-lg mt-4"
          >
            查询路线
          </button>
   
          <!-- 路线结果 -->
          <div class="mt-4 bg-gray-100 rounded-lg p-4 text-sm">
            <div v-if="loading" class="text-gray-500 animate-pulse">
              <i class="fa fa-spinner fa-spin mr-1"></i> {{ loadingMessage }}
            </div>
            <div v-else-if="errorMessage" class="text-red-500">
              <i class="fa fa-exclamation-circle mr-1"></i> {{ errorMessage }}
            </div>
            <div v-else-if="routeResults">
              <!-- 路线展示逻辑保持不变 -->
              <div class="bg-white rounded-lg shadow-sm p-4 mb-4">
                <div class="flex justify-between items-center mb-3">
                  <h3 class="font-medium">{{ startStation }} → {{ endStation }}</h3>
                  <span class="bg-green-100 text-green-800 text-xs px-2 py-1 rounded-full">
                    <i class="fa fa-clock-o mr-1"></i> 约{{ routeResults.duration || '未知' }}分钟
                  </span>
                </div>
                <!-- 路线步骤展示 -->
              </div>
            </div>
            <div v-else class="text-gray-500">
              请输入起点和终点,点击查询路线
            </div>
          </div>
        </div>
      </div>
   
      <!-- 显示面板按钮 -->
      <button
        v-show="!panelVisible"
        @click="showPanel"
        class="fixed top-4 left-4 bg-white hover:bg-gray-100 text-gray-800 font-medium py-2 px-4 rounded-lg shadow-md z-50"
      >
        <i class="fa fa-search mr-2"></i> 显示搜索面板
      </button>
       
      <!-- 百度地铁地图组件 -->
      <BaiduSubwayMap
        :currentCity="currentCity"
        :startStation="startStation"
        :endStation="endStation"
        :cityData="cityData"
        @routeFound="handleRouteFound"
        @error="handleError"
        @mapLoaded="handleMapLoaded"
      />
    </div>
  </template>
   
  <script lang="ts">
  import { defineComponent, ref, computed, onMounted, watch } from 'vue';
  import BaiduSubwayMap from '../components/BaiduSubwayMap.vue';
   
  interface CityData {
    [city: string]: {
      start: string;
      end: string;
    };
  }
   
  export default defineComponent({
    name: 'SubWayView',
     
    components: {
      BaiduSubwayMap
    },
     
    setup() {
      // 状态管理
      const currentCity = ref('深圳');
      const startStation = ref('');
      const endStation = ref('');
      const panelVisible = ref(true);
      const loading = ref(false);
      const loadingMessage = ref('');
      const errorMessage = ref('');
      const routeResults = ref(null);
      const cityData = ref<CityData>({});
      const citySuggestions = ref<string[]>([]);
      const cityHistory = ref<string[]>([]); // 新增:历史记录数组
      const panelTitle = ref('深圳地铁线路规划');  //
       
      // 计算属性
      const isDefaultStartStation = computed(() => {
        return cityData.value[currentCity.value]?.start === startStation.value;
      });
       
      const isDefaultEndStation = computed(() => {
        return cityData.value[currentCity.value]?.end === endStation.value;
      });
       
      // 生命周期钩子
      onMounted(() => {
        loadCityData();
        loadSavedState();
      });
       
      // 从city.json加载城市数据
      const loadCityData = async () => {
        try {
          console.log('开始加载城市数据...');
          loading.value = true;
          loadingMessage.value = '正在加载城市数据...';
           
          const response = await fetch('city.json');
          cityData.value = await response.json();
          console.log('城市数据加载成功:', cityData.value);
          // 设置当前城市的默认站点
          setDefaultStations();
           
          loading.value = false;
        } catch (error) {
          console.error('加载城市数据失败:', error);
          errorMessage.value = '加载城市数据失败,请稍后再试';
          loading.value = false;
        }
      };
       
      // 加载保存的状态
      const loadSavedState = () => {
        try {
          const savedState = localStorage.getItem('subwayMapState');
           
          if (savedState) {
            const parsedState = JSON.parse(savedState);
             
            // 恢复当前城市
            if (parsedState.currentCity && cityData.value[parsedState.currentCity]) {
              currentCity.value = parsedState.currentCity;
              panelTitle.value = `${currentCity.value}地铁线路规划`;
            }
             
            // 恢复站点
            if (parsedState.startStation) {
              startStation.value = parsedState.startStation;
            }
             
            if (parsedState.endStation) {
              endStation.value = parsedState.endStation;
            }
             
            // 恢复面板可见性
            if (typeof parsedState.panelVisible === 'boolean') {
              panelVisible.value = parsedState.panelVisible;
            }
             
            console.log('从本地存储恢复状态:', parsedState);
          }
        } catch (e) {
          console.error('恢复应用状态失败:', e);
        }
      };
       
      // 保存当前状态到本地存储
      const saveState = () => {
        try {
          const stateToSave = {
            currentCity: currentCity.value,
            startStation: startStation.value,
            endStation: endStation.value,
            panelVisible: panelVisible.value
          };
           
          localStorage.setItem('subwayMapState', JSON.stringify(stateToSave));
        } catch (e) {
          console.error('保存应用状态失败:', e);
        }
      };
       
      // 设置当前城市的默认站点
      const setDefaultStations = () => {
        const defaultStations = cityData.value[currentCity.value];
         
        if (defaultStations) {
          // 只有在站点为空时设置默认值,保留用户修改
          if (!startStation.value) {
            startStation.value = defaultStations.start;
          }
           
          if (!endStation.value) {
            endStation.value = defaultStations.end;
          }
        }   
      };
       
      // 切换城市
      const changeCity = () => {
        console.log(`点击:选择城市...`);

        const cityName1 = currentCity.value.trim();
        console.log(`点击:选择城市${cityName1}`);    

        const defaultStations = cityData.value[currentCity.value];
         
         if (defaultStations) {
           startStation.value = defaultStations.start;
           endStation.value = defaultStations.end;
           panelTitle.value = `${currentCity.value}地铁线路规划`;
           // 保存状态
           saveState();
         }        
        // 清除错误消息
        errorMessage.value = null;
      };
       
      // 处理城市输入
      const handleCityInput = () => {
        const query = currentCity.value.trim().toLowerCase();
         
        if (query.length < 2) {
          citySuggestions.value = [];
          return;
        }
         // 检查输入的城市是否有效(存在于cityData中)
        const isValidCity = cityData.value[query];
        if (isValidCity && !cityHistory.value.includes(query)) {
            // 添加到历史记录(去重)
            cityHistory.value.push(query);
        }
        //
        const allCities = [...new Set([...Object.keys(cityData.value), ...cityHistory.value])];
        const matchedCities = allCities.filter(city => 
            city.toLowerCase().includes(query)
        );

        // 过滤匹配的城市
        //const matchedCities = Object.keys(cityData.value).filter(city =>
          //city.toLowerCase().includes(query)
        //);
         
        // 更新建议列表
        citySuggestions.value = matchedCities;
      };
       
      // 选择城市
      const selectCity = (cityName: string) => {
        currentCity.value = cityName;
        console.log(`换了地图:选择城市${cityName}`);      
        //setDefaultStations(); // 强制设置默认站点

           // itySuggestions.value = [];
            if (!cityHistory.value.includes(cityName)) {
                cityHistory.value.push(cityName);
            }


        //citySuggestions.value = [];
        const defaultStations = cityData.value[currentCity.value];
         
         if (defaultStations) {
           startStation.value = defaultStations.start;
           endStation.value = defaultStations.end;
           panelTitle.value = `${currentCity.value}地铁线路规划`;
           // 保存状态
           saveState();
         }



      };
       
      // 搜索路线
      const searchRoute = () => {
        if (!startStation.value || !endStation.value) {
          errorMessage.value = '请输入起点站和终点站';
          return;
        }
         
        // 保存当前状态
        saveState();
         
        // 清空错误消息
        //errorMessage.value = null;
      };
       
      // 处理路线结果
      const handleRouteFound = (results: any) => {
        routeResults.value = results;
        loading.value = false;
         
        // 保存当前状态
        saveState();
      };
       
      // 处理错误
      const handleError = (message: string) => {
        errorMessage.value = message;
        loading.value = false;
      };
       
      // 处理地图加载完成
      const handleMapLoaded = () => {
        loading.value = false;
      };
       
      // 关闭面板
      const closePanel = () => {
        panelVisible.value = false;
        saveState();
      };
       
      // 显示面板
      const showPanel = () => {
        panelVisible.value = true;
        saveState();
      };
       
      // 重置为默认值
      const resetToDefault = () => {
        const defaultStations = cityData.value[currentCity.value];
         
        if (defaultStations) {
          startStation.value = defaultStations.start;
          endStation.value = defaultStations.end;
          panelTitle.value = `${currentCity.value}地铁线路规划`;
          // 保存状态
          saveState();
        }
      };
       
      // 监听面板可见性变化
      watch(panelVisible, () => {
        saveState();
      });
       
      // 监听站点变化
      watch([startStation, endStation], () => {
        saveState();
      });
       
      return {
        currentCity,
        startStation,
        endStation,
        panelVisible,
        loading,
        loadingMessage,
        errorMessage,
        routeResults,
        cityData,
        citySuggestions,
        panelTitle,
        isDefaultStartStation,
        isDefaultEndStation,
        changeCity,
        handleCityInput,
        selectCity,
        searchRoute,
        closePanel,
        showPanel,
        resetToDefault,
        handleRouteFound, // 确保将方法添加到返回对象中
        handleError,
        handleMapLoaded
      };
    }
  });
  </script>
   
   <style scoped>
  /* 优化字体和间距 */
  @tailwind base;
  @tailwind components;
  @tailwind utilities;
   
  /* 修复搜索面板层级问题 */
  .z-50 {
    z-index: 50;
  }
  </style>

输出:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值