Files
gaoYuJournal/miniprogram/pages/main/journal-map/index.ts
2025-12-10 13:55:29 +08:00

266 lines
6.8 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// pages/main/journal-map/index.ts
import config from "../../../config/index";
import Time from "../../../utils/Time";
import { Journal, JournalPageType } from "../../../types/Journal";
import { MediaAttachExt, MediaAttachType } from "../../../types/Attachment";
interface MapMarker {
id: number;
latitude: number;
longitude: number;
width: number;
height: number;
customCallout: {
anchorY: number;
anchorX: number;
display: string;
};
}
interface JournalMarker {
id: number;
date: string;
location?: string;
lat: number;
lng: number;
idea?: string;
items: Array<{
type: number;
thumbURL: string;
sourceURL: string;
mongoId: string;
}>;
}
interface JournalMapData {
centerLat: number;
centerLng: number;
scale: number;
markers: MapMarker[];
journals: JournalMarker[];
customCalloutMarkerIds: number[];
selectedMarker: JournalMarker | null;
isLoading: boolean;
}
Page({
data: <JournalMapData>{
centerLat: 39.908823,
centerLng: 116.397470,
scale: 13,
markers: [],
journals: [],
customCalloutMarkerIds: [],
selectedMarker: null,
isLoading: true,
},
async onLoad() {
await this.loadJournals();
},
/** 计算合适的缩放级别以显示所有标记点 */
calculateScale(journals: JournalMarker[]): number {
if (journals.length === 0) return 13;
if (journals.length === 1) return 15;
// 计算经纬度的范围
const lats = journals.map(j => j.lat);
const lngs = journals.map(j => j.lng);
const minLat = Math.min(...lats);
const maxLat = Math.max(...lats);
const minLng = Math.min(...lngs);
const maxLng = Math.max(...lngs);
// 计算跨度
const latSpan = maxLat - minLat;
const lngSpan = maxLng - minLng;
const maxSpan = Math.max(latSpan, lngSpan);
// 根据跨度映射到缩放级别scale 范围 3-20
if (maxSpan > 10) return 5;
if (maxSpan > 5) return 7;
if (maxSpan > 2) return 9;
if (maxSpan > 1) return 10;
if (maxSpan > .5) return 11;
if (maxSpan > .2) return 12;
if (maxSpan > .1) return 13;
if (maxSpan > .05) return 14;
if (maxSpan > .02) return 15;
if (maxSpan > .01) return 16;
return 17;
},
/** 加载所有记录 */
async loadJournals() {
this.setData({ isLoading: true });
try {
const list: Journal[] = await new Promise((resolve, reject) => {
wx.request({
url: `${config.url}/journal/list`,
method: "POST",
header: {
Key: wx.getStorageSync("key")
},
data: {
page: 0,
size: 9007199254740992,
type: JournalPageType.PREVIEW
},
success: (resp: any) => {
if (resp.data.code === 20000) {
resolve(resp.data.data.list);
} else {
reject(new Error(resp.data.message || "加载失败"));
}
},
fail: reject
});
}) || [];
// 过滤有位置信息的记录
const journals: JournalMarker[] = list
.filter((journal: any) => journal.lat && journal.lng)
.map((journal: any) => ({
id: journal.id,
date: Time.toPassedDateTime(journal.createdAt),
location: journal.location,
lat: journal.lat,
lng: journal.lng,
idea: journal.idea,
items: journal.items.filter((item: any) => item.attachType === "THUMB")
.map((item: any) => {
const ext = JSON.parse(item.ext);
return {
type: ext.isVideo ? 1 : 0,
thumbURL: `${config.url}/attachment/read/${item.mongoId}`,
mongoId: item.mongoId,
source: journal.items.find((source: any) => source.id === ext.sourceId)
};
})
}));
if (journals.length === 0) {
wx.showToast({
title: "暂无位置记录",
icon: "none"
});
this.setData({ isLoading: false });
return;
}
// 生成地图标记
const markers: MapMarker[] = journals.map((journal) => ({
id: journal.id,
latitude: journal.lat,
longitude: journal.lng,
width: 24,
height: 30,
customCallout: {
anchorY: -2,
anchorX: 0,
display: "ALWAYS"
}
}));
// 所有标记的 ID 列表
const customCalloutMarkerIds = journals.map((j) => j.id);
// 计算中心点(所有标记的平均位置)
const centerLat = journals.reduce((sum, j) => sum + j.lat, 0) / journals.length;
const centerLng = journals.reduce((sum, j) => sum + j.lng, 0) / journals.length;
// 计算合适的缩放级别
const scale = this.calculateScale(journals) - 2;
this.setData({
journals,
markers,
customCalloutMarkerIds,
centerLat,
centerLng,
scale,
isLoading: false
});
} catch (err: any) {
wx.showToast({
title: err.message || "加载失败",
icon: "error"
});
this.setData({ isLoading: false });
}
},
/** 标记点击事件 */
onMarkerTap(e: any) {
const markerId = e.detail.markerId || e.markerId;
this.loadMarkerDetail(markerId);
},
/** 气泡点击事件 */
onCalloutTap(e: any) {
const markerId = e.detail.markerId || e.markerId;
this.loadMarkerDetail(markerId);
},
/** 加载标记详情 */
async loadMarkerDetail(markerId: number) {
wx.showLoading({ title: "加载中...", mask: true });
try {
const journal: any = await new Promise((resolve, reject) => {
wx.request({
url: `${config.url}/journal/${markerId}`,
method: "POST",
header: {
Key: wx.getStorageSync("key")
},
success: (res: any) => {
if (res.data.code === 20000) {
resolve(res.data.data);
} else {
reject(new Error(res.data.message || "加载失败"));
}
},
fail: reject
});
});
const items = journal.items || [];
const thumbItems = items.filter((item: any) => item.attachType === MediaAttachType.THUMB);
const mediaItems = thumbItems.map((thumbItem: any) => {
const ext = JSON.parse(thumbItem.ext) as MediaAttachExt;
return {
type: ext.isVideo ? 1 : 0,
thumbURL: `${config.url}/attachment/read/${thumbItem.mongoId}`,
sourceURL: `${config.url}/attachment/read/${ext.sourceMongoId}`,
mongoId: thumbItem.mongoId,
};
});
this.setData({
selectedMarker: {
id: journal.id,
date: Time.toPassedDateTime(journal.createdAt),
location: journal.location,
lat: journal.lat,
lng: journal.lng,
idea: journal.idea,
items: mediaItems
}
});
wx.hideLoading();
} catch (err: any) {
wx.hideLoading();
wx.showToast({
title: err.message || "加载失败",
icon: "error"
});
}
},
/** 关闭详情 */
closeDetail() {
this.setData({ selectedMarker: null });
},
/** 预览媒体 */
previewMedia(e: WechatMiniprogram.BaseEvent) {
const index = e.currentTarget.dataset.index;
if (!this.data.selectedMarker) return;
const sources = this.data.selectedMarker.items.map((item: any) => ({
url: item.sourceURL,
type: item.type === 0 ? "image" : "video"
}));
wx.previewMedia({
current: index,
sources: sources as WechatMiniprogram.MediaSource[]
});
},
});