功能:完善车辆地图合作站信息与缓存刷新

This commit is contained in:
lingniu
2026-09-02 14:05:49 +08:00
parent c6875fbb98
commit e7ee8583aa
5 changed files with 262 additions and 20 deletions
+29 -5
View File
@@ -22,7 +22,7 @@ const i18n = {
filterResult: '筛选', searchVehicle: '搜索车牌 / VIN', searchStation: '搜索站点名称 / 地址', nearbyVehicles: '附近车辆', nearbyStations: '附近加氢站',
detailVehicle: '车辆', detailStation: '加氢站', detailPlate: '车牌', detailVin: 'VIN', detailStatus: '车辆状态', detailActive: '今日上线',
detailSpeed: '当前速度', detailDailyMileage: '今日里程', detailTotalMileage: '累计里程', detailLocation: '实时位置', detailCoordinate: '坐标',
detailStationType: '站点类型', detailAdmin: '行政区域', detailAddress: '详细地址', detailMonthlyHydrogen: '本月加氢量', detailTotalHydrogen: '累计加氢量', detailNavigate: '导航',
detailStationType: '站点类型', detailAdmin: '行政区域', detailAddress: '详细地址', detailContactPerson: '联系人', detailContactPhone: '联系方式', detailUnitPrice: '单价', detailMonthlyHydrogen: '本月加氢量', detailTotalHydrogen: '累计加氢量', detailNavigate: '导航',
navigationLaunchingTitle: '正在打开高德地图', navigationLaunchingDescription: '正在准备导航路线,请稍候',
valueYes: '是', valueNo: '否', valueUnavailable: '暂无数据', locatePermissionHint: '无法获取位置,请在浏览器中允许位置权限后重试',
locateUnavailableHint: '暂时无法获取设备位置,请稍后重试', locateTimeoutHint: '定位超时,请到开阔区域后重试',
@@ -50,7 +50,7 @@ const i18n = {
filterResult: 'Filtered', searchVehicle: 'Search plate / VIN', searchStation: 'Search station / address', nearbyVehicles: 'Nearby vehicles', nearbyStations: 'Nearby H₂ stations',
detailVehicle: 'Vehicle', detailStation: 'H₂ station', detailPlate: 'Plate', detailVin: 'VIN', detailStatus: 'Status', detailActive: 'Active today',
detailSpeed: 'Speed', detailDailyMileage: 'Today mileage', detailTotalMileage: 'Total mileage', detailLocation: 'Live location', detailCoordinate: 'Coordinates',
detailStationType: 'Station type', detailAdmin: 'Region', detailAddress: 'Address', detailMonthlyHydrogen: 'Monthly hydrogen', detailTotalHydrogen: 'Total hydrogen', detailNavigate: 'Navigate',
detailStationType: 'Station type', detailAdmin: 'Region', detailAddress: 'Address', detailContactPerson: 'Contact', detailContactPhone: 'Phone', detailUnitPrice: 'Unit price', detailMonthlyHydrogen: 'Monthly hydrogen', detailTotalHydrogen: 'Total hydrogen', detailNavigate: 'Navigate',
navigationLaunchingTitle: 'Opening AMap', navigationLaunchingDescription: 'Preparing your route. Just a moment.',
valueYes: 'Yes', valueNo: 'No', valueUnavailable: 'Unavailable', locatePermissionHint: 'Location access is blocked. Allow it in your browser and try again.',
locateUnavailableHint: 'Your location is temporarily unavailable. Please try again.', locateTimeoutHint: 'Location timed out. Move to an open area and try again.',
@@ -1247,6 +1247,17 @@ function detailField(label, value, wide = false, extra = false) {
return `<div class="detail-field${wide ? ' is-wide' : ''}${extra ? ' is-extra' : ''}"><dt>${escapeHTML(label)}</dt><dd>${escapeHTML(value)}</dd></div>`;
}
function detailPhoneField(label, phone, wide = false) {
const value = String(phone || '').trim(), href = value.replace(/[^\d+]/g, '');
if (!href) return detailField(label, i18n[currentLang].valueUnavailable, wide);
return `<div class="detail-field${wide ? ' is-wide' : ''}"><dt>${escapeHTML(label)}</dt><dd><a class="detail-phone-link" href="tel:${escapeHTML(href)}">${escapeHTML(value)}</a></dd></div>`;
}
function stationUnitPrice(station, fallback = i18n[currentLang].valueUnavailable) {
const value = Number(station?.unitPrice);
return Number.isFinite(value) && value > 0 ? `¥${formatNumber(value, 2)} / kg` : fallback;
}
function vehicleStatusLabel(vehicle) {
const status = vehicleActiveToday(vehicle)
? Number(vehicle?.speedKmh || 0) > 3 ? i18n[currentLang].statusRunning : i18n[currentLang].statusStopped
@@ -1307,8 +1318,15 @@ function navigateToSelectedEntity() {
}
button?.classList.add('is-launching');
if (button) button.disabled = true;
// Paint the acknowledgement before Safari hands control to the AMap URL scheme.
navigationLaunchTimer = window.setTimeout(() => window.location.assign(url), 140);
// Open synchronously to avoid Safari's popup blocker, then let the launch
// animation acknowledge the action while the current map remains intact.
const target = window.open('', '_blank');
navigationLaunchTimer = window.setTimeout(() => {
if (target) {
target.opener = null;
target.location.href = url;
} else window.open(url, '_blank', 'noopener');
}, 140);
window.setTimeout(resetNavigationLaunch, 4000);
}
@@ -1337,6 +1355,11 @@ function showEntityDetails(mode, entity) {
const publicFields = [
detailField(dict.detailStationType, entity.cooperative ? dict.stationCooperative : dict.stationExternal),
detailField(dict.detailAdmin, detailValue(admin)),
...(entity.cooperative ? [
detailField(dict.detailUnitPrice, stationUnitPrice(entity)),
detailField(dict.detailContactPerson, detailValue(entity.contactPerson)),
detailPhoneField(dict.detailContactPhone, entity.contactPhone),
] : []),
detailField(dict.detailAddress, detailValue(entity.address), true),
detailField(dict.detailCoordinate, `${formatNumber(entity.longitude, 6)}, ${formatNumber(entity.latitude, 6)}`, true, true)
];
@@ -1483,6 +1506,7 @@ function renderRankingList(type) {
name: node.name,
value: userLocation && level === 'station' ? `${formatNumber(distanceKm(userLocation, node.lnglat), 1)} km`
: byHydrogen ? `${formatNumber(node.hydrogenKg, 1)} kg` : countLabel(node.count, 'station'),
price: level === 'station' && node.station?.cooperative ? stationUnitPrice(node.station, '') : '',
location: node.lnglat,
zoom: level === 'province' ? 7.2 : level === 'city' ? 11.2 : 14,
node, entity: node.kind === 'station' ? node.station : null, mode: 'station'
@@ -1496,7 +1520,7 @@ function renderRankingList(type) {
rows.forEach((item, index) => {
const row = document.createElement('div');
row.className = `rank-glass-row${item.entity && selectedEntity?.key === entityKey(item.mode, item.entity) ? ' is-selected' : ''}`;
row.innerHTML = `<span class="r-badge ${index < 3 ? `r-top${index + 1}` : ''}">${index + 1}</span><span class="r-name">${escapeHTML(item.name)}</span><span class="r-val">${escapeHTML(item.value)}</span>`;
row.innerHTML = `<span class="r-badge ${index < 3 ? `r-top${index + 1}` : ''}">${index + 1}</span><span class="r-name">${escapeHTML(item.name)}</span><span class="r-value-stack">${item.price ? `<span class="station-list-price">${escapeHTML(item.price)}</span>` : ''}<span class="r-val">${escapeHTML(item.value)}</span></span>`;
row.onclick = () => {
if (item.location) map?.setZoomAndCenter(item.zoom || 9, item.location);
if (item.entity) showEntityDetails(item.mode, item.entity);