165 lines
4.6 KiB
JavaScript
165 lines
4.6 KiB
JavaScript
|
|
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
|
||
|
|
};
|