項目結構:
<!--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'; //,PropTypeinterface 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"><!-- 搜索面板 --><divv-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"><inputv-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="請輸入城市名稱"/><divv-show="citySuggestions.length > 0"class="absolute left-0 right-0 top-full mt-1 bg-white rounded-lg shadow-lg z-50"><divv-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><inputv-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="請輸入起點站"/><divv-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><inputv-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="請輸入終點站"/><divv-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><!-- 顯示面板按鈕 --><buttonv-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>
輸出: