diff --git a/miniprogram/components/journal-detail-popup/index.less b/miniprogram/components/journal-detail-popup/index.less
index b1b9ebe..6b2206b 100644
--- a/miniprogram/components/journal-detail-popup/index.less
+++ b/miniprogram/components/journal-detail-popup/index.less
@@ -131,17 +131,19 @@
height: 100%;
.items {
+ gap: 8rpx;
+ display: flex;
padding: 0 32rpx 128rpx 32rpx;
-
- .wrapper {
- column-gap: 8rpx;
- column-count: 3;
- padding-bottom: 128rpx;
+ align-items: flex-start;
+
+ .column {
+ flex: 1;
+ display: flex;
+ flex-direction: column;
.item {
overflow: hidden;
background: var(--theme-bg-card);
- break-inside: avoid;
margin-bottom: 8rpx;
&.thumbnail {
diff --git a/miniprogram/components/journal-detail-popup/index.ts b/miniprogram/components/journal-detail-popup/index.ts
index 3c9b261..713cbf6 100644
--- a/miniprogram/components/journal-detail-popup/index.ts
+++ b/miniprogram/components/journal-detail-popup/index.ts
@@ -1,7 +1,8 @@
// components/journal-detail-panel/index.ts
import { Journal } from "../../types/Journal";
import config from "../../config/index";
-import { MediaAttachExt, MediaAttachType } from "../../types/Attachment";
+import Toolkit from "../../utils/Toolkit";
+import { ImageMetadata, MediaAttachExt, MediaAttachType } from "../../types/Attachment";
import { MediaItem, MediaItemType } from "../../types/UI";
import Time from "../../utils/Time";
@@ -61,7 +62,8 @@ Component({
if (!thumbItems) {
return;
}
- const mediaItems: MediaItem[] = thumbItems.map((thumbItem) => {
+ const mediaItems: MediaItem[] = thumbItems.map((thumbItem, index) => {
+ const metadata = thumbItem.metadata as ImageMetadata;
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}`;
@@ -70,10 +72,21 @@ Component({
thumbURL,
sourceURL,
size: thumbItem.size || 0,
- attachmentId: thumbItem.id
+ attachmentId: thumbItem.id,
+ width: metadata.width,
+ height: metadata.height,
+ originalIndex: index
} as MediaItem;
});
journal.mediaItems = mediaItems;
+ // 为每个 journal 添加分列后的 mediaItems
+ (journal as any).columnedMediaItems = Toolkit.splitItemsIntoColumns(mediaItems, 3, (item) => {
+ // 计算相对高度:如果有宽高信息,使用宽高比;否则假设为正方形
+ if (item.width && item.height && 0 < item.width) {
+ return item.height / item.width;
+ }
+ return 1; // 默认正方形
+ });
})
this.setData({
journals,
@@ -119,14 +132,15 @@ Component({
},
/** 预览媒体 */
previewMedia(e: WechatMiniprogram.BaseEvent) {
- const journals = this.properties.journals as Journal[];
+ const journals = this.data.journals;
if (!journals || journals.length === 0) {
return;
}
const { itemIndex } = e.currentTarget.dataset;
- const items = journals[this.data.currentJournalIndex].mediaItems!;
+ const journal = journals[this.data.currentJournalIndex];
+ const items = journal.mediaItems!;
const total = items.length;
-
+
const startIndex = Math.max(0, itemIndex - 25);
const endIndex = Math.min(total, startIndex + 50);
const newCurrentIndex = itemIndex - startIndex;
@@ -134,7 +148,7 @@ Component({
const sources = items.slice(startIndex, endIndex).map((item) => {
return {
url: item.sourceURL,
- type: item.type === 0 ? "image" : "video"
+ type: item.type === MediaItemType.IMAGE ? "image" : "video"
}
}) as any;
wx.previewMedia({
diff --git a/miniprogram/components/journal-detail-popup/index.wxml b/miniprogram/components/journal-detail-popup/index.wxml
index 8499210..76df922 100644
--- a/miniprogram/components/journal-detail-popup/index.wxml
+++ b/miniprogram/components/journal-detail-popup/index.wxml
@@ -40,16 +40,16 @@
{{item.datetime}}
{{item.idea}}
-
+
-
-
+
+
diff --git a/miniprogram/pages/main/journal/index.less b/miniprogram/pages/main/journal/index.less
index 4bf91c3..3c4fd65 100644
--- a/miniprogram/pages/main/journal/index.less
+++ b/miniprogram/pages/main/journal/index.less
@@ -78,37 +78,43 @@
}
.items {
- column-gap: .25rem;
- column-count: 3;
+ gap: .25rem;
+ display: flex;
padding-bottom: 2rem;
+ align-items: flex-start;
- .item {
- overflow: hidden;
- background: var(--theme-bg-card);
- break-inside: avoid;
- margin-bottom: .25rem;
+ .column {
+ flex: 1;
+ display: flex;
+ flex-direction: column;
- &.thumbnail {
- width: 100%;
- display: block;
- }
+ .item {
+ overflow: hidden;
+ background: var(--theme-bg-card);
+ margin-bottom: .25rem;
- &.video {
- height: auto;
- position: relative;
+ &.thumbnail {
+ width: 100%;
+ display: block;
+ }
- &::after {
- content: "";
- top: 50%;
- left: 53%;
- width: 0;
- height: 0;
- position: absolute;
- transform: translate(-50%, -50%);
- border-top: 16px solid transparent;
- border-left: 24px solid var(--theme-video-play);
- border-bottom: 16px solid transparent;
- pointer-events: none;
+ &.video {
+ height: auto;
+ position: relative;
+
+ &::after {
+ content: "";
+ top: 50%;
+ left: 53%;
+ width: 0;
+ height: 0;
+ position: absolute;
+ transform: translate(-50%, -50%);
+ border-top: 16px solid transparent;
+ border-left: 24px solid var(--theme-video-play);
+ border-bottom: 16px solid transparent;
+ pointer-events: none;
+ }
}
}
}
diff --git a/miniprogram/pages/main/journal/index.ts b/miniprogram/pages/main/journal/index.ts
index b45a030..dfce207 100644
--- a/miniprogram/pages/main/journal/index.ts
+++ b/miniprogram/pages/main/journal/index.ts
@@ -3,27 +3,11 @@
import Time from "../../../utils/Time";
import config from "../../../config/index"
import Events from "../../../utils/Events";
-import { JournalPage, JournalPageType } from "../../../types/Journal";
-import { OrderType } from "../../../types/Model";
-
-export type Journal = {
- date: string;
- location?: string;
- lat?: number;
- lng?: number;
- idea?: string;
- items: JournalItem[]
-}
-
-export type JournalItem = {
- type: JournalItemType;
- mongoId: string;
-}
-
-export enum JournalItemType {
- IMAGE,
- VIDEO
-}
+import Toolkit from "../../../utils/Toolkit";
+import { Journal, JournalPage, JournalPageType } from "../../../types/Journal";
+import { OrderType, QueryPageResult } from "../../../types/Model";
+import { ImageMetadata, MediaAttachExt } from "../../../types/Attachment";
+import { MediaItem, MediaItemType } from "../../../types/UI";
interface JournalData {
page: JournalPage;
@@ -174,30 +158,41 @@ Page({
},
data: this.data.page,
success: async (resp: any) => {
- const list = resp.data.data.list;
+ const pageResult = resp.data.data as QueryPageResult;
+ const list = pageResult.list;
if (!list || list.length === 0) {
this.setData({
isFinished: true
})
return;
}
- const result = list.map((journal: any) => {
- return {
- date: Time.toPassedDateTime(journal.createdAt),
- idea: journal.idea,
- lat: journal.lat,
- lng: journal.lng,
- location: journal.location,
- items: journal.items.filter((item: any) => item.attachType === "THUMB").map((item: any) => {
- const ext = JSON.parse(item.ext);
- return {
- type: ext.isVideo ? JournalItemType.VIDEO : JournalItemType.IMAGE,
- thumbUrl: `${config.url}/attachment/read/${item.mongoId}`,
- mongoId: item.mongoId,
- source: journal.items.find((source: any) => source.id === ext.sourceId)
- }
- })
- }
+ list.forEach(journal => {
+ const mediaItems = journal.items!.filter((item) => item.attachType === "THUMB").map((thumbItem, index) => {
+ const metadata = thumbItem.metadata as ImageMetadata;
+ 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,
+ width: metadata.width,
+ height: metadata.height,
+ originalIndex: index
+ } as MediaItem;
+ });
+ journal.date = Time.toDate(journal.createdAt);
+ journal.time = Time.toTime(journal.createdAt);
+ journal.datetime = Time.toPassedDateTime(journal.createdAt);
+ journal.mediaItems = mediaItems;
+ journal.columnedItems = Toolkit.splitItemsIntoColumns(mediaItems, 3, (item) => {
+ if (item.width && item.height && 0 < item.width) {
+ return item.height / item.width;
+ }
+ return 1;
+ })
})
this.setData({
page: {
@@ -211,7 +206,7 @@ Page({
createdAt: OrderType.DESC
}
},
- list: this.data.list.concat(result),
+ list: this.data.list.concat(list),
isFinished: list.length < this.data.page.size
});
},
@@ -224,17 +219,18 @@ Page({
},
preview(e: WechatMiniprogram.BaseEvent) {
const { journalIndex, itemIndex } = e.currentTarget.dataset;
- const items = this.data.list[journalIndex].items;
+ const journal = this.data.list[journalIndex];
+ const items = journal.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: any) => {
+ const sources = items.slice(startIndex, endIndex).map((item) => {
return {
- url: `${config.url}/attachment/read/${item.source.mongoId}`,
- type: item.type === 0 ? "image" : "video"
+ url: item.sourceURL,
+ type: item.type === MediaItemType.IMAGE ? "image" : "video"
}
}) as any;
wx.previewMedia({
diff --git a/miniprogram/pages/main/journal/index.wxml b/miniprogram/pages/main/journal/index.wxml
index 46b472d..bcb42d9 100644
--- a/miniprogram/pages/main/journal/index.wxml
+++ b/miniprogram/pages/main/journal/index.wxml
@@ -37,17 +37,19 @@
{{journal.location}}
-
-
-
-
+
+
+
+
+
+
已回到最初的起点
diff --git a/miniprogram/types/Attachment.ts b/miniprogram/types/Attachment.ts
index 8a9b50e..74839ba 100644
--- a/miniprogram/types/Attachment.ts
+++ b/miniprogram/types/Attachment.ts
@@ -15,6 +15,10 @@ export type Attachment = {
/** 文件名 */
name: string;
+ mimeType?: string;
+
+ metadata?: string | ImageMetadata;
+
/** 文件 MD5 */
md5: string;
@@ -38,6 +42,16 @@ export enum MediaAttachType {
THUMB = "THUMB"
}
+/** 图片附件元数据 */
+export type ImageMetadata = {
+
+ /** 宽度 */
+ width: number;
+
+ /** 高度 */
+ height: number;
+}
+
/** 媒体附件扩展数据 */
export type MediaAttachExt = {
@@ -52,4 +66,10 @@ export type MediaAttachExt = {
/** true 为视频 */
isVideo: boolean;
+
+ /** 原图宽度(像素) */
+ width?: number;
+
+ /** 原图高度(像素) */
+ height?: number;
}
diff --git a/miniprogram/types/Journal.ts b/miniprogram/types/Journal.ts
index 33f6a66..1d6f844 100644
--- a/miniprogram/types/Journal.ts
+++ b/miniprogram/types/Journal.ts
@@ -31,11 +31,14 @@ export type Journal = {
/** 时间 */
time?: string;
- /** 时间 */
+ /** 详细时间 */
datetime?: string;
/** 附件(照片、视频等) */
items?: Attachment[];
+
+ /** 分列后的 items,用于瀑布流展示 */
+ columnedItems?: MediaItem[][];
/** 媒体项(由附件转) */
mediaItems?: MediaItem[];
diff --git a/miniprogram/types/UI.ts b/miniprogram/types/UI.ts
index 12b6c4f..072907b 100644
--- a/miniprogram/types/UI.ts
+++ b/miniprogram/types/UI.ts
@@ -18,6 +18,15 @@ export type MediaItem = {
/** 附件 ID */
attachmentId: number;
+
+ /** 原图宽度 */
+ width?: number;
+
+ /** 原图高度 */
+ height?: number;
+
+ /** 原始索引(用于预览时定位) */
+ originalIndex?: number;
}
/** 微信媒体项目 */
diff --git a/miniprogram/utils/Toolkit.ts b/miniprogram/utils/Toolkit.ts
index 20a29e8..0aa0d6a 100644
--- a/miniprogram/utils/Toolkit.ts
+++ b/miniprogram/utils/Toolkit.ts
@@ -220,10 +220,58 @@ export default class Toolkit {
public static symmetricDiff(arr1: any[], arr2: any[]) {
const set1 = new Set(arr1);
const set2 = new Set(arr2);
-
+
return [
...arr1.filter(item => !set2.has(item)),
...arr2.filter(item => !set1.has(item))
];
}
+
+ /**
+ * 将数组元素分配到指定数量的列中
+ *
+ * - 如果提供 getHeight 函数:使用贪心算法,每次将元素放到当前高度最小的列(适合瀑布流布局)
+ * - 如果不提供 getHeight:使用轮询方式分配(第 1、4、7... 个到列 1,第 2、5、8... 个到列 2)
+ *
+ * @param items 原始数组
+ * @param columnCount 列数,默认 3
+ * @param getHeight 可选的高度获取函数,用于计算每个元素的高度
+ * @returns 分列后的二维数组
+ *
+ * @example
+ * // 简单轮询分配
+ * splitItemsIntoColumns([1, 2, 3, 4, 5, 6], 3)
+ * // => [[1, 4], [2, 5], [3, 6]]
+ *
+ * @example
+ * // 基于高度的贪心分配
+ * const items = [{ h: 100 }, { h: 200 }, { h: 150 }]
+ * splitItemsIntoColumns(items, 2, item => item.h)
+ * // => [[ { h: 100 }, { h: 150 }], [{ h: 200 }]]
+ */
+ public static splitItemsIntoColumns(
+ items: T[],
+ columnCount = 3,
+ getHeight?: (item: T) => number
+ ): T[][] {
+ const columns: T[][] = Array.from({ length: columnCount }, () => []);
+
+ if (!getHeight) {
+ // 降级为轮询分配
+ items.forEach((item, index) => {
+ columns[index % columnCount].push(item);
+ });
+ } else {
+ // 使用贪心算法:总是放到当前高度最小的列
+ const columnHeights = Array(columnCount).fill(0);
+ items.forEach((item) => {
+ // 找到高度最小的列
+ const minHeightIndex = columnHeights.indexOf(Math.min(...columnHeights));
+ columns[minHeightIndex].push(item);
+ columnHeights[minHeightIndex] += getHeight(item);
+ });
+ }
+
+ return columns;
+ }
}