const DEFAULT_THUMBNAILS = { image: '/images/placeholder.svg', video: 'https://res.wx.qq.com/a/wx_fed/weui-design/assets/component/picker/image-default.png', mixed: '/images/placeholder.svg' }; const MAP_FEED_MARKER_STYLE = { single: { background: '#f02d05', color: '#FFFFFF', icon: 'https://res.wx.qq.com/a/wx_fed/weui-design/assets/icon_nav/icon_nav_location_blue.png' }, cluster: { background: '#BB10BB', color: '#FFFFFF', icon: 'https://res.wx.qq.com/a/wx_fed/weui-design/assets/icon_nav/icon_nav_discover.png' }, administrative: { background: '#f0500a', color: '#FFFFFF', icon: 'https://res.wx.qq.com/a/wx_fed/weui-design/assets/icon_nav/icon_nav_map.png' }, stranger: { background: '#f0500a', color: '#FFFFFF', icon: 'https://res.wx.qq.com/a/wx_fed/weui-design/assets/icon_nav/icon_nav_map.png' }, friend: { background: '#44a186', color: '#FFFFFF', icon: 'https://res.wx.qq.com/a/wx_fed/weui-design/assets/icon_nav/icon_nav_map.png' } }; const MAP_FEED_SCALE_BUCKETS = [ { mode: 'nearby', level: 'street', minScale: 11 }, { mode: 'administrative', level: 'district', minScale: 9 }, { mode: 'administrative', level: 'city', minScale: 7 }, { mode: 'administrative', level: 'province', minScale: 5 }, { mode: 'administrative', level: 'country', minScale: 0 } ]; const MAP_FEED_DEBOUNCE = 400; const MAP_FEED_CACHE_TTL = 3 * 60 * 1000; const MAP_FEED_BUBBLE_LIMIT = 5; function getFeedScalePreset(scale) { if (typeof scale !== 'number') { return MAP_FEED_SCALE_BUCKETS[0]; } return MAP_FEED_SCALE_BUCKETS.find((bucket) => scale >= bucket.minScale) || MAP_FEED_SCALE_BUCKETS[MAP_FEED_SCALE_BUCKETS.length - 1]; } function pickCorner(bounds = {}, key) { return bounds[key] || bounds[key?.toLowerCase?.()] || bounds[key?.replace?.(/([A-Z])/g, '_$1').toLowerCase?.()] || null; } function normalizeCorner(corner) { if (!corner) { return null; } if (corner.latitude !== undefined && corner.longitude !== undefined) { return { latitude: Number(corner.latitude), longitude: Number(corner.longitude) }; } if (corner.lat !== undefined && corner.lng !== undefined) { return { latitude: Number(corner.lat), longitude: Number(corner.lng) }; } return null; } function buildBoundsPayload(bounds) { if (!bounds) { return null; } const northEastRaw = pickCorner(bounds, 'northEast') || pickCorner(bounds, 'northeast'); const southWestRaw = pickCorner(bounds, 'southWest') || pickCorner(bounds, 'southwest'); const northEast = normalizeCorner(northEastRaw); const southWest = normalizeCorner(southWestRaw); if (!northEast || !southWest) { return null; } if (Number.isNaN(northEast.latitude) || Number.isNaN(northEast.longitude) || Number.isNaN(southWest.latitude) || Number.isNaN(southWest.longitude)) { return null; } return { bounds: { northEast, southWest } }; } function resolveFeedThumbnail(point = {}) { let candidate = point.thumbnail || point.cover || point.preview || (Array.isArray(point.media) && point.media.length > 0 ? point.media[0]?.url : null); if (candidate && typeof candidate === 'string') { if (candidate.startsWith('/')) { return candidate; } const lower = candidate.toLowerCase(); if (lower.includes('example.com') || lower.startsWith('http://example.com') || lower.includes('placeholder')) { return DEFAULT_THUMBNAILS.mixed; } return candidate; } const typeKey = point.contentType || point.type || 'mixed'; return DEFAULT_THUMBNAILS[typeKey] || DEFAULT_THUMBNAILS.mixed; } function buildCacheKey({ mode, level }, payload = {}) { const boundsKey = payload?.bounds ? [ payload.bounds.northEast?.latitude?.toFixed(4), payload.bounds.northEast?.longitude?.toFixed(4), payload.bounds.southWest?.latitude?.toFixed(4), payload.bounds.southWest?.longitude?.toFixed(4) ].join(',') : 'unknown'; const filterParts = [ payload?.timeFilter?.enabled ? payload.timeFilter.range : 'all', Array.isArray(payload?.contentTypes) ? payload.contentTypes.sort().join('|') : 'all', payload?.level || 'none' ]; return `${mode}:${level}:${boundsKey}:${filterParts.join(':')}`; } function isCacheFresh(entry, ttl = MAP_FEED_CACHE_TTL) { if (!entry || !entry.timestamp) { return false; } return Date.now() - entry.timestamp < ttl; } module.exports = { DEFAULT_THUMBNAILS, MAP_FEED_SCALE_BUCKETS, MAP_FEED_DEBOUNCE, MAP_FEED_CACHE_TTL, MAP_FEED_BUBBLE_LIMIT, MAP_FEED_MARKER_STYLE, getFeedScalePreset, buildBoundsPayload, resolveFeedThumbnail, buildCacheKey, isCacheFresh };