Compare commits

..

18 Commits

Author SHA1 Message Date
8adc28ae9c update 1.6.6 2026-01-04 19:09:52 +08:00
e31a3432a0 remove PreviewImageMetadata.isImage/isVideo 2026-01-04 19:09:28 +08:00
2c6478c729 merge Attachment metadata/ext 2026-01-04 17:41:02 +08:00
f4232e8752 udpate 1.6.5 2025-12-23 16:18:23 +08:00
41e2959a72 fix travel location type 2025-12-23 16:18:16 +08:00
bf5deff045 update 1.6.4 2025-12-23 14:57:37 +08:00
f94cf05f62 preview travel location attach in list 2025-12-23 14:45:15 +08:00
9538a21e42 add MALL travel type 2025-12-23 14:27:51 +08:00
3b091c4f18 update 1.6.3 2025-12-20 14:45:59 +08:00
b64e2767c2 add fiilter for travel location list 2025-12-20 14:45:42 +08:00
df7cfa95a0 fix miss key for Network.request 2025-12-20 13:53:16 +08:00
84fc382c91 fix travel order 2025-12-18 21:05:08 +08:00
107177d095 fix travel location attachment style 2025-12-18 19:33:31 +08:00
2966289930 fix TravelLocation type 2025-12-18 19:24:10 +08:00
6f74559c01 fix map marker width 2025-12-18 19:21:46 +08:00
186a74bc77 scroe and importance allow nullable 2025-12-18 11:59:01 +08:00
51e679dd83 fix transport icon 2025-12-18 11:57:29 +08:00
1ad1da1c4e fix scroll exception 2025-12-18 11:57:07 +08:00
31 changed files with 561 additions and 361 deletions

View File

