diff --git a/station-navigation/app.js b/station-navigation/app.js
index 5289672b..c0d9aacb 100644
--- a/station-navigation/app.js
+++ b/station-navigation/app.js
@@ -1,4 +1,4 @@
-const state = { stations: [], query: '', searchText: '', locationFilters: [], suggestions: [], activeSuggestion: -1, searchDebounce: null, stationFilter: 'all', selected: null, userLocation: null, map: null, markers: [], userMarker: null, pitch: true };
+const state = { stations: [], query: '', searchText: '', locationFilters: [], suggestions: [], activeSuggestion: -1, searchDebounce: null, stationFilter: 'all', selected: null, userLocation: null, map: null, markers: [], userMarker: null, pitch: true, regionPicker: null };
document.addEventListener('DOMContentLoaded', () => { initClock(); initMap(); loadStations(); });
document.addEventListener('pointerdown', event => { if (!event.target?.closest?.('.map-explore-toolbar')) closeStationSearchSuggestions(); });
@@ -45,7 +45,7 @@ function stationMatchesLocation(station) { return !state.locationFilters.length
function stationTypeScope() { return state.stations.filter(station => state.stationFilter !== 'partner' || station.cooperative); }
function filteredStations() { return stationTypeScope().filter(station => stationMatchesLocation(station) && stationMatchesQuery(station)); }
function stationSearchScore(station, query) {
- const fields = [station.name, station.shortName, station.province, station.city, station.district, station.address].filter(Boolean);
+ const fields = [station.name, station.shortName, station.address].filter(Boolean);
for (const value of fields) { const text = normalize(value); if (text.includes(query)) return 3; const pinyin = toPinyin(value); if (pinyin.includes(query) || initials(pinyin).includes(query)) return 2; }
return 0;
}
@@ -123,7 +123,8 @@ function stationLocationOptions() {
const add = (level, station) => {
const province = station.province || '', city = station.city || '', district = station.district || '';
const key = level === 'province' ? `province|${province}` : level === 'city' ? `city|${province}|${city}` : `district|${province}|${city}|${district}`;
- if (!groups.has(key)) groups.set(key, { type: 'location', level, province, city, district, stations: [] });
+ const filter = level === 'province' ? { province, city: '', district: '' } : level === 'city' ? { province, city, district: '' } : { province, city, district };
+ if (!groups.has(key)) groups.set(key, { type: 'location', level, ...filter, stations: [] });
groups.get(key).stations.push(station);
};
for (const station of stationTypeScope()) { const district = stationDistrictName(station); if (station.province) add('province', station); if (station.province && station.city) add('city', station); if (station.province && station.city && district) add('district', { ...station, district }); }
@@ -145,13 +146,9 @@ function stationEntitySuggestions(query) {
}
function renderStationSearchSuggestions() {
const box = document.getElementById('stationSearchSuggestions'); if (!box || box.hidden) return;
- const locations = stationLocationSuggestions(state.searchText), stations = stationEntitySuggestions(state.searchText);
- state.suggestions = [...locations, ...stations]; state.activeSuggestion = Math.min(state.activeSuggestion, state.suggestions.length - 1);
- if (!state.suggestions.length) { box.innerHTML = '未找到匹配的位置或站点'; return; }
- let html = '';
- if (locations.length) html += `位置${locations.map((item, index) => ``).join('')}`;
- if (stations.length) html += `加氢站${stations.map((item, offset) => { const index = locations.length + offset, station = item.station; return ``; }).join('')}`;
- box.innerHTML = html;
+ state.suggestions = stationEntitySuggestions(state.searchText); state.activeSuggestion = Math.min(state.activeSuggestion, state.suggestions.length - 1);
+ if (!state.suggestions.length) { box.innerHTML = '未找到匹配的站点或详细地址'; return; }
+ box.innerHTML = `加氢站${state.suggestions.map((item, index) => { const station = item.station; return ``; }).join('')}`;
}
function openStationSearchSuggestions() { const box = document.getElementById('stationSearchSuggestions'); if (!box) return; box.hidden = false; renderStationSearchSuggestions(); }
function closeStationSearchSuggestions() { const box = document.getElementById('stationSearchSuggestions'); if (box) box.hidden = true; state.activeSuggestion = -1; }
@@ -160,6 +157,7 @@ function renderLocationFilterChips() {
const rail = document.getElementById('selectedLocationFilters'); if (!rail) return;
rail.hidden = !state.locationFilters.length;
rail.innerHTML = state.locationFilters.map(filter => { const key = locationFilterKey(filter), label = locationFilterLabel(filter); return ``; }).join('');
+ const count = document.getElementById('regionPickerTriggerCount'); if (count) { count.hidden = !state.locationFilters.length; count.textContent = state.locationFilters.length; }
}
function removeLocationFilter(key) {
state.locationFilters = state.locationFilters.filter(filter => locationFilterKey(filter) !== key);
@@ -171,6 +169,55 @@ function addLocationFilter(option) {
if (!state.locationFilters.some(current => locationFilterKey(current) === locationFilterKey(filter))) state.locationFilters.push(filter);
renderLocationFilterChips();
}
+function cloneLocationFilter(filter) { return { level: filter.level, province: filter.province || '', city: filter.city || '', district: filter.district || '', label: filter.label || '' }; }
+function openRegionPicker() {
+ state.regionPicker = { level: 'province', province: '', city: '', search: '', draft: state.locationFilters.map(cloneLocationFilter), options: [] };
+ const modal = document.getElementById('regionPickerModal'); if (modal) modal.hidden = false;
+ renderRegionPicker(); window.setTimeout(() => document.getElementById('regionPickerSearch')?.focus(), 0);
+}
+function closeRegionPicker() { state.regionPicker = null; const modal = document.getElementById('regionPickerModal'); if (modal) modal.hidden = true; }
+function regionPickerOptions() {
+ const picker = state.regionPicker; if (!picker) return [];
+ const all = stationLocationOptions(); const needle = normalize(picker.search);
+ if (needle) return all.map(option => ({ ...option, score: Math.min(fuzzySearchScore(option.label, needle), fuzzySearchScore(option.path, needle)) })).filter(option => Number.isFinite(option.score)).sort((left, right) => left.score - right.score || left.label.localeCompare(right.label, 'zh-CN')).slice(0, 80);
+ if (picker.level === 'province') return all.filter(option => option.level === 'province');
+ if (picker.level === 'city') return all.filter(option => option.level === 'city' && option.province === picker.province);
+ return all.filter(option => option.level === 'district' && option.province === picker.province && option.city === picker.city);
+}
+function regionPickerOptionPath(option) {
+ const parts = option.level === 'province' ? [option.province] : option.level === 'city' ? [option.province, option.city] : [option.province, option.city, option.district];
+ return `${compactPath(parts)} · ${formatNumber(option.stations.length)} 座站点`;
+}
+function regionPickerOptionSelected(option) { return state.regionPicker?.draft.some(filter => locationFilterKey(filter) === locationFilterKey(option)); }
+function renderRegionPicker() {
+ const picker = state.regionPicker; if (!picker) return;
+ const search = document.getElementById('regionPickerSearch'); if (search && search.value !== picker.search) search.value = picker.search;
+ const clear = document.getElementById('regionPickerSearchClear'); if (clear) clear.hidden = !picker.search;
+ const selected = document.getElementById('regionPickerSelected');
+ if (selected) selected.innerHTML = picker.draft.length ? `已选${picker.draft.map((filter, index) => ``).join('')}` : '可同时选择多个省 / 市 / 区县';
+ const path = document.getElementById('regionPickerPath');
+ if (path) {
+ const crumbs = [{ label: '全部省份', level: 'province' }];
+ if (picker.province) crumbs.push({ label: stationGroupName({ province: picker.province }, 'province'), level: 'city' });
+ if (picker.city) crumbs.push({ label: stationGroupName({ city: picker.city }, 'city'), level: 'district' });
+ path.innerHTML = picker.search ? '搜索结果' : crumbs.map((crumb, index) => ``).join('›');
+ }
+ const options = regionPickerOptions(); picker.options = options;
+ const list = document.getElementById('regionPickerList');
+ if (list) list.innerHTML = options.length ? options.map((option, index) => {
+ const selectedOption = regionPickerOptionSelected(option), canDescend = !picker.search && option.level !== 'district';
+ return `