diff --git a/miniprogram/components/journal-detail-panel/index.ts b/miniprogram/components/journal-detail-panel/index.ts deleted file mode 100644 index ace0bd8..0000000 --- a/miniprogram/components/journal-detail-panel/index.ts +++ /dev/null @@ -1,109 +0,0 @@ -// components/journal-detail-panel/index.ts - -interface JournalInfo { - id: number; - date: string; - time: string; - lat?: number; - lng?: number; - location?: string; - idea?: string; - items: Array<{ - type: number; - thumbURL: string; - sourceURL: string; - mongoId: string; - }>; -} - -interface JournalDetailPanelData { - currentJournalIndex: number; -} - -Component({ - properties: { - visible: { - type: Boolean, - value: false - }, - title: { - type: String, - value: "" - }, - journals: { - type: Array, - value: [] - }, - mode: { - type: String, - value: "DATE" - } - }, - data: { - currentJournalIndex: 0, - }, - - observers: { - 'journals, visible'(journals: JournalInfo[], visible: boolean) { - if (visible && journals && journals.length > 0) { - // 显示时重置索引和 margin - this.setData({ - currentJournalIndex: 0, - }); - } - } - }, - - methods: { - /** 关闭详情 */ - closeDetail() { - this.triggerEvent("close"); - }, - /** swiper 切换事件 */ - onSwiperChange(e: WechatMiniprogram.SwiperChange) { - this.setData({ - currentJournalIndex: e.detail.current - }); - }, - openLocation(e: WechatMiniprogram.BaseEvent) { - const { journalIndex } = e.currentTarget.dataset; - if (!journalIndex && this.properties.mode !== "LOCATION") { - return; - } - const journals = this.properties.journals as JournalInfo[]; - const journal = journals[journalIndex || 0]; - if (journal.lat && journal.lng) { - wx.openLocation({ - latitude: journal.lat, - longitude: journal.lng, - }); - } - }, - - /** 预览媒体 */ - previewMedia(e: WechatMiniprogram.BaseEvent) { - const journals = this.properties.journals as JournalInfo[]; - if (!journals || journals.length === 0) { - return; - } - const { itemIndex } = e.currentTarget.dataset; - const items = journals[this.data.currentJournalIndex].items; - const total = items.length; - - const startIndex = Math.max(0, itemIndex - 25); - const endIndex = Math.min(total, startIndex + 50); - const newCurrentIndex = itemIndex - startIndex; - - const sources = items.slice(startIndex, endIndex).map((item) => { - return { - url: item.sourceURL, - type: item.type === 0 ? "image" : "video" - } - }) as any; - wx.previewMedia({ - current: newCurrentIndex, - sources - }) - } - } -}); diff --git a/miniprogram/components/journal-detail-panel/index.json b/miniprogram/components/journal-detail-popup/index.json similarity index 100% rename from miniprogram/components/journal-detail-panel/index.json rename to miniprogram/components/journal-detail-popup/index.json diff --git a/miniprogram/components/journal-detail-panel/index.less b/miniprogram/components/journal-detail-popup/index.less similarity index 85% rename from miniprogram/components/journal-detail-panel/index.less rename to miniprogram/components/journal-detail-popup/index.less index 34c2918..b1b9ebe 100644 --- a/miniprogram/components/journal-detail-panel/index.less +++ b/miniprogram/components/journal-detail-popup/index.less @@ -12,12 +12,12 @@ overflow: hidden; flex-direction: column; - .header { + > .header { display: flex; padding: 32rpx 32rpx 0 32rpx; flex-shrink: 0; - margin-bottom: 24rpx; align-items: flex-start; + margin-bottom: 24rpx; justify-content: space-between; .info { @@ -30,13 +30,19 @@ font-size: 32rpx; font-weight: 600; align-items: center; - margin-bottom: 8rpx; .icon { color: var(--theme-wx); font-size: 48rpx; margin-right: 8rpx; } + + .text { + width: calc(100% - 90rpx); + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; + } } } @@ -50,9 +56,9 @@ color: var(--theme-wx); padding: 4rpx 12rpx; font-size: 24rpx; - font-weight: 600; - border-radius: 12rpx; + font-weight: bold; background: var(--theme-bg-journal); + border-radius: 12rpx; } } } @@ -69,14 +75,24 @@ display: flex; flex-direction: column; - .journal-header { + .header { gap: 16rpx; display: flex; padding: 0 32rpx; - flex-wrap: wrap; flex-shrink: 0; + align-items: center; margin-bottom: 16rpx; - align-items: baseline; + + .portfolio { + color: #FFF; + width: 52rpx; + padding: 4rpx 8rpx; + font-size: 24rpx; + background: var(--theme-wx); + text-align: center; + font-weight: bold; + border-radius: 8rpx; + } .location { gap: 8rpx; @@ -95,14 +111,9 @@ } } - .date { - flex: 1; + .datetime { font-size: 28rpx; - font-weight: 600; - } - - .time { - font-size: 24rpx; + font-weight: bold; } } diff --git a/miniprogram/components/journal-detail-popup/index.ts b/miniprogram/components/journal-detail-popup/index.ts new file mode 100644 index 0000000..3c9b261 --- /dev/null +++ b/miniprogram/components/journal-detail-popup/index.ts @@ -0,0 +1,146 @@ +// components/journal-detail-panel/index.ts +import { Journal } from "../../types/Journal"; +import config from "../../config/index"; +import { MediaAttachExt, MediaAttachType } from "../../types/Attachment"; +import { MediaItem, MediaItemType } from "../../types/UI"; +import Time from "../../utils/Time"; + +interface JournalDetailPanelData { + journals: Journal[]; + currentJournalIndex: number; +} + +Component({ + properties: { + visible: { + type: Boolean, + value: false + }, + ids: { + type: Array, + value: [] + }, + mode: { + type: String, + value: "DATE" + } + }, + data: { + journals: [], + currentJournalIndex: 0, + }, + observers: { + async 'ids, visible'(ids: number[], visible: boolean) { + if (visible && ids && 0 < ids.length) { + wx.showLoading({ title: "加载中...", mask: true }); + try { + const journals: Journal[] = await new Promise((resolve, reject) => { + wx.request({ + url: `${config.url}/journal/list/ids`, + method: "POST", + header: { + Key: wx.getStorageSync("key") + }, + data: ids, + success: (resp: any) => { + if (resp.data.code === 20000) { + resolve(resp.data.data); + } else { + reject(new Error(resp.data.message || "加载失败")); + } + }, + fail: reject + }); + }) || []; + journals.forEach(journal => { + journal.date = Time.toPassedDate(journal.createdAt); + journal.time = Time.toTime(journal.createdAt); + journal.datetime = Time.toPassedDateTime(journal.createdAt); + + const thumbItems = journal.items?.filter((item) => item.attachType === MediaAttachType.THUMB); + if (!thumbItems) { + return; + } + const mediaItems: MediaItem[] = thumbItems.map((thumbItem) => { + const ext = thumbItem.ext = JSON.parse(thumbItem.ext!.toString()) as MediaAttachExt; + const thumbURL = `${config.url}/attachment/read/${thumbItem.mongoId}`; + const sourceURL = `${config.url}/attachment/read/${ext.sourceMongoId}`; + return { + type: ext.isVideo ? MediaItemType.VIDEO : MediaItemType.IMAGE, + thumbURL, + sourceURL, + size: thumbItem.size || 0, + attachmentId: thumbItem.id + } as MediaItem; + }); + journal.mediaItems = mediaItems; + }) + this.setData({ + journals, + currentJournalIndex: 0, + }); + wx.hideLoading(); + } catch (err: any) { + wx.hideLoading(); + wx.showToast({ + title: err.message || "加载失败", + icon: "error" + }); + } + } + } + }, + + methods: { + /** 关闭详情 */ + closeDetail() { + this.triggerEvent("close"); + }, + /** swiper 切换事件 */ + onSwiperChange(e: WechatMiniprogram.SwiperChange) { + this.setData({ + currentJournalIndex: e.detail.current + }); + }, + /** 打开位置 */ + openLocation(e: WechatMiniprogram.BaseEvent) { + const { journalIndex } = e.currentTarget.dataset; + if (!journalIndex && this.properties.mode !== "LOCATION") { + return; + } + const journals = this.properties.journals as Journal[]; + const journal = journals[journalIndex || 0]; + if (journal.lat && journal.lng) { + wx.openLocation({ + latitude: journal.lat, + longitude: journal.lng, + }); + } + }, + /** 预览媒体 */ + previewMedia(e: WechatMiniprogram.BaseEvent) { + const journals = this.properties.journals as Journal[]; + if (!journals || journals.length === 0) { + return; + } + const { itemIndex } = e.currentTarget.dataset; + const items = journals[this.data.currentJournalIndex].mediaItems!; + const total = items.length; + + const startIndex = Math.max(0, itemIndex - 25); + const endIndex = Math.min(total, startIndex + 50); + const newCurrentIndex = itemIndex - startIndex; + + const sources = items.slice(startIndex, endIndex).map((item) => { + return { + url: item.sourceURL, + type: item.type === 0 ? "image" : "video" + } + }) as any; + wx.previewMedia({ + current: newCurrentIndex, + sources + }) + } + } +}); diff --git a/miniprogram/components/journal-detail-panel/index.wxml b/miniprogram/components/journal-detail-popup/index.wxml similarity index 71% rename from miniprogram/components/journal-detail-panel/index.wxml rename to miniprogram/components/journal-detail-popup/index.wxml index cc5f317..8499210 100644 --- a/miniprogram/components/journal-detail-panel/index.wxml +++ b/miniprogram/components/journal-detail-popup/index.wxml @@ -9,10 +9,11 @@ - + - {{title}} + {{journals[currentJournalIndex].location}} + {{journals[currentJournalIndex].datetime}} @@ -25,7 +26,8 @@ - + + 专拍 {{item.location}} - {{item.date}} + {{item.datetime}} {{item.idea}} - + - + ; -} - -interface SelectedDateInfo { - displayDate: string; - journals: JournalInfo[]; -} +import Time from "../../../utils/Time"; interface JournalDateData { - journalMap: Record; // 存储每个日期的日记 id 列表 - selectedDate: SelectedDateInfo | null; + // 存储每个日期的日记 id 列表 isLoading: boolean; - popupVisible: boolean; // popup 显示状态 + journalMap: Record; + + popupIds: number[]; + popupVisible: boolean; } Page({ data: { - journalMap: {}, - selectedDate: null, isLoading: true, - popupVisible: false - }, + journalMap: {}, + popupIds: [], + popupVisible: false, + }, async onLoad() { await this.loadJournals(); }, - /** 加载所有日记 */ async loadJournals() { this.setData({ isLoading: true }); @@ -70,24 +49,19 @@ Page({ fail: reject }); }) || []; - // 按日期分组,只存储 id const journalMap: Record = {}; list.forEach((journal: any) => { - const date = new Date(journal.createdAt); - const dateKey = `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}-${String(date.getDate()).padStart(2, '0')}`; - + const dateKey = Time.toDate(journal.createdAt); if (!journalMap[dateKey]) { journalMap[dateKey] = []; } journalMap[dateKey].push(journal.id); }); - // 按 id 倒序排序每天的日记 Object.keys(journalMap).forEach(dateKey => { journalMap[dateKey].sort((a, b) => b - a); }); - this.setData({ journalMap, isLoading: false @@ -100,12 +74,10 @@ Page({ this.setData({ isLoading: false }); } }, - /** 日期选择事件(来自 calendar 组件) */ onDateSelect(e: WechatMiniprogram.CustomEvent) { - const { date, year, month, day } = e.detail; + const { date } = e.detail; const journalIds = this.data.journalMap[date]; - if (!journalIds || journalIds.length === 0) { wx.showToast({ title: "该日期无日记", @@ -113,81 +85,22 @@ Page({ }); return; } - // 调用接口获取详情 - this.loadJournalsByIds(journalIds, `${year} 年 ${month} 月 ${day} 日`); + this.loadJournalsByIds(journalIds); }, /** 根据 id 列表加载日记详情 */ - async loadJournalsByIds(ids: number[], displayDate: string) { - wx.showLoading({ title: "加载中...", mask: true }); - try { - const list: Journal[] = await new Promise((resolve, reject) => { - wx.request({ - url: `${config.url}/journal/list/ids`, - method: "POST", - header: { - Key: wx.getStorageSync("key") - }, - data: ids, - success: (resp: any) => { - if (resp.data.code === 20000) { - resolve(resp.data.data); - } else { - reject(new Error(resp.data.message || "加载失败")); - } - }, - fail: reject - }); - }) || []; - - // 转换为 JournalInfo 格式 - const journals: JournalInfo[] = list.sort((a, b) => a.createdAt! - b.createdAt!).map((journal: any) => { - const date = new Date(journal.createdAt); - return { - id: journal.id, - date: Time.toPassedDateTime(journal.createdAt), - time: `${String(date.getHours()).padStart(2, '0')}:${String(date.getMinutes()).padStart(2, '0')}`, - lat: journal.lat, - lng: journal.lng, - location: journal.location, - idea: journal.idea, - items: journal.items - .filter((item: any) => item.attachType === MediaAttachType.THUMB) - .map((item: any) => { - const ext = JSON.parse(item.ext); - return { - type: ext.isVideo ? 1 : 0, - thumbURL: `${config.url}/attachment/read/${item.mongoId}`, - sourceURL: `${config.url}/attachment/read/${ext.sourceMongoId}`, - mongoId: item.mongoId, - }; - }) - }; - }); - - this.setData({ - selectedDate: { - displayDate, - journals - }, - popupVisible: true // 显示 popup - }); - - wx.hideLoading(); - } catch (err: any) { - wx.hideLoading(); - wx.showToast({ - title: err.message || "加载失败", - icon: "error" - }); - } + async loadJournalsByIds(ids: number[]) { + this.setData({ + popupIds: ids, + popupVisible: true + }); + wx.hideLoading(); }, - /** 关闭详情 */ closeDetail() { this.setData({ popupVisible: false, - selectedDate: null + popupIds: [] }); } }); diff --git a/miniprogram/pages/main/journal-date/index.wxml b/miniprogram/pages/main/journal-date/index.wxml index 1af99c1..c988eac 100644 --- a/miniprogram/pages/main/journal-date/index.wxml +++ b/miniprogram/pages/main/journal-date/index.wxml @@ -9,10 +9,9 @@ - diff --git a/miniprogram/pages/main/journal-map/index.json b/miniprogram/pages/main/journal-map/index.json index b7014d1..772b017 100644 --- a/miniprogram/pages/main/journal-map/index.json +++ b/miniprogram/pages/main/journal-map/index.json @@ -1,7 +1,7 @@ { "usingComponents": { "t-navbar": "tdesign-miniprogram/navbar/navbar", - "journal-detail-panel": "/components/journal-detail-panel/index" + "journal-detail-popup": "/components/journal-detail-popup/index" }, "navigationStyle": "custom" } diff --git a/miniprogram/pages/main/journal-map/index.ts b/miniprogram/pages/main/journal-map/index.ts index 8a962f0..3d130c8 100644 --- a/miniprogram/pages/main/journal-map/index.ts +++ b/miniprogram/pages/main/journal-map/index.ts @@ -2,7 +2,6 @@ import config from "../../../config/index"; import Time from "../../../utils/Time"; import { Journal, JournalPageType } from "../../../types/Journal"; -import { MediaAttachType } from "../../../types/Attachment"; import Toolkit from "../../../utils/Toolkit"; interface MapMarker { @@ -18,20 +17,6 @@ interface MapMarker { }; } -interface JournalInfo { - id: number; - date: string; - time: string; - location?: string; - idea?: string; - items: Array<{ - type: number; - thumbURL: string; - sourceURL: string; - mongoId: string; - }>; -} - interface LocationMarker { locationKey: string; // 位置键 "lat,lng" lat: number; @@ -43,11 +28,6 @@ interface LocationMarker { previewThumb?: string; // 预览缩略图 } -interface SelectedLocationInfo { - location?: string; - journals: JournalInfo[]; -} - interface JournalMapData { centerLat: number; centerLng: number; @@ -56,9 +36,8 @@ interface JournalMapData { locations: LocationMarker[]; // 位置标记列表 customCalloutMarkerIds: string[]; // 改为 string[] 以支持 locationKey includePoints: Array<{ latitude: number; longitude: number }>; // 缩放视野以包含所有点 - selectedLocation: SelectedLocationInfo | null; // 选中的位置信息 - showDetail: boolean; // 是否显示详情(控制 DOM 存在) - detailVisible: boolean; // 详情是否可见(控制动画) + popupIds: number[]; + popupVisible: boolean; isLoading: boolean; } @@ -72,8 +51,8 @@ Page({ customCalloutMarkerIds: [], includePoints: [], selectedLocation: null, - showDetail: false, - detailVisible: false, + popupIds: [], + popupVisible: false, isLoading: true, }, async onLoad() { @@ -105,10 +84,8 @@ Page({ fail: reject }); }) || []; - // 过滤有位置信息的记录,并按位置分组 const locationMap = new Map(); - list.filter((journal: any) => journal.lat && journal.lng).forEach((journal: any) => { // 保留 6 位小数作为位置键,约等于 0.1 米精度 const lat = Number(journal.lat.toFixed(6)); @@ -116,7 +93,6 @@ Page({ const locationKey = `${lat},${lng}`; if (!locationMap.has(locationKey)) { - // 获取第一个有缩略图的日记 const thumbItem = journal.items.find((item: any) => item.attachType === "THUMB"); locationMap.set(locationKey, { locationKey, @@ -129,7 +105,6 @@ Page({ previewThumb: thumbItem ? `${config.url}/attachment/read/${thumbItem.mongoId}` : undefined }); } - const marker = locationMap.get(locationKey)!; marker.journalIds.push(journal.id); marker.count++; @@ -141,9 +116,7 @@ Page({ } } }); - const locations = Array.from(locationMap.values()); - if (locations.length === 0) { wx.showToast({ title: "暂无位置记录", @@ -152,7 +125,6 @@ Page({ this.setData({ isLoading: false }); return; } - // 生成地图标记 const markers: MapMarker[] = locations.map((location, index) => ({ id: index, @@ -162,11 +134,11 @@ Page({ height: 30, customCallout: { anchorY: -2, - anchorX: 0, + // 随机错位避免近距离重叠 + anchorX: Toolkit.random(-10, 10), display: "ALWAYS" } })); - // 所有标记的 locationKey 列表 const customCalloutMarkerIds = locations.map(l => l.locationKey); // 计算中心点(所有标记的平均位置) @@ -177,7 +149,6 @@ Page({ latitude: l.lat, longitude: l.lng })); - this.setData({ locations, markers, @@ -208,81 +179,18 @@ Page({ /** 加载位置详情(该位置的所有日记) */ async loadLocationDetail(markerId: number) { const location = this.data.locations[markerId]; - if (!location) return; - - wx.showLoading({ title: "加载中...", mask: true }); - try { - // 根据 journalIds 加载日记详情 - const list: Journal[] = await new Promise((resolve, reject) => { - wx.request({ - url: `${config.url}/journal/list/ids`, - method: "POST", - header: { - Key: wx.getStorageSync("key") - }, - data: location.journalIds, - success: (resp: any) => { - if (resp.data.code === 20000) { - resolve(resp.data.data); - } else { - reject(new Error(resp.data.message || "加载失败")); - } - }, - fail: reject - }); - }) || []; - - // 转换为 JournalInfo 格式 - const journals: JournalInfo[] = list.map((journal: any) => { - const date = new Date(journal.createdAt); - return { - id: journal.id, - date: Time.toPassedDateTime(journal.createdAt), - time: `${String(date.getHours()).padStart(2, '0')}:${String(date.getMinutes()).padStart(2, '0')}`, - lat: journal.lat, - lng: journal.lng, - location: journal.location, - idea: journal.idea, - items: journal.items - .filter((item: any) => item.attachType === MediaAttachType.THUMB) - .map((item: any) => { - const ext = JSON.parse(item.ext); - return { - type: ext.isVideo ? 1 : 0, - thumbURL: `${config.url}/attachment/read/${item.mongoId}`, - sourceURL: `${config.url}/attachment/read/${ext.sourceMongoId}`, - mongoId: item.mongoId, - }; - }) - }; - }); - - // 先显示元素,再触发动画 - this.setData({ - selectedLocation: { - location: location.location, - journals - }, - showDetail: true - }); - wx.nextTick(() => { - this.setData({ detailVisible: true }); - }); - wx.hideLoading(); - } catch (err: any) { - wx.hideLoading(); - wx.showToast({ - title: err.message || "加载失败", - icon: "error" - }); + if (!location) { + return; } + this.setData({ + popupIds: location.journalIds, + popupVisible: true + }); }, /** 关闭详情 */ async closeDetail() { - this.setData({ detailVisible: false }); - await Toolkit.sleep(350); this.setData({ - showDetail: false, + popupVisible: false, selectedLocation: null }); }, diff --git a/miniprogram/pages/main/journal-map/index.wxml b/miniprogram/pages/main/journal-map/index.wxml index 1d08050..0c4789b 100644 --- a/miniprogram/pages/main/journal-map/index.wxml +++ b/miniprogram/pages/main/journal-map/index.wxml @@ -37,10 +37,9 @@ - diff --git a/miniprogram/types/Journal.ts b/miniprogram/types/Journal.ts index b36ec5d..33f6a66 100644 --- a/miniprogram/types/Journal.ts +++ b/miniprogram/types/Journal.ts @@ -1,5 +1,6 @@ import { Attachment } from "./Attachment"; import { Model, QueryPage } from "./Model"; +import { MediaItem } from "./UI"; /** 日记 */ export type Journal = { @@ -22,8 +23,22 @@ export type Journal = { /** 天气 */ weatcher?: string; + // ---------- 以下为 VO 字段 ---------- + + /** 日期 */ + date?: string; + + /** 时间 */ + time?: string; + + /** 时间 */ + datetime?: string; + /** 附件(照片、视频等) */ items?: Attachment[]; + + /** 媒体项(由附件转) */ + mediaItems?: MediaItem[]; } & Model; /** 日记类型 */ diff --git a/miniprogram/types/UI.ts b/miniprogram/types/UI.ts index b46f29d..12b6c4f 100644 --- a/miniprogram/types/UI.ts +++ b/miniprogram/types/UI.ts @@ -2,6 +2,8 @@ /** 系统媒体项目 */ export type MediaItem = { + journalId?: number; + /** 类型 */ type: MediaItemType;