@ -2,7 +2,7 @@
import { Journal } from "../../types/Journal"; import { Journal } from "../../types/Journal";
import config from "../../config/index"; import config from "../../config/index";
import Toolkit from "../../utils/Toolkit"; import Toolkit from "../../utils/Toolkit";
import { ImageMetadata, MediaAttachExt, MediaAttachType } from "../../types/Attachment"; import { MediaAttachType, PreviewImageMetadata } from "../../types/Attachment";
import { MediaItem, MediaItemType } from "../../types/UI"; import { MediaItem, MediaItemType } from "../../types/UI";
import Time from "../../utils/Time"; import Time from "../../utils/Time";
import { JournalApi } from "../../api/JournalApi"; import { JournalApi } from "../../api/JournalApi";
@ -47,12 +47,12 @@ Component({
return; return;
} }
const mediaItems: MediaItem[] = thumbItems.map((thumbItem, index) => { const mediaItems: MediaItem[] = thumbItems.map((thumbItem, index) => {
const metadata = thumbItem.metadata as ImageMetadata; const metadata = (typeof thumbItem.metadata === "string" ? JSON.parse(thumbItem.metadata) : thumbItem.metadata) as PreviewImageMetadata;
const ext = thumbItem.ext = JSON.parse(thumbItem.ext!.toString()) as MediaAttachExt;
const thumbURL = `${config.url}/attachment/read/${thumbItem.mongoId}`; const thumbURL = `${config.url}/attachment/read/${thumbItem.mongoId}`;
const sourceURL = `${config.url}/attachment/read/${ext.sourceMongoId}`; const sourceURL = `${config.url}/attachment/read/${metadata.sourceMongoId}`;
const isVideo = metadata.sourceMimeType?.startsWith("video/");
return { return {
type: ext.isVideo ? MediaItemType.VIDEO : MediaItemType.IMAGE, type: isVideo ? MediaItemType.VIDEO : MediaItemType.IMAGE,
thumbURL, thumbURL,
sourceURL, sourceURL,
size: thumbItem.size || 0, size: thumbItem.size || 0,

View File

@ -57,7 +57,7 @@
.loading, .loading,
.finished { .finished {
color: var(--td-text-color-placeholder); color: var(--td-text-color-placeholder);
padding: 32rpx 0; padding: 32rpx 0 64rpx 0;
font-size: 24rpx; font-size: 24rpx;
text-align: center; text-align: center;
} }

View File

@ -1,7 +1,7 @@
// components/travel-location-popup/index.ts // components/travel-location-popup/index.ts
import { TravelLocation, TravelLocationTypeLabel, TravelLocationTypeIcon } from "../../types/Travel"; import { TravelLocation, TravelLocationTypeLabel, TravelLocationTypeIcon } from "../../types/Travel";
import { TravelLocationApi } from "../../api/TravelLocationApi"; import { TravelLocationApi } from "../../api/TravelLocationApi";
import { ImageMetadata, MediaAttachType } from "../../types/Attachment"; import { MediaAttachType, PreviewImageMetadata } from "../../types/Attachment";
import { MediaItem, MediaItemType } from "../../types/UI"; import { MediaItem, MediaItemType } from "../../types/UI";
import config from "../../config/index"; import config from "../../config/index";
import Toolkit from "../../utils/Toolkit"; import Toolkit from "../../utils/Toolkit";
@ -43,12 +43,12 @@ Component({
if (0 < thumbItems.length) { if (0 < thumbItems.length) {
const mediaItems: MediaItem[] = thumbItems.map((thumbItem, index) => { const mediaItems: MediaItem[] = thumbItems.map((thumbItem, index) => {
const metadata = thumbItem.metadata as ImageMetadata; const metadata = (typeof thumbItem.metadata === "string" ? JSON.parse(thumbItem.metadata) : thumbItem.metadata) as PreviewImageMetadata;
const ext = typeof thumbItem.ext === "string" ? JSON.parse(thumbItem.ext) : thumbItem.ext;
const thumbURL = `${config.url}/attachment/read/${thumbItem.mongoId}`; const thumbURL = `${config.url}/attachment/read/${thumbItem.mongoId}`;
const sourceURL = `${config.url}/attachment/read/${ext.sourceMongoId}`; const sourceURL = `${config.url}/attachment/read/${metadata.sourceMongoId}`;
const isVideo = metadata.sourceMimeType?.startsWith("video/");
return { return {
type: ext.isVideo ? MediaItemType.VIDEO : MediaItemType.IMAGE, type: isVideo ? MediaItemType.VIDEO : MediaItemType.IMAGE,
thumbURL, thumbURL,
sourceURL, sourceURL,
size: thumbItem.size || 0, size: thumbItem.size || 0,

View File

@ -1,11 +1,12 @@
const envArgs = { const envArgs = {
develop: { develop: {
url: "http://localhost:8091"
// url: "https://api.imyeyu.dev" // url: "https://api.imyeyu.dev"
// url: "https://api.imyeyu.com" // url: "https://api.imyeyu.com"
// url: "http://192.168.3.123:8091" // url: "http://192.168.3.123:8091"
// url: "http://192.168.3.137:8091" // url: "http://192.168.3.137:8091"
// url: "http://192.168.3.173:8091" // url: "http://192.168.3.173:8091"
url: "http://192.168.3.174:8091" // url: "http://192.168.3.174:8091"
}, },
trial: { trial: {
url: "https://api.imyeyu.com" url: "https://api.imyeyu.com"

View File

@ -1,7 +1,5 @@
// index.ts // index.ts
import { JournalPageType } from "../../types/Journal";
import config from "../../config/index"
import { JournalPage, JournalPageType } from "../../types/Journal";
import { JournalApi } from "../../api/JournalApi"; import { JournalApi } from "../../api/JournalApi";
interface IndexData { interface IndexData {
@ -22,12 +20,12 @@ Page({
}, },
async navigateToMain() { async navigateToMain() {
try { try {
wx.setStorageSync("key", this.data.key);
await JournalApi.getList({ await JournalApi.getList({
index: 0, index: 0,
size: 1, size: 1,
type: JournalPageType.PREVIEW type: JournalPageType.PREVIEW
}); });
wx.setStorageSync("key", this.data.key);
wx.switchTab({ wx.switchTab({
url: "/pages/main/journal/index", url: "/pages/main/journal/index",
}) })

View File

@ -26,7 +26,7 @@
</view> </view>
<view class="item"> <view class="item">
<text class="label">版本:</text> <text class="label">版本:</text>
<text>1.6.2</text> <text>1.6.6</text>
</view> </view>
<view class="item copyright"> <view class="item copyright">
<text>{{copyright}}</text> <text>{{copyright}}</text>

View File

@ -5,7 +5,7 @@ import Toolkit from "../../../utils/Toolkit";
import config from "../../../config/index"; import config from "../../../config/index";
import { Location, MediaItem, MediaItemType, WechatMediaItem } from "../../../types/UI"; import { Location, MediaItem, MediaItemType, WechatMediaItem } from "../../../types/UI";
import { JournalType } from "../../../types/Journal"; import { JournalType } from "../../../types/Journal";
import { MediaAttachExt, MediaAttachType } from "../../../types/Attachment"; import { MediaAttachType, PreviewImageMetadata } from "../../../types/Attachment";
import IOSize, { Unit } from "../../../utils/IOSize"; import IOSize, { Unit } from "../../../utils/IOSize";
import { JournalApi } from "../../../api/JournalApi"; import { JournalApi } from "../../../api/JournalApi";
@ -156,11 +156,12 @@ Page({
const thumbItems = items.filter((item) => item.attachType === MediaAttachType.THUMB); const thumbItems = items.filter((item) => item.attachType === MediaAttachType.THUMB);
const mediaList: MediaItem[] = thumbItems.map((thumbItem) => { const mediaList: MediaItem[] = thumbItems.map((thumbItem) => {
const ext = thumbItem.ext = JSON.parse(thumbItem.ext!.toString()) as MediaAttachExt; const metadata = (typeof thumbItem.metadata === "string" ? JSON.parse(thumbItem.metadata) : thumbItem.metadata) as PreviewImageMetadata;
const thumbURL = `${config.url}/attachment/read/${thumbItem.mongoId}`; const thumbURL = `${config.url}/attachment/read/${thumbItem.mongoId}`;
const sourceURL = `${config.url}/attachment/read/${ext.sourceMongoId}`; const sourceURL = `${config.url}/attachment/read/${metadata.sourceMongoId}`;
const isVideo = metadata.sourceMimeType?.startsWith("video/");
return { return {
type: ext.isVideo ? MediaItemType.VIDEO : MediaItemType.IMAGE, type: isVideo ? MediaItemType.VIDEO : MediaItemType.IMAGE,
thumbURL, thumbURL,
sourceURL, sourceURL,
size: thumbItem.size || 0, size: thumbItem.size || 0,

View File

@ -8,22 +8,19 @@
.map { .map {
width: 100%; width: 100%;
height: 100%; height: 100%;
}
.custom-callout { .location {
width: fit-content; width: fit-content;
min-width: 350rpx; background: var(--theme-bg-card);
max-width: 450rpx;
background: var(--theme-bg-card-secondary);
box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, .15); box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, .15);
border-radius: 6rpx; border-radius: 6rpx;
.callout-content { .content {
display: flex; display: flex;
padding: 8rpx 16rpx 8rpx 8rpx; padding: 8rpx 16rpx 8rpx 8rpx;
align-items: flex-start; align-items: flex-start;
.thumb-container { .thumb {
width: 72rpx; width: 72rpx;
height: 72rpx; height: 72rpx;
position: relative; position: relative;
@ -32,12 +29,12 @@
overflow: hidden; overflow: hidden;
border-radius: 6rpx; border-radius: 6rpx;
.thumb { .img {
width: 100%; width: 100%;
height: 100%; height: 100%;
} }
.count-badge { .badge {
top: 4rpx; top: 4rpx;
right: 4rpx; right: 4rpx;
color: #fff; color: #fff;
@ -49,14 +46,15 @@
} }
} }
.text-container { .text {
flex: 1; flex: 1;
display: flex; display: flex;
overflow: hidden; overflow: hidden;
flex-direction: column; flex-direction: column;
.location { .value {
color: var(--theme-text-primary); color: var(--theme-text-primary);
width: calc(var(--title-length) * 30rpx);
overflow: hidden; overflow: hidden;
font-size: 30rpx; font-size: 30rpx;
white-space: nowrap; white-space: nowrap;
@ -82,6 +80,7 @@
} }
} }
} }
}
.loading { .loading {
top: 50%; top: 50%;

View File

@ -13,23 +13,28 @@
bindcallouttap="onCalloutTap" bindcallouttap="onCalloutTap"
> >
<cover-view slot="callout"> <cover-view slot="callout">
<block wx:for="{{locations}}" wx:key="locationKey" wx:for-index="markerIndex"> <cover-view
<cover-view class="custom-callout" marker-id="{{markerIndex}}"> class="location"
<cover-view class="callout-content"> wx:for="{{locations}}"
<cover-view wx:if="{{item.previewThumb}}" class="thumb-container"> wx:key="locationKey"
<cover-image class="thumb" src="{{item.previewThumb}}" /> wx:for-index="locationIndex"
<cover-view wx:if="{{item.count > 1}}" class="count-badge">{{item.count}}</cover-view> marker-id="{{locationIndex}}"
style="{{'--title-length: ' + item.location.length}}"
>
<cover-view class="content">
<cover-view wx:if="{{item.previewThumb}}" class="thumb">
<cover-image class="img" src="{{item.previewThumb}}" />
<cover-view wx:if="{{1 < item.count}}" class="badge">{{item.count}}</cover-view>
</cover-view> </cover-view>
<cover-view class="text-container"> <cover-view class="text">
<cover-view wx:if="{{item.location}}" class="location">{{item.location}}</cover-view> <cover-view wx:if="{{item.location}}" class="value">{{item.location}}</cover-view>
<cover-view class="date-count"> <cover-view class="date-count">
<cover-view class="date">{{item.date}}</cover-view> <cover-view class="date">{{item.date}}</cover-view>
<cover-view wx:if="{{item.count > 1}}" class="count">{{item.count}} 条日记</cover-view> <cover-view wx:if="{{1 < item.count}}" class="count">{{item.count}} 条日记</cover-view>
</cover-view> </cover-view>
</cover-view> </cover-view>
</cover-view> </cover-view>
</cover-view> </cover-view>
</block>
</cover-view> </cover-view>
</map> </map>
<view wx:if="{{isLoading}}" class="loading"> <view wx:if="{{isLoading}}" class="loading">

View File

@ -1,8 +1,21 @@
page { page {
height: 100vh;
background: var(--td-bg-color-page); background: var(--td-bg-color-page);
} }
.content { .page-container {
width: 100vw; width: 100%;
height: calc(100vh - var(--navbar-height, 88rpx)); height: 100%;
display: flex;
overflow: hidden;
flex-direction: column;
.navbar {
flex-shrink: 0;
}
.content {
flex: 1;
overflow: hidden;
}
} }

View File

@ -1,7 +1,7 @@
<view class="custom-navbar"> <view class="page-container">
<view class="navbar">
<t-navbar title="列表查找" left-arrow /> <t-navbar title="列表查找" left-arrow />
</view> </view>
<view class="content"> <view class="content">
<journal-list <journal-list
id="listRef" id="listRef"
@ -10,3 +10,4 @@
bind:navigate="onNavigateItem" bind:navigate="onNavigateItem"
/> />
</view> </view>
</view>

View File

@ -6,7 +6,7 @@ import Events from "../../../utils/Events";
import Toolkit from "../../../utils/Toolkit"; import Toolkit from "../../../utils/Toolkit";
import { Journal, JournalPage, JournalPageType } from "../../../types/Journal"; import { Journal, JournalPage, JournalPageType } from "../../../types/Journal";
import { OrderType } from "../../../types/Model"; import { OrderType } from "../../../types/Model";
import { ImageMetadata, MediaAttachExt } from "../../../types/Attachment"; import { PreviewImageMetadata } from "../../../types/Attachment";
import { MediaItem, MediaItemType } from "../../../types/UI"; import { MediaItem, MediaItemType } from "../../../types/UI";
import { JournalApi } from "../../../api/JournalApi"; import { JournalApi } from "../../../api/JournalApi";
@ -163,12 +163,12 @@ Page({
} }
list.forEach(journal => { list.forEach(journal => {
const mediaItems = journal.items!.filter((item) => item.attachType === "THUMB").map((thumbItem, index) => { const mediaItems = journal.items!.filter((item) => item.attachType === "THUMB").map((thumbItem, index) => {
const metadata = thumbItem.metadata as ImageMetadata; const metadata = (typeof thumbItem.metadata === "string" ? JSON.parse(thumbItem.metadata) : thumbItem.metadata) as PreviewImageMetadata;
const ext = thumbItem.ext = JSON.parse(thumbItem.ext!.toString()) as MediaAttachExt;
const thumbURL = `${config.url}/attachment/read/${thumbItem.mongoId}`; const thumbURL = `${config.url}/attachment/read/${thumbItem.mongoId}`;
const sourceURL = `${config.url}/attachment/read/${ext.sourceMongoId}`; const sourceURL = `${config.url}/attachment/read/${metadata.sourceMongoId}`;
const isVideo = metadata.sourceMimeType?.startsWith("video/");
return { return {
type: ext.isVideo ? MediaItemType.VIDEO : MediaItemType.IMAGE, type: isVideo ? MediaItemType.VIDEO : MediaItemType.IMAGE,
thumbURL, thumbURL,
sourceURL, sourceURL,
size: thumbItem.size || 0, size: thumbItem.size || 0,

View File

@ -5,7 +5,7 @@ import IOSize, { Unit } from "../../../utils/IOSize";
import Time from "../../../utils/Time"; import Time from "../../../utils/Time";
import Toolkit from "../../../utils/Toolkit"; import Toolkit from "../../../utils/Toolkit";
import { Location, MediaItemType } from "../../../types/UI"; import { Location, MediaItemType } from "../../../types/UI";
import { MediaAttachExt } from "../../../types/Attachment"; import { PreviewImageMetadata } from "../../../types/Attachment";
import { MomentApi } from "../../../api/MomentApi"; import { MomentApi } from "../../../api/MomentApi";
import { JournalApi } from "../../../api/JournalApi"; import { JournalApi } from "../../../api/JournalApi";
import { Network } from "../../../utils/Network"; import { Network } from "../../../utils/Network";
@ -134,12 +134,13 @@ Page({
} }
this.setData({ this.setData({
list: list.map((item: any) => { list: list.map((item: any) => {
const ext = JSON.parse(item.ext) as MediaAttachExt; const metadata = (typeof item.metadata === "string" ? JSON.parse(item.metadata) : item.metadata) as PreviewImageMetadata;
const thumbURL = `${config.url}/attachment/read/${item.mongoId}`; const thumbURL = `${config.url}/attachment/read/${item.mongoId}`;
const sourceURL = `${config.url}/attachment/read/${ext.sourceMongoId}`; const sourceURL = `${config.url}/attachment/read/${metadata.sourceMongoId}`;
const isImage = metadata.sourceMimeType?.startsWith("image/");
return { return {
id: item.id, id: item.id,
type: ext.isImage ? MediaItemType.IMAGE : MediaItemType.VIDEO, type: isImage ? MediaItemType.IMAGE : MediaItemType.VIDEO,
thumbURL, thumbURL,
sourceURL, sourceURL,
checked: false checked: false
@ -259,12 +260,13 @@ Page({
const list = await MomentApi.create(tempFileIds); const list = await MomentApi.create(tempFileIds);
wx.showToast({ title: "上传成功", icon: "success" }); wx.showToast({ title: "上传成功", icon: "success" });
const added = list.map((item: any) => { const added = list.map((item: any) => {
const ext = JSON.parse(item.ext) as MediaAttachExt; const metadata = (typeof item.metadata === "string" ? JSON.parse(item.metadata) : item.metadata) as PreviewImageMetadata;
const thumbURL = `${config.url}/attachment/read/${item.mongoId}`; const thumbURL = `${config.url}/attachment/read/${item.mongoId}`;
const sourceURL = `${config.url}/attachment/read/${ext.sourceMongoId}`; const sourceURL = `${config.url}/attachment/read/${metadata.sourceMongoId}`;
const isImage = item.mimeType?.startsWith("image/");
return { return {
id: item.id, id: item.id,
type: ext.isImage ? MediaItemType.IMAGE : MediaItemType.VIDEO, type: isImage ? MediaItemType.IMAGE : MediaItemType.VIDEO,
thumbURL, thumbURL,
sourceURL, sourceURL,
checked: false checked: false

View File

@ -6,7 +6,7 @@ import Events from "../../../utils/Events";
import Toolkit from "../../../utils/Toolkit"; import Toolkit from "../../../utils/Toolkit";
import { Journal, JournalPage, JournalPageType } from "../../../types/Journal"; import { Journal, JournalPage, JournalPageType } from "../../../types/Journal";
import { OrderType, } from "../../../types/Model"; import { OrderType, } from "../../../types/Model";
import { ImageMetadata, MediaAttachExt } from "../../../types/Attachment"; import { PreviewImageMetadata } from "../../../types/Attachment";
import { MediaItem, MediaItemType } from "../../../types/UI"; import { MediaItem, MediaItemType } from "../../../types/UI";
import { JournalApi } from "../../../api/JournalApi"; import { JournalApi } from "../../../api/JournalApi";
@ -92,12 +92,12 @@ Page({
} }
list.forEach(journal => { list.forEach(journal => {
const mediaItems = journal.items!.filter((item) => item.attachType === "THUMB").map((thumbItem, index) => { const mediaItems = journal.items!.filter((item) => item.attachType === "THUMB").map((thumbItem, index) => {
const metadata = thumbItem.metadata as ImageMetadata; const metadata = (typeof thumbItem.metadata === "string" ? JSON.parse(thumbItem.metadata) : thumbItem.metadata) as PreviewImageMetadata;
const ext = thumbItem.ext = JSON.parse(thumbItem.ext!.toString()) as MediaAttachExt;
const thumbURL = `${config.url}/attachment/read/${thumbItem.mongoId}`; const thumbURL = `${config.url}/attachment/read/${thumbItem.mongoId}`;
const sourceURL = `${config.url}/attachment/read/${ext.sourceMongoId}`; const sourceURL = `${config.url}/attachment/read/${metadata.sourceMongoId}`;
const isVideo = metadata.sourceMimeType?.startsWith("video/");
return { return {
type: ext.isVideo ? MediaItemType.VIDEO : MediaItemType.IMAGE, type: isVideo ? MediaItemType.VIDEO : MediaItemType.IMAGE,
thumbURL, thumbURL,
sourceURL, sourceURL,
size: thumbItem.size || 0, size: thumbItem.size || 0,

View File

@ -42,9 +42,55 @@
.header { .header {
padding: 16rpx 32rpx; padding: 16rpx 32rpx;
.left-actions {
gap: 16rpx;
display: flex;
align-items: center;
.type-picker {
.picker-button {
gap: 8rpx;
color: var(--theme-wx);
border: 1px solid var(--theme-wx);
display: flex;
padding: 14rpx 24rpx 14rpx 32rpx;
font-size: 26rpx;
background: var(--theme-bg-card);
align-items: center;
border-radius: 16rpx;
}
}
}
} }
.location { .location {
.thumb {
width: 96rpx;
height: 96rpx;
border: 1px solid var(--theme-border-light);
overflow: hidden;
background: var(--theme-bg-page);
flex-shrink: 0;
border-radius: 16rpx;
}
.thumb-img {
width: 100%;
height: 100%;
}
.thumb-placeholder {
color: var(--theme-text-secondary);
width: 100%;
height: 100%;
display: flex;
font-size: 24rpx;
background: var(--theme-bg-page);
align-items: center;
justify-content: center;
}
.note { .note {
width: 2em; width: 2em;

View File

@ -3,7 +3,13 @@
import Time from "../../../utils/Time"; import Time from "../../../utils/Time";
import { TravelApi } from "../../../api/TravelApi"; import { TravelApi } from "../../../api/TravelApi";
import { TravelLocationApi } from "../../../api/TravelLocationApi"; import { TravelLocationApi } from "../../../api/TravelLocationApi";
import { Travel, TravelStatusLabel, TravelStatusIcon, TransportationTypeLabel, TravelLocation, TravelLocationTypeLabel, TravelLocationTypeIcon } from "../../../types/Travel"; import config from "../../../config/index";
import { Travel, TravelStatusLabel, TravelStatusIcon, TransportationTypeLabel, TravelLocation, TravelLocationTypeLabel, TravelLocationTypeIcon, TransportationTypeIcon, TravelLocationType } from "../../../types/Travel";
interface TravelLocationView extends TravelLocation {
/** 预览图 */
previewThumb?: string;
}
interface TravelDetailData { interface TravelDetailData {
/** 出行详情 */ /** 出行详情 */
@ -13,7 +19,7 @@ interface TravelDetailData {
/** 是否正在加载 */ /** 是否正在加载 */
isLoading: boolean; isLoading: boolean;
/** 地点列表 */ /** 地点列表 */
locations: TravelLocation[]; locations: TravelLocationView[];
/** 是否正在加载地点 */ /** 是否正在加载地点 */
isLoadingLocations: boolean; isLoadingLocations: boolean;
/** 状态标签映射 */ /** 状态标签映射 */
@ -22,10 +28,16 @@ interface TravelDetailData {
statusIcons: typeof TravelStatusIcon; statusIcons: typeof TravelStatusIcon;
/** 交通类型标签映射 */ /** 交通类型标签映射 */
transportLabels: typeof TransportationTypeLabel; transportLabels: typeof TransportationTypeLabel;
/** 交通类型图标映射 */
transportIcons: typeof TransportationTypeIcon;
/** 地点类型标签映射 */ /** 地点类型标签映射 */
locationTypeLabels: typeof TravelLocationTypeLabel; locationTypeLabels: typeof TravelLocationTypeLabel;
/** 地点类型图标映射 */ /** 地点类型图标映射 */
locationTypeIcons: typeof TravelLocationTypeIcon; locationTypeIcons: typeof TravelLocationTypeIcon;
/** 地点类型选项 */
locationTypes: string[];
/** 选中的地点类型索引 */
selectedLocationTypeIndex: number;
/** 删除对话框可见性 */ /** 删除对话框可见性 */
deleteDialogVisible: boolean; deleteDialogVisible: boolean;
/** 删除确认文本 */ /** 删除确认文本 */
@ -42,8 +54,11 @@ Page({
statusLabels: TravelStatusLabel, statusLabels: TravelStatusLabel,
statusIcons: TravelStatusIcon, statusIcons: TravelStatusIcon,
transportLabels: TransportationTypeLabel, transportLabels: TransportationTypeLabel,
transportIcons: TransportationTypeIcon,
locationTypeLabels: TravelLocationTypeLabel, locationTypeLabels: TravelLocationTypeLabel,
locationTypeIcons: TravelLocationTypeIcon, locationTypeIcons: TravelLocationTypeIcon,
locationTypes: ["全部", ...Object.values(TravelLocationTypeLabel)],
selectedLocationTypeIndex: 0,
deleteDialogVisible: false, deleteDialogVisible: false,
deleteConfirmText: "" deleteConfirmText: ""
}, },
@ -106,15 +121,39 @@ Page({
this.setData({ isLoadingLocations: true }); this.setData({ isLoadingLocations: true });
try { try {
const { selectedLocationTypeIndex, locationTypes } = this.data;
// 构建查询条件
const equalsExample: { [key: string]: number | string } = {
travelId: Number(travelId)
};
// 添加类型过滤(索引 0 表示"全部"
if (0 < selectedLocationTypeIndex) {
const selectedTypeLabel = locationTypes[selectedLocationTypeIndex];
const selectedType = Object.keys(TravelLocationTypeLabel).find(
key => TravelLocationTypeLabel[key as TravelLocationType] === selectedTypeLabel
) as TravelLocationType | undefined;
if (selectedType) {
equalsExample.type = selectedType;
}
}
const result = await TravelLocationApi.getList({ const result = await TravelLocationApi.getList({
index: 0, index: 0,
size: 100, size: 999,
equalsExample: { equalsExample
travelId: Number(travelId)
}
}); });
this.setData({ locations: result.list }); const locations = result.list.map((location) => {
const previewItem = location.items && 0 < location.items.length ? location.items[0] : undefined;
return {
...location,
previewThumb: previewItem ? `${config.url}/attachment/read/${previewItem.mongoId}` : undefined
};
});
this.setData({ locations });
} catch (error) { } catch (error) {
console.error("获取地点列表失败:", error); console.error("获取地点列表失败:", error);
} finally { } finally {
@ -122,6 +161,14 @@ Page({
} }
}, },
/** 地点类型改变 */
onLocationTypeChange(e: WechatMiniprogram.PickerChange) {
const index = Number(e.detail.value);
this.setData({ selectedLocationTypeIndex: index });
// 重新从接口获取过滤后的数据
this.fetchLocations(this.data.travelId);
},
/** 编辑出行 */ /** 编辑出行 */
toEdit() { toEdit() {
const { travel } = this.data; const { travel } = this.data;

View File

@ -40,7 +40,7 @@
<text wx:else class="undecided-value">未定</text> <text wx:else class="undecided-value">未定</text>
</view> </view>
</t-cell> </t-cell>
<t-cell left-icon="{{travel.transportationType === 'PLANE' ? 'flight-takeoff' : travel.transportationType === 'TRAIN' ? 'map-route' : travel.transportationType === 'SELF_DRIVING' ? 'control-platform' : 'location'}}" title="交通方式"> <t-cell left-icon="{{transportIcons[travel.transportationType]}}" title="交通方式">
<view slot="note">{{transportLabels[travel.transportationType]}}</view> <view slot="note">{{transportLabels[travel.transportationType]}}</view>
</t-cell> </t-cell>
</t-cell-group> </t-cell-group>
@ -52,7 +52,15 @@
<t-cell-group class="section locations"> <t-cell-group class="section locations">
<view slot="title" class="title">地点列表</view> <view slot="title" class="title">地点列表</view>
<t-cell class="header"> <t-cell class="header">
<t-button slot="left-icon" theme="primary" icon="map" size="small" bind:tap="toMap">地图浏览</t-button> <view slot="left-icon" class="left-actions">
<t-button theme="primary" icon="map" size="small" bind:tap="toMap">地图浏览</t-button>
<picker class="type-picker" mode="selector" range="{{locationTypes}}" value="{{selectedLocationTypeIndex}}" bind:change="onLocationTypeChange">
<view class="picker-button">
<text>{{locationTypes[selectedLocationTypeIndex]}}</text>
<t-icon class="icon" name="chevron-down" size="16px" />
</view>
</picker>
</view>
<t-icon slot="right-icon" name="add" size="20px" color="var(--theme-wx)" bind:tap="toAddLocation" /> <t-icon slot="right-icon" name="add" size="20px" color="var(--theme-wx)" bind:tap="toAddLocation" />
</t-cell> </t-cell>
<t-cell wx:if="{{isLoadingLocations}}" class="loading"> <t-cell wx:if="{{isLoadingLocations}}" class="loading">
@ -63,12 +71,17 @@
class="location" class="location"
wx:for="{{locations}}" wx:for="{{locations}}"
wx:key="id" wx:key="id"
left-icon="{{locationTypeIcons[item.type]}}"
title="{{item.title || '未命名地点'}}" title="{{item.title || '未命名地点'}}"
bind:tap="toLocationDetail" bind:tap="toLocationDetail"
data-id="{{item.id}}" data-id="{{item.id}}"
arrow arrow
> >
<view slot="left-icon" class="thumb">
<image wx:if="{{item.previewThumb}}" class="thumb-img" src="{{item.previewThumb}}" mode="aspectFill" />
<view wx:else class="thumb-placeholder">
<t-icon name="{{locationTypeIcons[item.type]}}" size="28px" color="var(--theme-wx)" />
</view>
</view>
<view slot="note" class="note">{{locationTypeLabels[item.type]}}</view> <view slot="note" class="note">{{locationTypeLabels[item.type]}}</view>
<view slot="description" class="description"> <view slot="description" class="description">
<view wx:if="{{item.requireIdCard}}" class="item"> <view wx:if="{{item.requireIdCard}}" class="item">

View File

@ -55,28 +55,21 @@
.map { .map {
padding: 0; padding: 0;
}
.mini-map { .instance {
width: 100%; width: 100%;
height: 520rpx; height: 520rpx;
}
.custom-callout { .marker {
width: fit-content; width: calc(var(--title-length) * 28rpx);
max-width: 400rpx;
background: var(--theme-bg-card-secondary);
box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, .15);
border-radius: 8rpx;
.callout-content {
padding: 12rpx 20rpx;
.callout-text {
color: var(--theme-text-primary); color: var(--theme-text-primary);
padding: 8rpx;
overflow: hidden; overflow: hidden;
font-size: 28rpx; font-size: 28rpx;
background: var(--theme-bg-card);
box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, .15);
white-space: nowrap; white-space: nowrap;
border-radius: 8rpx;
text-overflow: ellipsis; text-overflow: ellipsis;
} }
} }
@ -91,11 +84,10 @@
grid-template-columns: repeat(3, 1fr); grid-template-columns: repeat(3, 1fr);
.media-item { .media-item {
width: 100%; width: 220rpx;
height: 200rpx; height: 220rpx;
overflow: hidden; overflow: hidden;
position: relative; position: relative;
border-radius: 12rpx;
.thumbnail { .thumbnail {
width: 100%; width: 100%;

View File

@ -3,7 +3,7 @@
import config from "../../../config/index"; import config from "../../../config/index";
import { TravelLocationApi } from "../../../api/TravelLocationApi"; import { TravelLocationApi } from "../../../api/TravelLocationApi";
import { TravelLocation, TravelLocationTypeIcon, TravelLocationTypeLabel } from "../../../types/Travel"; import { TravelLocation, TravelLocationTypeIcon, TravelLocationTypeLabel } from "../../../types/Travel";
import { MediaAttachExt, MediaAttachType } from "../../../types/Attachment"; import { MediaAttachType, PreviewImageMetadata } from "../../../types/Attachment";
import { MapMarker, MediaItem, MediaItemType } from "../../../types/UI"; import { MapMarker, MediaItem, MediaItemType } from "../../../types/UI";
import Toolkit from "../../../utils/Toolkit"; import Toolkit from "../../../utils/Toolkit";
@ -95,18 +95,19 @@ Page({
thumbItems.forEach((thumbItem) => { thumbItems.forEach((thumbItem) => {
try { try {
const ext = JSON.parse(thumbItem.ext!.toString()) as MediaAttachExt; const metadata = (typeof thumbItem.metadata === "string" ? JSON.parse(thumbItem.metadata) : thumbItem.metadata) as PreviewImageMetadata;
const thumbURL = `${config.url}/attachment/read/${thumbItem.mongoId}`; const thumbURL = `${config.url}/attachment/read/${thumbItem.mongoId}`;
const sourceURL = `${config.url}/attachment/read/${ext.sourceMongoId}`; const sourceURL = `${config.url}/attachment/read/${metadata.sourceMongoId}`;
const isVideo = metadata.sourceMimeType?.startsWith("video/");
mediaItems.push({ mediaItems.push({
type: ext.isVideo ? MediaItemType.VIDEO : MediaItemType.IMAGE, type: isVideo ? MediaItemType.VIDEO : MediaItemType.IMAGE,
thumbURL, thumbURL,
sourceURL, sourceURL,
size: thumbItem.size || 0, size: thumbItem.size || 0,
attachmentId: thumbItem.id! attachmentId: thumbItem.id!
}); });
} catch (parseError) { } catch (parseError) {
console.warn("解析附件扩展信息失败", parseError); console.warn("解析附件元数据失败", parseError);
} }
}); });
@ -120,15 +121,12 @@ Page({
width: 24, width: 24,
height: 30, height: 30,
customCallout: { customCallout: {
anchorY: 0, anchorY: -2,
anchorX: 0, anchorX: 0,
display: "ALWAYS" display: "ALWAYS"
} }
}); });
} }
console.log(mediaItems);
this.setData({ this.setData({
location: { location: {
...location, ...location,

View File

@ -35,7 +35,7 @@
<t-cell class="map"> <t-cell class="map">
<map <map
slot="description" slot="description"
class="mini-map" class="instance"
latitude="{{location.lat}}" latitude="{{location.lat}}"
longitude="{{location.lng}}" longitude="{{location.lng}}"
markers="{{mapMarkers}}" markers="{{mapMarkers}}"
@ -43,10 +43,8 @@
show-location show-location
> >
<cover-view slot="callout"> <cover-view slot="callout">
<cover-view class="custom-callout" marker-id="0"> <cover-view class="marker" marker-id="0" style="--title-length: {{location.title.length}}">
<cover-view class="callout-content"> {{location.title || '地点'}}
<cover-view class="callout-text">{{location.title || '地点'}}</cover-view>
</cover-view>
</cover-view> </cover-view>
</cover-view> </cover-view>
</map> </map>
@ -92,9 +90,9 @@
</t-cell-group> </t-cell-group>
<!-- 照片/视频 --> <!-- 照片/视频 -->
<t-cell-group wx:if="{{location.mediaItems && 0 < location.mediaItems.length}}" class="section media"> <t-cell-group wx:if="{{location.mediaItems && 0 < location.mediaItems.length}}" class="section media">
<view slot="title" class="title">照片/视频</view> <view slot="title" class="title">照片视频</view>
<t-cell> <t-cell>
<view class="media-grid"> <view slot="title" class="media-grid">
<view <view
wx:for="{{location.mediaItems}}" wx:for="{{location.mediaItems}}"
wx:key="attachmentId" wx:key="attachmentId"

View File

@ -7,13 +7,11 @@
"t-input": "tdesign-miniprogram/input/input", "t-input": "tdesign-miniprogram/input/input",
"t-button": "tdesign-miniprogram/button/button", "t-button": "tdesign-miniprogram/button/button",
"t-dialog": "tdesign-miniprogram/dialog/dialog", "t-dialog": "tdesign-miniprogram/dialog/dialog",
"t-picker": "tdesign-miniprogram/picker/picker",
"t-navbar": "tdesign-miniprogram/navbar/navbar", "t-navbar": "tdesign-miniprogram/navbar/navbar",
"t-loading": "tdesign-miniprogram/loading/loading", "t-loading": "tdesign-miniprogram/loading/loading",
"t-stepper": "tdesign-miniprogram/stepper/stepper", "t-stepper": "tdesign-miniprogram/stepper/stepper",
"t-textarea": "tdesign-miniprogram/textarea/textarea", "t-textarea": "tdesign-miniprogram/textarea/textarea",
"t-cell-group": "tdesign-miniprogram/cell-group/cell-group", "t-cell-group": "tdesign-miniprogram/cell-group/cell-group"
"t-picker-item": "tdesign-miniprogram/picker-item/picker-item"
}, },
"styleIsolation": "shared" "styleIsolation": "shared"
} }

View File

@ -23,6 +23,34 @@
.section { .section {
margin-top: 48rpx; margin-top: 48rpx;
.rate-cell {
&.decided {
.rate {
display: block;
gap: 16rpx;
display: flex;
align-items: center;
}
.text {
display: none;
}
}
&.undecided {
.rate {
display: none;
}
.text {
display: block;
}
}
}
> .title { > .title {
color: var(--theme-text-secondary); color: var(--theme-text-secondary);
padding: 0 32rpx; padding: 0 32rpx;
@ -53,8 +81,8 @@
grid-template-columns: repeat(3, 1fr); grid-template-columns: repeat(3, 1fr);
.item { .item {
width: 240rpx; width: 220rpx;
height: 240rpx; height: 220rpx;
position: relative; position: relative;
overflow: hidden; overflow: hidden;
background: var(--theme-bg-card); background: var(--theme-bg-card);
@ -149,11 +177,11 @@
display: flex; display: flex;
padding: 24rpx 16rpx 48rpx 16rpx; padding: 24rpx 16rpx 48rpx 16rpx;
.delete-btn { .delete {
flex: .6; flex: .6;
} }
.submit-btn { .submit {
flex: 1; flex: 1;
} }
} }

View File

@ -2,8 +2,8 @@
import { Network, WechatMediaItem } from "../../../utils/Network"; import { Network, WechatMediaItem } from "../../../utils/Network";
import { TravelLocationApi } from "../../../api/TravelLocationApi"; import { TravelLocationApi } from "../../../api/TravelLocationApi";
import { TravelLocationType } from "../../../types/Travel"; import { TravelLocationType, TravelLocationTypeLabel } from "../../../types/Travel";
import { MediaAttachExt, MediaAttachType } from "../../../types/Attachment"; import { MediaAttachType, PreviewImageMetadata } from "../../../types/Attachment";
import config from "../../../config/index"; import config from "../../../config/index";
import { MediaItem, MediaItemType } from "../../../types/UI"; import { MediaItem, MediaItemType } from "../../../types/UI";
@ -34,8 +34,12 @@ interface TravelLocationEditorData {
requireAppointment: boolean; requireAppointment: boolean;
/** 评分 */ /** 评分 */
score: number; score: number;
/** 评分是否未定 */
scoreUndecided: boolean;
/** 重要程度 */ /** 重要程度 */
importance: number; importance: number;
/** 重要程度是否未定 */
importanceUndecided: boolean;
/** 媒体列表(创建和编辑模式使用) */ /** 媒体列表(创建和编辑模式使用) */
mediaList: (MediaItem | WechatMediaItem)[]; mediaList: (MediaItem | WechatMediaItem)[];
/** 新媒体列表(编辑模式使用) */ /** 新媒体列表(编辑模式使用) */
@ -67,7 +71,7 @@ Page({
mode: "create", mode: "create",
id: undefined, id: undefined,
travelId: 0, travelId: 0,
type: TravelLocationType.ATTRACTION, type: TravelLocationType.FOOD,
title: "", title: "",
description: "", description: "",
location: "", location: "",
@ -77,7 +81,9 @@ Page({
requireIdCard: false, requireIdCard: false,
requireAppointment: false, requireAppointment: false,
score: 3, score: 3,
scoreUndecided: true,
importance: 1, importance: 1,
importanceUndecided: true,
mediaList: [], mediaList: [],
newMediaList: [], newMediaList: [],
isLoading: false, isLoading: false,
@ -87,7 +93,7 @@ Page({
mediaItemTypeEnum: { mediaItemTypeEnum: {
...MediaItemType ...MediaItemType
}, },
locationTypes: ["美食", "酒店", "交通", "景点", "购物", "玩乐", "生活"], locationTypes: Object.values(TravelLocationTypeLabel),
locationTypeValues: [ locationTypeValues: [
TravelLocationType.FOOD, TravelLocationType.FOOD,
TravelLocationType.HOTEL, TravelLocationType.HOTEL,
@ -155,11 +161,12 @@ Page({
const thumbItems = items.filter((item) => item.attachType === MediaAttachType.THUMB); const thumbItems = items.filter((item) => item.attachType === MediaAttachType.THUMB);
const mediaList: MediaItem[] = thumbItems.map((thumbItem) => { const mediaList: MediaItem[] = thumbItems.map((thumbItem) => {
const ext = thumbItem.ext = JSON.parse(thumbItem.ext!.toString()) as MediaAttachExt; const metadata = (typeof thumbItem.metadata === "string" ? JSON.parse(thumbItem.metadata) : thumbItem.metadata) as PreviewImageMetadata;
const thumbURL = `${config.url}/attachment/read/${thumbItem.mongoId}`; const thumbURL = `${config.url}/attachment/read/${thumbItem.mongoId}`;
const sourceURL = `${config.url}/attachment/read/${ext.sourceMongoId}`; const sourceURL = `${config.url}/attachment/read/${metadata.sourceMongoId}`;
const isVideo = metadata.sourceMimeType?.startsWith("video/");
return { return {
type: ext.isVideo ? MediaItemType.VIDEO : MediaItemType.IMAGE, type: isVideo ? MediaItemType.VIDEO : MediaItemType.IMAGE,
thumbURL, thumbURL,
sourceURL, sourceURL,
size: thumbItem.size || 0, size: thumbItem.size || 0,
@ -167,6 +174,14 @@ Page({
} as MediaItem; } as MediaItem;
}); });
// 判断评分是否未定
const scoreUndecided = location.score === null || location.score === undefined;
const score = location.score !== null && location.score !== undefined ? location.score : 3;
// 判断重要程度是否未定
const importanceUndecided = location.importance === null || location.importance === undefined;
const importance = location.importance !== null && location.importance !== undefined ? location.importance : 1;
this.setData({ this.setData({
type, type,
locationTypeIndex: locationTypeIndex >= 0 ? locationTypeIndex : 0, locationTypeIndex: locationTypeIndex >= 0 ? locationTypeIndex : 0,
@ -178,8 +193,10 @@ Page({
amount: location.amount || 0, amount: location.amount || 0,
requireIdCard: location.requireIdCard || false, requireIdCard: location.requireIdCard || false,
requireAppointment: location.requireAppointment || false, requireAppointment: location.requireAppointment || false,
score: location.score !== undefined ? location.score : 3, score,
importance: location.importance !== undefined ? location.importance : 1, scoreUndecided,
importance,
importanceUndecided,
mediaList, mediaList,
isLoading: false isLoading: false
}); });
@ -225,6 +242,32 @@ Page({
this.setData({ importance: e.detail.value }); this.setData({ importance: e.detail.value });
}, },
/** 清除评分 */
clearScore() {
this.setData({ scoreUndecided: true });
},
/** 清除重要程度 */
clearImportance() {
this.setData({ importanceUndecided: true });
},
/** 点击未定文字选择评分 */
selectScore() {
this.setData({
scoreUndecided: false,
score: 3
});
},
/** 点击未定文字选择重要程度 */
selectImportance() {
this.setData({
importanceUndecided: false,
importance: 1
});
},
/** 选择位置 */ /** 选择位置 */
chooseLocation() { chooseLocation() {
wx.chooseLocation({ wx.chooseLocation({
@ -483,8 +526,8 @@ Page({
amount: this.data.amount, amount: this.data.amount,
requireIdCard: this.data.requireIdCard, requireIdCard: this.data.requireIdCard,
requireAppointment: this.data.requireAppointment, requireAppointment: this.data.requireAppointment,
score: this.data.score, score: this.data.scoreUndecided ? null : this.data.score,
importance: this.data.importance, importance: this.data.importanceUndecided ? null : this.data.importance,
tempFileIds tempFileIds
}); });
wx.showToast({ wx.showToast({
@ -539,8 +582,8 @@ Page({
amount: this.data.amount, amount: this.data.amount,
requireIdCard: this.data.requireIdCard, requireIdCard: this.data.requireIdCard,
requireAppointment: this.data.requireAppointment, requireAppointment: this.data.requireAppointment,
score: this.data.score, score: this.data.scoreUndecided ? null : this.data.score,
importance: this.data.importance, importance: this.data.importanceUndecided ? null : this.data.importance,
attachmentIds, attachmentIds,
tempFileIds tempFileIds
}); });

View File

@ -61,29 +61,53 @@
<switch checked="{{requireAppointment}}" bindchange="onChangeRequireAppointment" /> <switch checked="{{requireAppointment}}" bindchange="onChangeRequireAppointment" />
</view> </view>
</t-cell> </t-cell>
<t-cell title="重要程度"> <t-cell title="重要程度" class="rate-cell importance {{importanceUndecided ? 'undecided' : 'decided'}}">
<view slot="right-icon"> <view slot="note">
<view class="rate">
<t-rate <t-rate
value="{{importance}}" value="{{importance}}"
count="{{5}}" count="{{5}}"
size="24px" size="20px"
bind:change="onChangeImportance" bind:change="onChangeImportance"
/> />
<t-icon
name="close-circle-filled"
size="20px"
class="clear-icon"
bindtap="clearImportance"
/>
</view>
<view class="text" bindtap="selectImportance">
未定
</view>
</view> </view>
</t-cell> </t-cell>
<t-cell title="评分"> <t-cell title="评分" class="rate-cell score {{scoreUndecided ? 'undecided' : 'decided'}}">
<view slot="right-icon"> <view slot="note">
<view class="rate">
<t-rate <t-rate
value="{{score}}" value="{{score}}"
count="{{5}}" count="{{5}}"
size="24px" size="20px"
bind:change="onChangeScore" bind:change="onChangeScore"
/> />
<t-icon
name="close-circle-filled"
size="20px"
class="clear-icon"
bindtap="clearScore"
/>
</view>
<view class="text" bindtap="selectScore">
未定
</view>
</view> </view>
</t-cell> </t-cell>
</t-cell-group> </t-cell-group>
<view class="section media"> <t-cell-group class="section media">
<view class="gallery"> <view slot="title" class="title">图片视频</view>
<t-cell>
<view slot="title" class="gallery">
<!-- 创建模式mediaList 显示新选择的媒体 --> <!-- 创建模式mediaList 显示新选择的媒体 -->
<block wx:if="{{mode === 'create'}}"> <block wx:if="{{mode === 'create'}}">
<block wx:for="{{mediaList}}" wx:key="index"> <block wx:for="{{mediaList}}" wx:key="index">
@ -207,7 +231,8 @@
<t-icon name="add" /> <t-icon name="add" />
</t-button> </t-button>
</view> </view>
</view> </t-cell>
</t-cell-group>
<!-- 上传进度提示 --> <!-- 上传进度提示 -->
<view wx:if="{{isUploading}}" class="section upload"> <view wx:if="{{isUploading}}" class="section upload">

View File

@ -9,20 +9,17 @@
width: 100%; width: 100%;
height: 100%; height: 100%;
.custom-callout { .marker {
width: fit-content; width: fit-content;
padding: 12rpx 16rpx;
display: flex; display: flex;
min-width: 300rpx; background: var(--theme-bg-card);
max-width: 400rpx;
background: var(--theme-bg-card-secondary);
box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, .15); box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, .15);
border-radius: 6rpx; border-radius: 6rpx;
flex-direction: column; flex-direction: column;
.location-item { .location {
display: flex; display: flex;
padding: 6rpx 0; padding: 8rpx 16rpx 8rpx 8rpx;
align-items: center; align-items: center;
.type { .type {
@ -37,6 +34,7 @@
.title { .title {
flex: 1; flex: 1;
width: calc(var(--title-length) * 28rpx);
color: var(--theme-text-primary, #333); color: var(--theme-text-primary, #333);
overflow: hidden; overflow: hidden;
font-size: 28rpx; font-size: 28rpx;

View File

@ -2,6 +2,7 @@
import { TravelLocationApi } from "../../../api/TravelLocationApi"; import { TravelLocationApi } from "../../../api/TravelLocationApi";
import { TravelLocation, TravelLocationTypeLabel } from "../../../types/Travel"; import { TravelLocation, TravelLocationTypeLabel } from "../../../types/Travel";
import Toolkit from "../../../utils/Toolkit";
interface MapMarker { interface MapMarker {
id: number; id: number;
@ -110,7 +111,8 @@ Page({
height: 30, height: 30,
customCallout: { customCallout: {
anchorY: -2, anchorY: -2,
anchorX: 0, // 随机错位避免近距离重叠
anchorX: Toolkit.random(-10, 10),
display: "ALWAYS" display: "ALWAYS"
}, },
locations: locs locations: locs

View File

@ -13,16 +13,25 @@
bindcallouttap="onCalloutTap" bindcallouttap="onCalloutTap"
> >
<cover-view slot="callout"> <cover-view slot="callout">
<block wx:for="{{markers}}" wx:key="id" wx:for-index="markerIndex"> <cover-view
<cover-view class="custom-callout" marker-id="{{markerIndex}}"> class="marker"
<block wx:for="{{item.locations}}" wx:key="id" wx:for-item="location"> wx:for="{{markers}}"
<cover-view class="location-item"> wx:key="id"
wx:for-item="marker"
wx:for-index="markerIndex"
marker-id="{{markerIndex}}"
>
<cover-view
class="location"
wx:for="{{marker.locations}}"
wx:key="id"
wx:for-item="location"
style="{{'--title-length: ' + location.title.length}}"
>
<cover-view wx:if="{{location.typeLabel}}" class="type">{{location.typeLabel}}</cover-view> <cover-view wx:if="{{location.typeLabel}}" class="type">{{location.typeLabel}}</cover-view>
<cover-view class="title">{{location.title || '未命名地点'}}</cover-view> <cover-view class="title">{{location.title || '未命名地点'}}</cover-view>
</cover-view> </cover-view>
</block>
</cover-view> </cover-view>
</block>
</cover-view> </cover-view>
</map> </map>
<view wx:if="{{isLoading}}" class="loading"> <view wx:if="{{isLoading}}" class="loading">

View File

@ -3,7 +3,6 @@
import Time from "../../../utils/Time"; import Time from "../../../utils/Time";
import { TravelApi } from "../../../api/TravelApi"; import { TravelApi } from "../../../api/TravelApi";
import { Travel, TravelPage, TravelStatus, TravelStatusLabel, TravelStatusIcon, TransportationTypeLabel, TransportationTypeIcon } from "../../../types/Travel"; import { Travel, TravelPage, TravelStatus, TravelStatusLabel, TravelStatusIcon, TransportationTypeLabel, TransportationTypeIcon } from "../../../types/Travel";
import { OrderType } from "../../../types/Model";
interface TravelData { interface TravelData {
/** 分页参数 */ /** 分页参数 */
@ -35,10 +34,7 @@ Page({
data: <TravelData>{ data: <TravelData>{
page: { page: {
index: 0, index: 0,
size: 10, size: 10
orderMap: {
travelAt: OrderType.DESC
}
}, },
list: [], list: [],
currentStatus: "ALL", currentStatus: "ALL",
@ -79,9 +75,6 @@ Page({
page: { page: {
index: 0, index: 0,
size: 10, size: 10,
orderMap: {
travelAt: OrderType.DESC
},
equalsExample: this.data.currentStatus === "ALL" ? undefined : { equalsExample: this.data.currentStatus === "ALL" ? undefined : {
status: this.data.currentStatus as TravelStatus status: this.data.currentStatus as TravelStatus
} }

View File

@ -68,9 +68,9 @@ page {
--theme-bg-wx: #111; --theme-bg-wx: #111;
--theme-bg-primary: #1A1A1A; --theme-bg-primary: #1A1A1A;
--theme-bg-secondary: #2A2A2A; --theme-bg-secondary: #2A2A2A;
--theme-bg-card: #2C2C2C; --theme-bg-card: #3D3D3D;
--theme-bg-card-secondary: #404040; --theme-bg-card-secondary: #525252;
--theme-bg-journal: #404040; --theme-bg-journal: #4B4B4B;
--theme-bg-overlay: rgba(0, 0, 0, .3); --theme-bg-overlay: rgba(0, 0, 0, .3);
--theme-bg-menu: rgba(40, 40, 40, .95); --theme-bg-menu: rgba(40, 40, 40, .95);

View File

@ -17,7 +17,7 @@ export type Attachment = {
mimeType?: string; mimeType?: string;
metadata?: string | ImageMetadata; metadata?: string | ImageMetadata | PreviewImageMetadata;
/** 文件 MD5 */ /** 文件 MD5 */
md5: string; md5: string;
@ -27,9 +27,6 @@ export type Attachment = {
/** 大小 */ /** 大小 */
size: number; size: number;
/** 扩展数据 */
ext?: string | MediaAttachExt;
} & Model; } & Model;
/** 媒体附件类型 */ /** 媒体附件类型 */
@ -52,8 +49,7 @@ export type ImageMetadata = {
height: number; height: number;
} }
/** 媒体附件扩展数据 */ export type PreviewImageMetadata = {
export type MediaAttachExt = {
/** 原文件附件 ID */ /** 原文件附件 ID */
sourceId: number; sourceId: number;
@ -61,15 +57,6 @@ export type MediaAttachExt = {
/** 原文件访问 mongoId */ /** 原文件访问 mongoId */
sourceMongoId: string; sourceMongoId: string;
/** true 为图片 */ /** 原文件 MimeType */
isImage: boolean; sourceMimeType: string;
} & ImageMetadata;
/** true 为视频 */
isVideo: boolean;
/** 原图宽度(像素) */
width?: number;
/** 原图高度(像素) */
height?: number;
}

View File

@ -94,9 +94,10 @@ export enum TravelLocationType {
HOTEL = "HOTEL", HOTEL = "HOTEL",
TRANSPORT = "TRANSPORT", TRANSPORT = "TRANSPORT",
ATTRACTION = "ATTRACTION", ATTRACTION = "ATTRACTION",
MALL = "MALL",
SHOPPING = "SHOPPING", SHOPPING = "SHOPPING",
PLAY = "PLAY", PLAY = "PLAY",
LIFE = "LEFE" LIFE = "LIFE"
} }
/** 地点类型中文映射 */ /** 地点类型中文映射 */
@ -105,6 +106,7 @@ export const TravelLocationTypeLabel: Record<TravelLocationType, string> = {
[TravelLocationType.HOTEL]: "酒店", [TravelLocationType.HOTEL]: "酒店",
[TravelLocationType.TRANSPORT]: "交通", [TravelLocationType.TRANSPORT]: "交通",
[TravelLocationType.ATTRACTION]: "景点", [TravelLocationType.ATTRACTION]: "景点",
[TravelLocationType.MALL]: "商场",
[TravelLocationType.SHOPPING]: "购物", [TravelLocationType.SHOPPING]: "购物",
[TravelLocationType.PLAY]: "玩乐", [TravelLocationType.PLAY]: "玩乐",
[TravelLocationType.LIFE]: "生活" [TravelLocationType.LIFE]: "生活"
@ -116,9 +118,10 @@ export const TravelLocationTypeIcon: Record<TravelLocationType, string> = {
[TravelLocationType.HOTEL]: "city-8", [TravelLocationType.HOTEL]: "city-8",
[TravelLocationType.TRANSPORT]: "map-route-planning", [TravelLocationType.TRANSPORT]: "map-route-planning",
[TravelLocationType.ATTRACTION]: "image-1", [TravelLocationType.ATTRACTION]: "image-1",
[TravelLocationType.MALL]: "chart-3d",
[TravelLocationType.SHOPPING]: "shop", [TravelLocationType.SHOPPING]: "shop",
[TravelLocationType.PLAY]: "ferris-wheel", [TravelLocationType.PLAY]: "ferris-wheel",
[TravelLocationType.LIFE]: "cart" [TravelLocationType.LIFE]: "location"
}; };
/** 出行地点实体 */ /** 出行地点实体 */
@ -154,10 +157,10 @@ export interface TravelLocation extends Model {
requireAppointment?: boolean; requireAppointment?: boolean;
/** 评分 */ /** 评分 */
score?: number; score?: number | null;
/** 重要程度 */ /** 重要程度 */
importance?: number; importance?: number | null;
/** 附件 */ /** 附件 */
items?: Attachment[]; items?: Attachment[];