fix travel style

This commit is contained in:
Timi
2025-12-16 16:57:49 +08:00
parent e650571563
commit f0f2815971
32 changed files with 1238 additions and 594 deletions

1
.gitignore vendored
View File

@ -62,4 +62,5 @@ ehthumbs.db
.claude/ .claude/
CLAUDE.md CLAUDE.md
AGENTS.md
/docs /docs

View File

@ -8,9 +8,10 @@
"pages/main/journal-date/index", "pages/main/journal-date/index",
"pages/main/portfolio/index", "pages/main/portfolio/index",
"pages/main/travel/index", "pages/main/travel/index",
"pages/main/travel-location-map/index",
"pages/main/travel-detail/index", "pages/main/travel-detail/index",
"pages/main/travel-editor/index", "pages/main/travel-editor/index",
"pages/main/travel-location-detail/index",
"pages/main/travel-location-map/index",
"pages/main/travel-location-editor/index", "pages/main/travel-location-editor/index",
"pages/main/about/index", "pages/main/about/index",
"pages/main/moment/index" "pages/main/moment/index"
@ -19,8 +20,8 @@
"themeLocation": "theme.json", "themeLocation": "theme.json",
"window": { "window": {
"navigationStyle": "custom", "navigationStyle": "custom",
"navigationBarTextStyle": "black", "navigationBarTextStyle": "@navigationBarTextStyle",
"navigationBarBackgroundColor": "#FFFFFF" "navigationBarBackgroundColor": "@navigationBarBackgroundColor"
}, },
"lazyCodeLoading": "requiredComponents", "lazyCodeLoading": "requiredComponents",
"tabBar": { "tabBar": {
@ -70,4 +71,4 @@
"getLocation", "getLocation",
"chooseLocation" "chooseLocation"
] ]
} }

17
miniprogram/app.less Normal file
View File

@ -0,0 +1,17 @@
/**app.wxss**/
@import "./timi-web.less";
@import "./tdesign.less";
@import "./theme.less";
.setting-bg {
background: var(--theme-bg-wx);
&::before {
content: "";
width: 100vw;
height: 100vh;
z-index: -1;
position: fixed;
background: var(--theme-bg-wx);
}
}

View File

@ -1,3 +0,0 @@
/**app.wxss**/
@import "./tdesign.wxss";
@import "./theme.wxss";

View File

@ -40,9 +40,18 @@
<t-tag wx:if="{{item.score}}" theme="success" variant="outline"> <t-tag wx:if="{{item.score}}" theme="success" variant="outline">
评分 {{item.score}} 评分 {{item.score}}
</t-tag> </t-tag>
<t-tag wx:if="{{item.importance}}" theme="success" variant="outline">
重要性 {{item.importance}}
</t-tag>
<t-tag wx:if="{{item.travelCount > 0}}" theme="default" variant="outline">
已出行 {{item.travelCount}} 次
</t-tag>
<t-tag wx:if="{{item.requireIdCard}}" theme="danger" variant="outline"> <t-tag wx:if="{{item.requireIdCard}}" theme="danger" variant="outline">
需要身份证 需要身份证
</t-tag> </t-tag>
<t-tag wx:if="{{item.requireAppointment}}" theme="danger" variant="outline">
需要预约
</t-tag>
</view> </view>
<view wx:if="{{item.description}}" class="description">{{item.description}}</view> <view wx:if="{{item.description}}" class="description">{{item.description}}</view>
<view wx:if="{{item.columnedItems && item.columnedItems.length > 0}}" class="items"> <view wx:if="{{item.columnedItems && item.columnedItems.length > 0}}" class="items">

View File

@ -1,7 +1,7 @@
/* pages/info/info.less */ /* pages/info/info.less */
page { page {
height: 100vh;
display: flex; display: flex;
background: var(--theme-bg-wx);
flex-direction: column; flex-direction: column;
} }

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.0</text> <text>1.6.1</text>
</view> </view>
<view class="item copyright"> <view class="item copyright">
<text>{{copyright}}</text> <text>{{copyright}}</text>

View File

@ -59,6 +59,10 @@
margin: 0; margin: 0;
font-size: 80rpx; font-size: 80rpx;
background: transparent; background: transparent;
&::after {
border-radius: 0;
}
} }
.thumbnail { .thumbnail {

View File

@ -238,10 +238,6 @@
<text style="color: var(--theme-error)">确认删除</text> <text style="color: var(--theme-error)">确认删除</text>
<text>" 以继续</text> <text>" 以继续</text>
</view> </view>
<t-input <t-input model:value="{{deleteConfirmText}}" placeholder="请输入:确认删除" />
class="confirm-input"
model:value="{{deleteConfirmText}}"
placeholder="请输入:确认删除"
/>
</view> </view>
</t-dialog> </t-dialog>

View File

@ -24,7 +24,7 @@
sticky-offset="{{stickyOffset}}" sticky-offset="{{stickyOffset}}"
> >
<view class="content" wx:for="{{list}}" wx:for-item="journal" wx:for-index="journalIndex" wx:key="index"> <view class="content" wx:for="{{list}}" wx:for-item="journal" wx:for-index="journalIndex" wx:key="index">
<t-indexes-anchor class="date" index="{{journal.date}}" /> <t-indexes-anchor class="date" index="{{journal.datetime}}" />
<view wx:if="{{journal.idea || journal.location}}" class="text"> <view wx:if="{{journal.idea || journal.location}}" class="text">
<text class="idea">{{journal.idea}}</text> <text class="idea">{{journal.idea}}</text>
<view <view

View File

@ -2,12 +2,14 @@
"component": true, "component": true,
"usingComponents": { "usingComponents": {
"t-tag": "tdesign-miniprogram/tag/tag", "t-tag": "tdesign-miniprogram/tag/tag",
"t-cell": "tdesign-miniprogram/cell/cell",
"t-icon": "tdesign-miniprogram/icon/icon", "t-icon": "tdesign-miniprogram/icon/icon",
"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-navbar": "tdesign-miniprogram/navbar/navbar", "t-navbar": "tdesign-miniprogram/navbar/navbar",
"t-loading": "tdesign-miniprogram/loading/loading" "t-loading": "tdesign-miniprogram/loading/loading",
"t-cell-group": "tdesign-miniprogram/cell-group/cell-group"
}, },
"styleIsolation": "shared" "styleIsolation": "shared"
} }

View File

@ -1,308 +1,104 @@
// pages/main/travel-detail/index.less // pages/main/travel-detail/index.less
.detail-container { .travel-detail {
width: 100vw; width: 100vw;
min-height: 100vh;
box-sizing: border-box; box-sizing: border-box;
background: var(--theme-bg-page); background: var(--theme-bg-page);
.content { .content {
gap: 24rpx;
display: flex; display: flex;
padding-top: 48rpx; padding-top: 48rpx;
flex-direction: column; flex-direction: column;
.status-section { .section {
display: flex; margin-top: 32rpx;
padding: 16rpx 0;
justify-content: center;
}
.title-section { > .title {
padding: 24rpx; color: var(--theme-text-secondary);
text-align: center; padding: 0 32rpx;
background: var(--theme-bg-card); font-size: 28rpx;
box-shadow: 0 2px 12px var(--theme-shadow-light); line-height: 64rpx;
}
.title { &.status {
display: flex;
margin-top: 24rpx;
justify-content: center;
}
&.title {
color: var(--theme-text-primary); color: var(--theme-text-primary);
padding: 24rpx;
font-size: 40rpx; font-size: 40rpx;
text-align: center;
margin-top: 24rpx;
background: var(--theme-bg-card);
box-shadow: 0 2px 12px var(--theme-shadow-light);
font-weight: bold; font-weight: bold;
line-height: 1.5; line-height: 1.5;
} }
}
.info-card, &.locations {
.content-card,
.locations-card {
overflow: hidden;
background: var(--theme-bg-card);
box-shadow: 0 2px 12px var(--theme-shadow-light);
.card-title { .action {
display: flex; gap: 32rpx;
padding: 24rpx;
align-items: center;
justify-content: space-between;
border-bottom: 1px solid var(--theme-border-light);
.title-left {
gap: 12rpx;
display: flex; display: flex;
align-items: center;
.icon {
color: var(--theme-primary);
}
.text {
color: var(--theme-text-primary);
font-size: 32rpx;
font-weight: bold;
}
} }
.title-right { .location {
gap: 16rpx;
display: flex;
align-items: center;
.icon-btn { .note {
width: 2em;
}
.description {
column-gap: 24rpx;
color: var(--theme-text-secondary);
display: flex; display: flex;
padding: 8rpx; flex-wrap: wrap;
align-items: center; font-size: 26rpx;
justify-content: center;
transition: all .2s;
&:active { .item {
transform: scale(1.1); gap: 12rpx;
} width: fit-content;
}
}
}
.info-list {
padding: 24rpx;
.info-item {
display: flex;
padding: 20rpx 0;
align-items: center;
justify-content: space-between;
border-bottom: 1px solid var(--theme-border-light);
&:last-child {
border-bottom: none;
}
.label {
gap: 12rpx;
flex: 1;
display: flex;
align-items: center;
.icon {
color: var(--theme-text-secondary);
}
.text {
color: var(--theme-text-secondary);
font-size: 28rpx;
}
}
.value {
color: var(--theme-text-primary);
font-size: 28rpx;
font-weight: 500;
}
}
}
.content-text {
color: var(--theme-text-primary);
padding: 24rpx;
font-size: 28rpx;
line-height: 1.8;
white-space: pre-wrap;
word-break: break-all;
}
.loading-container {
display: flex;
padding: 48rpx;
align-items: center;
justify-content: center;
}
.locations-list {
padding: 24rpx;
.location-item {
gap: 24rpx;
display: flex;
padding: 24rpx;
margin-bottom: 20rpx;
border-radius: 12rpx;
background: var(--theme-bg-page);
border: 1px solid var(--theme-border-light);
transition: all .3s;
&:last-child {
margin-bottom: 0;
}
&:active {
transform: scale(.98);
background: var(--theme-bg-card);
}
.location-icon {
display: flex;
flex-shrink: 0;
align-items: center;
justify-content: center;
}
.location-content {
gap: 16rpx;
flex: 1;
display: flex;
overflow: hidden;
flex-direction: column;
.location-header {
display: flex; display: flex;
padding: 2rpx 4rpx;
align-items: center; align-items: center;
justify-content: space-between; background: var(--theme-bg-page);
.location-title { .stars {
flex: 1;
color: var(--theme-text-primary);
overflow: hidden;
font-size: 30rpx;
font-weight: bold;
text-overflow: ellipsis;
white-space: nowrap;
}
}
.location-description {
color: var(--theme-text-secondary);
font-size: 26rpx;
line-height: 1.6;
word-break: break-all;
}
.location-info {
gap: 24rpx;
display: flex;
flex-wrap: wrap;
.info-item {
gap: 8rpx; gap: 8rpx;
display: flex; display: flex;
align-items: center;
color: var(--theme-text-secondary);
font-size: 24rpx;
} }
} }
} }
.location-arrow {
display: flex;
flex-shrink: 0;
align-items: center;
justify-content: center;
}
} }
} }
.empty-state { &.action {
gap: 16rpx; gap: 24rpx;
display: flex; display: flex;
padding: 64rpx 24rpx; padding: 24rpx 16rpx 128rpx 16rpx;
align-items: center;
flex-direction: column;
justify-content: center;
.empty-text { .edit {
color: var(--theme-text-secondary); flex: 2;
font-size: 26rpx;
}
}
}
.time-card {
gap: 16rpx;
display: flex;
padding: 24rpx;
flex-direction: column;
background: var(--theme-bg-card);
box-shadow: 0 2px 12px var(--theme-shadow-light);
.time-item {
display: flex;
align-items: center;
justify-content: space-between;
.label {
color: var(--theme-text-secondary);
font-size: 24rpx;
} }
.value { .delete {
color: var(--theme-text-secondary); flex: 1;
font-size: 24rpx;
} }
} }
} }
.action-section {
gap: 24rpx;
display: flex;
padding: 24rpx 16rpx 48rpx 16rpx;
.edit-btn {
flex: 2;
}
.delete-btn {
flex: 1;
}
}
} }
} }
.delete-dialog-content { .delete-dialog {
gap: 32rpx; padding: 16rpx 0;
display: flex;
flex-direction: column;
.delete-warning { .tips {
gap: 16rpx; color: var(--theme-text-secondary);
display: flex; font-size: 28rpx;
padding: 24rpx; line-height: 1.5;
align-items: center; margin-bottom: 24rpx;
border-radius: 12rpx;
flex-direction: column;
background: #FFF4F4;
.warning-text {
color: #E34D59;
font-size: 28rpx;
text-align: center;
}
}
.delete-confirm {
gap: 16rpx;
display: flex;
flex-direction: column;
.confirm-label {
color: var(--theme-text-primary);
font-size: 28rpx;
}
} }
} }

View File

@ -83,15 +83,6 @@ Page({
travel.travelDate = Time.toDate(travel.travelAt); travel.travelDate = Time.toDate(travel.travelAt);
travel.travelTime = Time.toTime(travel.travelAt); travel.travelTime = Time.toTime(travel.travelAt);
} }
// 格式化创建和更新时间
if (travel.createdAt) {
(travel as any).createdAtFormatted = Time.toDateTime(travel.createdAt);
}
if (travel.updatedAt) {
(travel as any).updatedAtFormatted = Time.toDateTime(travel.updatedAt);
}
this.setData({ travel }); this.setData({ travel });
// 获取地点列表 // 获取地点列表
@ -151,13 +142,13 @@ Page({
} }
}, },
/** 编辑地点 */ /** 查看地点详情 */
toEditLocation(e: WechatMiniprogram.BaseEvent) { toLocationDetail(e: WechatMiniprogram.BaseEvent) {
const { id } = e.currentTarget.dataset; const { id } = e.currentTarget.dataset;
const { travel } = this.data; const { travel } = this.data;
if (id && travel && travel.id) { if (id && travel && travel.id) {
wx.navigateTo({ wx.navigateTo({
url: `/pages/main/travel-location-editor/index?id=${id}&travelId=${travel.id}` url: `/pages/main/travel-location-detail/index?id=${id}&travelId=${travel.id}`
}); });
} }
}, },

View File

@ -7,164 +7,105 @@
</t-navbar> </t-navbar>
</view> </view>
<view class="detail-container"> <view class="travel-detail setting-bg">
<!-- 加载状态 --> <!-- 加载状态 -->
<t-loading wx:if="{{isLoading}}" theme="dots" size="40rpx" /> <t-loading wx:if="{{isLoading}}" theme="dots" size="40rpx" />
<!-- 详情内容 --> <!-- 详情内容 -->
<view wx:if="{{!isLoading && travel}}" class="content"> <view wx:if="{{!isLoading && travel}}" class="content">
<!-- 状态标签 --> <!-- 状态标签 -->
<view class="status-section"> <view class="section status">
<t-tag <t-tag
size="large" size="large"
theme="{{travel.status === 'PLANNING' ? 'default' : travel.status === 'ONGOING' ? 'warning' : 'success'}}" theme="{{travel.status === 'PLANNING' ? 'default' : travel.status === 'ONGOING' ? 'warning' : 'success'}}"
variant="light" variant="outline"
icon="{{statusIcons[travel.status]}}" icon="{{statusIcons[travel.status]}}"
> >
{{statusLabels[travel.status]}} {{statusLabels[travel.status]}}
</t-tag> </t-tag>
</view> </view>
<!-- 标题 --> <!-- 标题 -->
<view class="title-section"> <view class="section title">{{travel.title || '未命名旅行'}}</view>
<text class="title">{{travel.title || '未命名旅行'}}</text>
</view>
<!-- 基本信息 --> <!-- 基本信息 -->
<view class="info-card"> <t-cell-group class="section info">
<view class="card-title"> <view slot="title" class="title">基本信息</view>
<t-icon name="info-circle" size="20px" class="icon" /> <t-cell left-icon="time" title="出行时间">
<text class="text">基本信息</text> <view slot="note">
</view> <text wx:if="{{travel.travelDate}}">{{travel.travelDate}} {{travel.travelTime}}</text>
<text wx:else class="undecided-value">未定</text>
<view class="info-list">
<view class="info-item">
<view class="label">
<t-icon name="time" size="18px" class="icon" />
<text class="text">出行时间</text>
</view>
<view class="value">{{travel.travelDate}} {{travel.travelTime}}</view>
</view> </view>
</t-cell>
<view wx:if="{{travel.days}}" class="info-item"> <t-cell left-icon="calendar" title="旅行天数">
<view class="label"> <view slot="note">
<t-icon name="calendar" size="18px" class="icon" /> <text wx:if="{{travel.days}}">{{travel.days}} 天</text>
<text class="text">旅行天数</text> <text wx:else class="undecided-value">未定</text>
</view>
<view class="value">{{travel.days}} 天</view>
</view> </view>
</t-cell>
<view wx:if="{{travel.transportationType}}" class="info-item"> <t-cell left-icon="{{travel.transportationType === 'PLANE' ? 'flight-takeoff' : travel.transportationType === 'TRAIN' ? 'map-route' : travel.transportationType === 'SELF_DRIVING' ? 'control-platform' : 'location'}}" title="交通方式">
<view class="label"> <view slot="note">{{transportLabels[travel.transportationType]}}</view>
<t-icon name="{{travel.transportationType === 'PLANE' ? 'flight-takeoff' : travel.transportationType === 'TRAIN' ? 'map-route' : travel.transportationType === 'SELF_DRIVING' ? 'control-platform' : 'location'}}" size="18px" class="icon" /> </t-cell>
<text class="text">交通方式</text> </t-cell-group>
</view>
<view class="value">{{transportLabels[travel.transportationType]}}</view>
</view>
</view>
</view>
<!-- 旅行内容 --> <!-- 旅行内容 -->
<view wx:if="{{travel.content}}" class="content-card"> <t-cell-group class="section">
<view class="card-title"> <view slot="title" class="title">详细说明</view>
<t-icon name="edit" size="20px" class="icon" /> <t-cell title="{{travel.content}}" />
<text class="text">详细说明</text> </t-cell-group>
</view> <t-cell-group class="section locations">
<view class="content-text">{{travel.content}}</view> <view slot="title" class="title">地点列表</view>
</view> <t-cell>
<view slot="right-icon" class="action">
<!-- 地点列表 --> <t-icon name="map" size="20px" color="var(--theme-wx)" bind:tap="toMap" />
<view class="locations-card"> <t-icon name="add" size="20px" color="var(--theme-wx)" bind:tap="toAddLocation" />
<view class="card-title">
<view class="title-left">
<t-icon name="location" size="20px" class="icon" />
<text class="text">地点列表</text>
</view> </view>
<view class="title-right"> </t-cell>
<view class="icon-btn" catch:tap="toMap"> <t-cell wx:if="{{isLoadingLocations}}" class="loading">
<t-icon name="map" size="20px" color="#5E7CE0" /> <t-loading slot="title" theme="dots" size="40rpx" />
</view> </t-cell>
<view class="icon-btn" catch:tap="toAddLocation"> <block wx:elif="{{0 < locations.length}}">
<t-icon name="add" size="20px" color="#5E7CE0" /> <t-cell
</view> class="location"
</view>
</view>
<!-- 加载中 -->
<view wx:if="{{isLoadingLocations}}" class="loading-container">
<t-loading theme="dots" size="40rpx" />
</view>
<!-- 地点列表 -->
<view wx:elif="{{locations.length > 0}}" class="locations-list">
<view
wx:for="{{locations}}" wx:for="{{locations}}"
wx:key="id" wx:key="id"
class="location-item" left-icon="{{locationTypeIcons[item.type]}}"
bind:tap="toEditLocation" title="{{item.title || '未命名地点'}}"
bind:tap="toLocationDetail"
data-id="{{item.id}}" data-id="{{item.id}}"
arrow
> >
<view class="location-icon"> <view slot="note" class="note">{{locationTypeLabels[item.type]}}</view>
<t-icon name="{{locationTypeIcons[item.type]}}" size="24px" color="#5E7CE0" /> <view slot="description" class="description">
</view> <view wx:if="{{item.amount}}" class="item">
<view class="location-content"> <t-icon name="money" size="14px" />
<view class="location-header"> <text>¥{{item.amount}}</text>
<text class="location-title">{{item.title || '未命名地点'}}</text>
<t-tag size="small" variant="light">{{locationTypeLabels[item.type]}}</t-tag>
</view> </view>
<view wx:if="{{item.description}}" class="location-description">{{item.description}}</view> <view wx:if="{{item.requireIdCard}}" class="item">
<view class="location-info"> <t-icon name="user" size="14px" />
<view wx:if="{{item.location}}" class="info-item"> <text>需要身份证</text>
<t-icon name="location" size="14px" /> </view>
<text>{{item.location}}</text> <view wx:if="{{item.importance}}" class="item">
</view> <t-icon name="chart-bubble" size="14px" />
<view wx:if="{{item.amount}}" class="info-item"> <text>重要程度</text>
<t-icon name="money-circle" size="14px" /> <view class="stars orange">
<text>¥{{item.amount}}</text> <t-icon
</view> wx:for="{{item.importance}}"
<view wx:if="{{item.requireIdCard}}" class="info-item"> wx:for-index="index"
<t-icon name="user" size="14px" /> wx:key="index"
<text>需要身份证</text> name="star-filled"
</view> size="14px"
<view wx:if="{{item.score}}" class="info-item"> />
<t-icon name="star" size="14px" />
<text>必要度 {{item.score}}</text>
</view> </view>
</view> </view>
</view> </view>
<view class="location-arrow"> </t-cell>
<t-icon name="chevron-right" size="20px" color="#DCDCDC" /> </block>
</view> </t-cell-group>
</view>
</view>
<!-- 空状态 -->
<view wx:else class="empty-state">
<t-icon name="location" size="48px" color="#DCDCDC" />
<text class="empty-text">暂无地点信息</text>
</view>
</view>
<!-- 时间信息 -->
<view class="time-card">
<view class="time-item">
<text class="label">创建时间</text>
<text class="value">{{travel.createdAtFormatted || ''}}</text>
</view>
<view wx:if="{{travel.updatedAt && travel.updatedAt !== travel.createdAt}}" class="time-item">
<text class="label">更新时间</text>
<text class="value">{{travel.updatedAtFormatted || ''}}</text>
</view>
</view>
<!-- 操作按钮 --> <!-- 操作按钮 -->
<view class="action-section"> <view class="section action">
<t-button <t-button
theme="danger" theme="danger"
variant="outline" variant="outline"
size="large" size="large"
icon="delete" icon="delete"
t-class="delete-btn" t-class="delete"
bind:tap="deleteTravel" bind:tap="deleteTravel"
> >
删除 删除
@ -173,7 +114,7 @@
theme="primary" theme="primary"
size="large" size="large"
icon="edit" icon="edit"
t-class="edit-btn" t-class="edit"
bind:tap="toEdit" bind:tap="toEdit"
> >
编辑旅行计划 编辑旅行计划
@ -186,23 +127,17 @@
<t-dialog <t-dialog
visible="{{deleteDialogVisible}}" visible="{{deleteDialogVisible}}"
title="删除旅行计划" title="删除旅行计划"
confirm-btn="删除" confirm-btn="{{ {content: '删除', variant: 'text', theme: 'danger'} }}"
cancel-btn="取消" cancel-btn="取消"
bind:confirm="confirmDelete" bind:confirm="confirmDelete"
bind:cancel="cancelDelete" bind:cancel="cancelDelete"
> >
<view class="delete-dialog-content"> <view slot="content" class="delete-dialog">
<view class="delete-warning"> <view class="tips">
<t-icon name="error-circle" size="48rpx" color="#E34D59" /> <text>此计划的地点、图片和视频也会同步删除,删除后无法恢复,请输入 "</text>
<text class="warning-text">删除后无法恢复,请谨慎操作!</text> <text style="color: var(--theme-error)">确认删除</text>
</view> <text>" 以继续</text>
<view class="delete-confirm">
<text class="confirm-label">请输入"确认删除"以继续:</text>
<t-input
placeholder="请输入确认删除"
model:value="{{deleteConfirmText}}"
borderless="{{false}}"
/>
</view> </view>
<t-input placeholder="请输入:确认删除" model:value="{{deleteConfirmText}}" />
</view> </view>
</t-dialog> </t-dialog>

View File

@ -30,6 +30,31 @@
align-items: center; align-items: center;
} }
.travel-at-content,
.days-content {
gap: 16rpx;
display: flex;
align-items: center;
.clear-icon {
color: var(--theme-text-tertiary);
cursor: pointer;
&:active {
opacity: .6;
}
}
.undecided-text {
color: var(--theme-text-tertiary);
cursor: pointer;
&:active {
opacity: .6;
}
}
}
.days-stepper { .days-stepper {
display: flex; display: flex;
align-items: center; align-items: center;
@ -58,35 +83,13 @@
} }
} }
.delete-dialog-content { .delete-dialog {
gap: 32rpx; padding: 16rpx 0;
display: flex;
flex-direction: column;
.delete-warning { .tips {
gap: 16rpx; color: var(--theme-text-secondary);
display: flex; font-size: 28rpx;
padding: 24rpx; line-height: 1.5;
align-items: center; margin-bottom: 24rpx;
border-radius: 12rpx;
flex-direction: column;
background: #FFF4F4;
.warning-text {
color: #E34D59;
font-size: 28rpx;
text-align: center;
}
}
.delete-confirm {
gap: 16rpx;
display: flex;
flex-direction: column;
.confirm-label {
color: var(--theme-text-primary);
font-size: 28rpx;
}
} }
} }

View File

@ -17,8 +17,12 @@ interface TravelEditorData {
date: string; date: string;
/** 出行时间 */ /** 出行时间 */
time: string; time: string;
/** 出行时间是否未定 */
travelAtUndecided: boolean;
/** 天数 */ /** 天数 */
days: number; days: number;
/** 天数是否未定 */
daysUndecided: boolean;
/** 交通类型 */ /** 交通类型 */
transportationType: TransportationType; transportationType: TransportationType;
/** 状态 */ /** 状态 */
@ -49,9 +53,11 @@ Page({
content: "", content: "",
date: "2025-06-28", date: "2025-06-28",
time: "16:00", time: "16:00",
travelAtUndecided: true,
days: 1, days: 1,
transportationType: TransportationType.PLANE, daysUndecided: true,
transportationTypeIndex: 0, transportationType: TransportationType.SELF_DRIVING,
transportationTypeIndex: 4,
status: TravelStatus.PLANNING, status: TravelStatus.PLANNING,
statusIndex: 0, statusIndex: 0,
isLoading: false, isLoading: false,
@ -108,13 +114,19 @@ Page({
// 格式化数据 // 格式化数据
let date = ""; let date = "";
let time = ""; let time = "";
let travelAtUndecided = true;
if (travel.travelAt) { if (travel.travelAt) {
date = Time.toDate(travel.travelAt); date = Time.toDate(travel.travelAt);
time = Time.toTime(travel.travelAt); time = Time.toTime(travel.travelAt);
travelAtUndecided = false;
} }
// 判断天数是否未定
const daysUndecided = !travel.days;
const days = travel.days || 1;
// 计算交通类型索引 // 计算交通类型索引
const transportationType = travel.transportationType || TransportationType.PLANE; const transportationType = travel.transportationType || TransportationType.SELF_DRIVING;
const transportationTypeIndex = this.data.transportationTypes.findIndex( const transportationTypeIndex = this.data.transportationTypes.findIndex(
item => item.value === transportationType item => item.value === transportationType
); );
@ -128,9 +140,11 @@ Page({
content: travel.content || "", content: travel.content || "",
date, date,
time, time,
days: travel.days || 1, travelAtUndecided,
days,
daysUndecided,
transportationType, transportationType,
transportationTypeIndex: transportationTypeIndex >= 0 ? transportationTypeIndex : 0, transportationTypeIndex: transportationTypeIndex >= 0 ? transportationTypeIndex : 4,
status, status,
statusIndex: statusIndex >= 0 ? statusIndex : 0, statusIndex: statusIndex >= 0 ? statusIndex : 0,
isLoading: false isLoading: false
@ -163,13 +177,42 @@ Page({
status: this.data.statuses[index].value status: this.data.statuses[index].value
}); });
}, },
/** 切换出行时间未定状态 */
toggleTravelAtUndecided(e: any) {
this.setData({ travelAtUndecided: e.detail.value });
},
/** 切换天数未定状态 */
toggleDaysUndecided(e: any) {
this.setData({ daysUndecided: e.detail.value });
},
/** 清除出行时间 */
clearTravelAt() {
this.setData({ travelAtUndecided: true });
},
/** 清除天数 */
clearDays() {
this.setData({ daysUndecided: true });
},
/** 点击未定文字选择时间 */
selectTravelAt() {
// 设置为已定状态,会自动显示选择器
const unixTime = new Date().getTime();
this.setData({
travelAtUndecided: false,
date: Time.toDate(unixTime),
time: Time.toTime(unixTime)
});
},
/** 点击未定文字选择天数 */
selectDays() {
this.setData({
daysUndecided: false,
days: 1
});
},
/** 取消 */ /** 取消 */
cancel() { cancel() {
if (this.data.mode === "create") { wx.navigateBack();
wx.navigateBack();
} else {
wx.navigateBack();
}
}, },
/** 提交/保存 */ /** 提交/保存 */
submit() { submit() {
@ -195,8 +238,10 @@ Page({
await TravelApi.create({ await TravelApi.create({
title: this.data.title.trim(), title: this.data.title.trim(),
content: this.data.content.trim(), content: this.data.content.trim(),
travelAt: new Date(`${this.data.date}T${this.data.time}:00`).getTime(), travelAt: this.data.travelAtUndecided
days: this.data.days, ? null
: new Date(`${this.data.date}T${this.data.time}:00`).getTime(),
days: this.data.daysUndecided ? null : this.data.days,
transportationType: this.data.transportationType, transportationType: this.data.transportationType,
status: this.data.status status: this.data.status
}); });
@ -219,8 +264,10 @@ Page({
id: this.data.id!, id: this.data.id!,
title: this.data.title.trim(), title: this.data.title.trim(),
content: this.data.content.trim(), content: this.data.content.trim(),
travelAt: new Date(`${this.data.date}T${this.data.time}:00`).getTime(), travelAt: this.data.travelAtUndecided
days: this.data.days, ? null
: new Date(`${this.data.date}T${this.data.time}:00`).getTime(),
days: this.data.daysUndecided ? null : this.data.days,
transportationType: this.data.transportationType, transportationType: this.data.transportationType,
status: this.data.status status: this.data.status
}); });

View File

@ -32,18 +32,29 @@
</t-cell-group> </t-cell-group>
<t-cell-group class="section"> <t-cell-group class="section">
<t-cell class="travel-at" title="出行时间"> <t-cell class="travel-at" title="出行时间">
<view slot="right-icon"> <view slot="right-icon" class="travel-at-content">
<picker class="picker" mode="date" model:value="{{date}}"> <picker wx:if="{{!travelAtUndecided}}" class="picker" mode="date" model:value="{{date}}">
<view slot="content" class="slot"> <view slot="content" class="slot">
<t-icon name="calendar" size="20px" class="icon" /> <t-icon name="calendar" size="20px" class="icon" />
<text>{{date}}</text> <text>{{date}}</text>
</view> </view>
</picker> </picker>
<view wx:else class="slot" bindtap="selectTravelAt">
<text class="undecided-text">未定</text>
</view>
<t-icon
wx:if="{{!travelAtUndecided}}"
name="close-circle-filled"
size="20px"
class="clear-icon"
bindtap="clearTravelAt"
/>
</view> </view>
</t-cell> </t-cell>
<t-cell title="旅行天数" t-class="days-cell"> <t-cell title="旅行天数" t-class="days-cell">
<view slot="right-icon" class="days-stepper"> <view slot="right-icon" class="days-content">
<t-stepper <t-stepper
wx:if="{{!daysUndecided}}"
theme="filled" theme="filled"
model:value="{{days}}" model:value="{{days}}"
size="medium" size="medium"
@ -51,6 +62,16 @@
max="{{999}}" max="{{999}}"
t-class="stepper" t-class="stepper"
/> />
<view wx:else class="slot" bindtap="selectDays">
<text class="undecided-text">未定</text>
</view>
<t-icon
wx:if="{{!daysUndecided}}"
name="close-circle-filled"
size="20px"
class="clear-icon"
bindtap="clearDays"
/>
</view> </view>
</t-cell> </t-cell>
<t-cell title="交通方式"> <t-cell title="交通方式">
@ -128,23 +149,17 @@
<t-dialog <t-dialog
visible="{{deleteDialogVisible}}" visible="{{deleteDialogVisible}}"
title="删除旅行计划" title="删除旅行计划"
confirm-btn="删除" confirm-btn="{{ {content: '删除', variant: 'text', theme: 'danger'} }}"
cancel-btn="取消" cancel-btn="取消"
bind:confirm="confirmDelete" bind:confirm="confirmDelete"
bind:cancel="cancelDelete" bind:cancel="cancelDelete"
> >
<view class="delete-dialog-content"> <view slot="content" class="delete-dialog">
<view class="delete-warning"> <view class="tips">
<t-icon name="error-circle" size="48rpx" color="#E34D59" /> <text>此计划的地点、图片和视频也会同步删除,删除后无法恢复,请输入 "</text>
<text class="warning-text">删除后无法恢复,请谨慎操作!</text> <text style="color: var(--theme-error)">确认删除</text>
</view> <text>" 以继续</text>
<view class="delete-confirm">
<text class="confirm-label">请输入"确认删除"以继续:</text>
<t-input
placeholder="请输入确认删除"
model:value="{{deleteConfirmText}}"
borderless="{{false}}"
/>
</view> </view>
<t-input placeholder="请输入:确认删除" model:value="{{deleteConfirmText}}" />
</view> </view>
</t-dialog> </t-dialog>

View File

@ -0,0 +1,17 @@
{
"component": true,
"usingComponents": {
"t-tag": "tdesign-miniprogram/tag/tag",
"t-cell": "tdesign-miniprogram/cell/cell",
"t-rate": "tdesign-miniprogram/rate/rate",
"t-icon": "tdesign-miniprogram/icon/icon",
"t-input": "tdesign-miniprogram/input/input",
"t-button": "tdesign-miniprogram/button/button",
"t-empty": "tdesign-miniprogram/empty/empty",
"t-dialog": "tdesign-miniprogram/dialog/dialog",
"t-navbar": "tdesign-miniprogram/navbar/navbar",
"t-loading": "tdesign-miniprogram/loading/loading",
"t-cell-group": "tdesign-miniprogram/cell-group/cell-group"
},
"styleIsolation": "shared"
}

View File

@ -0,0 +1,144 @@
// pages/main/travel-location-detail/index.less
.travel-location-detail {
width: 100vw;
min-height: 100vh;
background: var(--theme-bg-page);
box-sizing: border-box;
.status-card {
padding: 64rpx 24rpx;
}
.content {
display: flex;
padding-top: 48rpx;
flex-direction: column;
padding-bottom: 128rpx;
.section {
margin-top: 32rpx;
> .title {
color: var(--theme-text-secondary);
padding: 0 32rpx;
font-size: 28rpx;
line-height: 64rpx;
}
&.status {
display: flex;
margin-top: 24rpx;
justify-content: center;
}
&.title {
color: var(--theme-text-primary);
padding: 24rpx;
text-align: center;
margin-top: 24rpx;
background: var(--theme-bg-card);
box-shadow: 0 2px 12px var(--theme-shadow-light);
.title-text {
color: var(--theme-text-primary);
font-size: 40rpx;
font-weight: bold;
line-height: 1.5;
}
.subtitle {
color: var(--theme-text-secondary);
display: block;
font-size: 28rpx;
margin-top: 12rpx;
}
}
&.location {
.t-cell__title {
margin: 0;
}
.map {
padding: 0;
}
.mini-map {
width: 100%;
height: 520rpx;
}
}
&.media {
.media-grid {
gap: 16rpx;
display: grid;
grid-template-columns: repeat(3, 1fr);
.media-item {
width: 100%;
height: 200rpx;
overflow: hidden;
position: relative;
border-radius: 12rpx;
.thumbnail {
width: 100%;
height: 100%;
}
.video-container {
width: 100%;
height: 100%;
position: relative;
.thumbnail {
width: 100%;
height: 100%;
}
.play-icon {
top: 50%;
left: 50%;
color: rgba(255, 255, 255, .9);
position: absolute;
transform: translate(-50%, -50%);
}
}
}
}
}
&.navigate {
padding: 0 16rpx;
}
&.action {
gap: 24rpx;
display: flex;
padding: 24rpx 16rpx 0 16rpx;
.edit {
flex: 2;
}
.delete {
flex: 1;
}
}
}
}
}
.delete-dialog {
padding: 16rpx 0;
.tips {
color: var(--theme-text-secondary);
font-size: 28rpx;
line-height: 1.5;
margin-bottom: 24rpx;
}
}

View File

@ -0,0 +1,291 @@
// pages/main/travel-location-detail/index.ts
import Time from "../../../utils/Time";
import config from "../../../config/index";
import { TravelLocationApi } from "../../../api/TravelLocationApi";
import { TravelLocation, TravelLocationTypeIcon, TravelLocationTypeLabel } from "../../../types/Travel";
import { MediaAttachExt, MediaAttachType } from "../../../types/Attachment";
import { MediaItem, MediaItemType } from "../../../types/UI";
import Toolkit from "../../../utils/Toolkit";
interface TravelLocationView extends TravelLocation {
/** 首次出行时间 */
firstTravelTime?: string;
/** 最近出行时间 */
lastTravelTime?: string;
/** 媒体列表 */
mediaItems?: MediaItem[];
}
interface TravelLocationDetailData {
/** 地点详情 */
location: TravelLocationView | null;
/** 地点 ID */
locationId: string;
/** 旅行 ID */
travelId: string;
/** 是否正在加载 */
isLoading: boolean;
/** 错误信息 */
errorMessage: string;
/** 地点类型标签映射 */
locationTypeLabels: typeof TravelLocationTypeLabel;
/** 地点类型图标映射 */
locationTypeIcons: typeof TravelLocationTypeIcon;
/** 媒体类型枚举 */
mediaItemTypeEnum: typeof MediaItemType;
/** 地图标记 */
mapMarkers: WechatMiniprogram.MapMarker[];
/** 删除对话框可见性 */
deleteDialogVisible: boolean;
/** 删除确认文本 */
deleteConfirmText: string;
}
Page({
data: <TravelLocationDetailData>{
location: null,
locationId: "",
travelId: "",
isLoading: true,
errorMessage: "",
locationTypeLabels: TravelLocationTypeLabel,
locationTypeIcons: TravelLocationTypeIcon,
mediaItemTypeEnum: {
...MediaItemType
},
mapMarkers: [],
deleteDialogVisible: false,
deleteConfirmText: ""
},
onLoad(options: any) {
const { id, travelId } = options;
if (id) {
this.setData({
locationId: id,
travelId: travelId || ""
});
this.fetchDetail(id);
} else {
wx.showToast({
title: "参数错误",
icon: "error"
});
setTimeout(() => {
wx.navigateBack();
}, 1500);
}
},
onShow() {
// 页面显示时刷新数据(从编辑页返回时)
if (this.data.locationId && !this.data.isLoading && this.data.location) {
this.fetchDetail(this.data.locationId);
}
},
/** 获取地点详情 */
async fetchDetail(id: string) {
this.setData({
isLoading: true,
errorMessage: ""
});
try {
const location = await TravelLocationApi.getDetail(id);
const thumbItems = (location.items || []).filter((item) => item.attachType === MediaAttachType.THUMB);
const mediaItems: MediaItem[] = [];
thumbItems.forEach((thumbItem) => {
try {
const extStr = thumbItem.ext ? thumbItem.ext.toString() : "";
if (!extStr) {
return;
}
const ext = JSON.parse(extStr) as MediaAttachExt;
const thumbURL = `${config.url}/attachment/read/${thumbItem.mongoId}`;
const sourceURL = `${config.url}/attachment/read/${ext.sourceMongoId}`;
mediaItems.push({
type: ext.isVideo ? MediaItemType.VIDEO : MediaItemType.IMAGE,
thumbURL,
sourceURL,
size: thumbItem.size || 0,
attachmentId: thumbItem.id!
});
} catch (parseError) {
console.warn("解析附件扩展信息失败", parseError);
}
});
// 构建地图标记
const mapMarkers: WechatMiniprogram.MapMarker[] = [];
if (location.lat !== undefined && location.lng !== undefined) {
mapMarkers.push({
id: 0,
latitude: location.lat,
longitude: location.lng,
width: 30,
height: 30,
callout: {
content: location.title || "地点",
display: "ALWAYS",
padding: 10,
borderRadius: 5,
bgColor: "#FFFFFF",
color: "#333333",
fontSize: 12,
textAlign: "center"
}
});
}
console.log(mediaItems);
this.setData({
location: {
...location,
mediaItems,
firstTravelTime: location.firstTraveledAt ? Time.toDateTime(location.firstTraveledAt) : "",
lastTravelTime: location.lastTraveledAt ? Time.toDateTime(location.lastTraveledAt) : ""
},
travelId: location.travelId ? String(location.travelId) : this.data.travelId,
mapMarkers
});
} catch (error) {
console.error("获取地点详情失败:", error);
this.setData({
errorMessage: "加载失败,请稍后重试"
});
wx.showToast({
title: "加载失败",
icon: "error"
});
} finally {
this.setData({ isLoading: false });
}
},
/** 重新加载 */
retryFetch() {
if (this.data.locationId) {
this.fetchDetail(this.data.locationId);
}
},
/** 编辑地点 */
toEdit() {
const { location, travelId } = this.data;
if (location && location.id) {
wx.navigateTo({
url: `/pages/main/travel-location-editor/index?id=${location.id}&travelId=${travelId || location.travelId || ""}`
});
}
},
/** 出发(打开地图导航) */
navigate() {
const { location } = this.data;
if (location && location.lat !== undefined && location.lng !== undefined) {
wx.openLocation({
latitude: location.lat,
longitude: location.lng,
name: location.title || "目的地",
address: location.location || "",
scale: 15
});
} else {
wx.showToast({
title: "位置信息不完整",
icon: "error"
});
}
},
/** 预览媒体 */
preview(e: WechatMiniprogram.BaseEvent) {
const index = e.currentTarget.dataset.index;
const { location } = this.data;
if (!location || !location.mediaItems) {
return;
}
const sources = location.mediaItems.map(item => ({
url: item.sourceURL,
type: MediaItemType[item.type].toLowerCase()
}));
const total = sources.length;
const startIndex = Math.max(0, index - 25);
const endIndex = Math.min(total, startIndex + 50);
const newCurrentIndex = index - startIndex;
wx.previewMedia({
current: newCurrentIndex,
sources: sources.slice(startIndex, endIndex) as WechatMiniprogram.MediaSource[]
});
},
/** 删除地点 */
deleteLocation() {
this.setData({
deleteDialogVisible: true,
deleteConfirmText: ""
});
},
/** 取消删除 */
cancelDelete() {
this.setData({
deleteDialogVisible: false,
deleteConfirmText: ""
});
},
/** 确认删除 */
confirmDelete() {
const inputText = this.data.deleteConfirmText.trim();
if (inputText !== "确认删除") {
wx.showToast({
title: "输入不匹配",
icon: "error"
});
return;
}
this.setData({
deleteDialogVisible: false
});
this.executeDelete();
},
/** 执行删除 */
async executeDelete() {
if (!this.data.location || !this.data.location.id) {
return;
}
wx.showLoading({ title: "删除中...", mask: true });
try {
await TravelLocationApi.delete(this.data.location.id);
wx.showToast({
title: "删除成功",
icon: "success"
});
setTimeout(() => {
wx.navigateBack();
}, 1500);
} catch (error) {
// 错误已由 Network 类处理
} finally {
wx.hideLoading();
}
},
/** 返回 */
async goBack() {
Toolkit.async(() => wx.navigateBack()); // 微信 BUG需要延时
}
});

View File

@ -0,0 +1,165 @@
<!--pages/main/travel-location-detail/index.wxml-->
<view class="custom-navbar">
<t-navbar title="地点详情" leftArrow bind:go-back="goBack">
<view slot="right" class="edit-btn" bind:tap="toEdit">
<t-icon name="edit" size="24px" />
</view>
</t-navbar>
</view>
<view class="travel-location-detail setting-bg">
<t-loading wx:if="{{isLoading}}" theme="dots" size="40rpx" />
<view wx:elif="{{errorMessage}}" class="status-card">
<t-empty icon="error-circle" description="{{errorMessage}}">
<t-button size="small" theme="primary" variant="outline" bind:tap="retryFetch">
重新加载
</t-button>
</t-empty>
</view>
<view wx:elif="{{!location}}" class="status-card">
<t-empty icon="location" description="暂无地点信息" />
</view>
<view wx:else class="content">
<!-- 类型标签 -->
<view class="section status">
<t-tag size="large" theme="primary" variant="light" icon="{{locationTypeIcons[location.type]}}">
{{locationTypeLabels[location.type]}}
</t-tag>
</view>
<!-- 标题 -->
<view class="section title">
<text class="title-text">{{location.title || '未命名地点'}}</text>
</view>
<!-- 位置信息 -->
<t-cell-group wx:if="{{location.lat !== undefined && location.lng !== undefined}}" class="section location">
<view slot="title" class="title">位置信息</view>
<t-cell class="map">
<map
slot="description"
class="mini-map"
latitude="{{location.lat}}"
longitude="{{location.lng}}"
markers="{{mapMarkers}}"
scale="15"
show-location
></map>
</t-cell>
<t-cell
left-icon="location"
title="{{location.location || '位置信息'}}"
arrow
bind:tap="navigate"
/>
</t-cell-group>
<!-- 基础信息 -->
<t-cell-group class="section info">
<view slot="title" class="title">基础信息</view>
<t-cell wx:if="{{location.amount !== undefined && location.amount !== null}}" left-icon="money" title="费用">
<view slot="note">¥{{location.amount}}</view>
</t-cell>
<t-cell left-icon="verify" title="需要身份证">
<view slot="note" class="{{location.requireIdCard ? 'red' : ''}}">
{{location.requireIdCard ? '需要' : '无需'}}
</view>
</t-cell>
<t-cell left-icon="calendar" title="需要预约">
<view slot="note" class="{{location.requireAppointment ? 'warning' : ''}}">
{{location.requireAppointment ? '需要' : '无需'}}
</view>
</t-cell>
<t-cell wx:if="{{location.score !== undefined && location.score !== null}}" left-icon="star" title="评分">
<view slot="note">
<t-rate value="{{location.score}}" count="{{5}}" size="20px" readonly />
</view>
</t-cell>
<t-cell wx:if="{{location.importance !== undefined && location.importance !== null}}" left-icon="chart-bubble" title="重要程度">
<view slot="note">
<t-rate value="{{location.importance}}" count="{{5}}" size="20px" readonly />
</view>
</t-cell>
</t-cell-group>
<!-- 出行记录 -->
<t-cell-group class="section">
<view slot="title" class="title">出行记录</view>
<t-cell left-icon="flag" title="首次出行">
<view slot="note">{{location.firstTravelTime || '未记录'}}</view>
</t-cell>
<t-cell left-icon="calendar-1" title="最近出行">
<view slot="note">{{location.lastTravelTime || '未记录'}}</view>
</t-cell>
<t-cell left-icon="chart" title="累计次数">
<view slot="note">{{location.travelCount || 0}} 次</view>
</t-cell>
</t-cell-group>
<!-- 详细说明 -->
<t-cell-group wx:if="{{location.description}}" class="section">
<view slot="title" class="title">详细说明</view>
<t-cell title="{{location.description}}" />
</t-cell-group>
<!-- 照片/视频 -->
<t-cell-group wx:if="{{location.mediaItems && 0 < location.mediaItems.length}}" class="section media">
<view slot="title" class="title">照片/视频</view>
<t-cell>
<view class="media-grid">
<view
wx:for="{{location.mediaItems}}"
wx:key="attachmentId"
class="media-item"
bind:tap="preview"
data-index="{{index}}"
>
<image
wx:if="{{item.type === mediaItemTypeEnum.IMAGE}}"
src="{{item.thumbURL}}"
class="thumbnail"
mode="aspectFill"
></image>
<view wx:if="{{item.type === mediaItemTypeEnum.VIDEO}}" class="video-container">
<image src="{{item.thumbURL}}" class="thumbnail" mode="aspectFill"></image>
<t-icon class="play-icon" name="play-circle-filled" size="48px" />
</view>
</view>
</view>
</t-cell>
</t-cell-group>
<view class="section navigate">
<t-button theme="primary" size="large" icon="rocket" block bind:tap="navigate">
出发导航
</t-button>
</view>
<!-- 操作按钮 -->
<view class="section action">
<t-button
theme="danger"
variant="outline"
size="large"
icon="delete"
t-class="delete"
bind:tap="deleteLocation"
>
删除
</t-button>
<t-button theme="primary" size="large" icon="edit" t-class="edit" bind:tap="toEdit">
编辑地点
</t-button>
</view>
</view>
</view>
<t-dialog
visible="{{deleteDialogVisible}}"
title="删除地点"
confirm-btn="{{ {content: '删除', variant: 'text', theme: 'danger'} }}"
cancel-btn="取消"
bind:confirm="confirmDelete"
bind:cancel="cancelDelete"
>
<view slot="content" class="delete-dialog">
<view class="tips">
<text>此地点的图片和视频也会同步删除,删除后无法恢复,请输入 "</text>
<text style="color: var(--theme-error)">确认删除</text>
<text>" 以继续</text>
</view>
<t-input placeholder="请输入:确认删除" model:value="{{deleteConfirmText}}" />
</view>
</t-dialog>

View File

@ -6,11 +6,14 @@
"t-rate": "tdesign-miniprogram/rate/rate", "t-rate": "tdesign-miniprogram/rate/rate",
"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-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

@ -1,6 +1,6 @@
// pages/main/travel-location-editor/index.less // pages/main/travel-location-editor/index.less
.container { .travel-location-editor {
width: 100vw; width: 100vw;
min-height: 100vh; min-height: 100vh;
background: var(--theme-bg-secondary); background: var(--theme-bg-secondary);
@ -14,7 +14,7 @@
align-items: center; align-items: center;
flex-direction: column; flex-direction: column;
.loading-text { .text {
color: var(--theme-text-secondary); color: var(--theme-text-secondary);
margin-top: 24rpx; margin-top: 24rpx;
font-size: 28rpx; font-size: 28rpx;
@ -24,32 +24,31 @@
.section { .section {
margin-top: 48rpx; margin-top: 48rpx;
> .title {
color: var(--theme-text-secondary);
padding: 0 32rpx;
font-size: 28rpx;
line-height: 64rpx;
}
.location {
.title {
width: 2em;
}
}
.picker .slot { .picker .slot {
gap: 16rpx; gap: 16rpx;
display: flex; display: flex;
align-items: center; align-items: center;
} }
.location-slot {
gap: 16rpx;
display: flex;
align-items: center;
.location-text {
color: var(--theme-text-primary);
font-size: 28rpx;
}
.location-placeholder {
color: var(--theme-text-placeholder);
font-size: 28rpx;
}
}
&.media { &.media {
.gallery { .gallery {
gap: 10rpx; gap: 10rpx;
padding: 0 6rpx;
display: grid; display: grid;
grid-template-columns: repeat(3, 1fr); grid-template-columns: repeat(3, 1fr);
@ -67,6 +66,10 @@
margin: 0; margin: 0;
font-size: 80rpx; font-size: 80rpx;
background: transparent; background: transparent;
&::after {
border-radius: 0;
}
} }
.thumbnail { .thumbnail {
@ -224,3 +227,14 @@
} }
} }
} }
.delete-dialog {
padding: 16rpx 0;
.tips {
color: var(--theme-text-secondary);
font-size: 28rpx;
line-height: 1.5;
margin-bottom: 24rpx;
}
}

View File

@ -30,8 +30,18 @@ interface TravelLocationEditorData {
amount: number; amount: number;
/** 是否需要身份证 */ /** 是否需要身份证 */
requireIdCard: boolean; requireIdCard: boolean;
/** 必要评分 */ /** 是否需要预约 */
requireAppointment: boolean;
/** 首次出行时间戳 */
firstTraveledAt: number;
/** 上次出行时间戳 */
lastTraveledAt: number;
/** 出行次数 */
travelCount: number;
/** 评分 */
score: number; score: number;
/** 重要程度 */
importance: number;
/** 媒体列表(创建和编辑模式使用) */ /** 媒体列表(创建和编辑模式使用) */
mediaList: (MediaItem | WechatMediaItem)[]; mediaList: (MediaItem | WechatMediaItem)[];
/** 新媒体列表(编辑模式使用) */ /** 新媒体列表(编辑模式使用) */
@ -48,6 +58,12 @@ interface TravelLocationEditorData {
locationTypes: { label: string; value: TravelLocationType }[]; locationTypes: { label: string; value: TravelLocationType }[];
/** 地点类型选中索引 */ /** 地点类型选中索引 */
locationTypeIndex: number; locationTypeIndex: number;
/** 地点类型选择器可见性 */
locationTypePickerVisible: boolean;
/** 删除对话框可见性 */
deleteDialogVisible: boolean;
/** 删除确认文本 */
deleteConfirmText: string;
/** 媒体类型枚举 */ /** 媒体类型枚举 */
mediaItemTypeEnum: any; mediaItemTypeEnum: any;
} }
@ -65,7 +81,12 @@ Page({
lng: 0, lng: 0,
amount: 0, amount: 0,
requireIdCard: false, requireIdCard: false,
requireAppointment: false,
firstTraveledAt: 0,
lastTraveledAt: 0,
travelCount: 0,
score: 3, score: 3,
importance: 1,
mediaList: [], mediaList: [],
newMediaList: [], newMediaList: [],
isLoading: false, isLoading: false,
@ -83,7 +104,10 @@ Page({
{ label: "购物", value: TravelLocationType.SHOPPING }, { label: "购物", value: TravelLocationType.SHOPPING },
{ label: "其他", value: TravelLocationType.OTHER } { label: "其他", value: TravelLocationType.OTHER }
], ],
locationTypeIndex: 0 locationTypeIndex: 0,
locationTypePickerVisible: false,
deleteDialogVisible: false,
deleteConfirmText: ""
}, },
onLoad(options: any) { onLoad(options: any) {
@ -161,7 +185,12 @@ Page({
lng: location.lng || 0, lng: location.lng || 0,
amount: location.amount || 0, amount: location.amount || 0,
requireIdCard: location.requireIdCard || false, requireIdCard: location.requireIdCard || false,
requireAppointment: location.requireAppointment || false,
firstTraveledAt: location.firstTraveledAt || 0,
lastTraveledAt: location.lastTraveledAt || 0,
travelCount: location.travelCount || 0,
score: location.score !== undefined ? location.score : 3, score: location.score !== undefined ? location.score : 3,
importance: location.importance !== undefined ? location.importance : 1,
mediaList, mediaList,
isLoading: false isLoading: false
}); });
@ -187,21 +216,51 @@ Page({
}); });
}, },
/** 显示地点类型选择器 */
showLocationTypePicker() {
this.setData({ locationTypePickerVisible: true });
},
/** Picker 确认 */
onPickerConfirm(e: any) {
const index = e.detail.value;
this.setData({
locationTypeIndex: index,
type: this.data.locationTypes[index].value,
locationTypePickerVisible: false
});
},
/** Picker 取消 */
onPickerCancel() {
this.setData({ locationTypePickerVisible: false });
},
/** 改变是否需要身份证 */ /** 改变是否需要身份证 */
onChangeRequireIdCard(e: any) { onChangeRequireIdCard(e: any) {
this.setData({ requireIdCard: e.detail.value }); this.setData({ requireIdCard: e.detail.value });
}, },
/** 改变是否需要预约 */
onChangeRequireAppointment(e: any) {
this.setData({ requireAppointment: e.detail.value });
},
/** 改变评分 */ /** 改变评分 */
onChangeScore(e: any) { onChangeScore(e: any) {
this.setData({ score: e.detail.value }); this.setData({ score: e.detail.value });
}, },
/** 改变重要程度 */
onChangeImportance(e: any) {
this.setData({ importance: e.detail.value });
},
/** 选择位置 */ /** 选择位置 */
chooseLocation() { chooseLocation() {
wx.chooseLocation({ wx.chooseLocation({
success: (res) => { success: (res) => {
const locationName = res.address || res.name; const locationName = res.name || res.address;
const updateData: any = { const updateData: any = {
location: locationName, location: locationName,
lat: res.latitude, lat: res.latitude,
@ -340,31 +399,60 @@ Page({
/** 删除地点 */ /** 删除地点 */
deleteLocation() { deleteLocation() {
wx.showModal({ this.setData({
title: "确认删除", deleteDialogVisible: true,
content: "确定要删除这个地点吗?删除后无法恢复。", deleteConfirmText: ""
success: async (res) => {
if (res.confirm && this.data.id) {
try {
await TravelLocationApi.delete(this.data.id);
wx.showToast({
title: "删除成功",
icon: "success"
});
setTimeout(() => {
wx.navigateBack();
}, 1000);
} catch (error) {
wx.showToast({
title: "删除失败",
icon: "error"
});
}
}
}
}); });
}, },
/** 取消删除 */
cancelDelete() {
this.setData({
deleteDialogVisible: false,
deleteConfirmText: ""
});
},
/** 确认删除 */
confirmDelete() {
const inputText = this.data.deleteConfirmText.trim();
if (inputText !== "确认删除") {
wx.showToast({
title: "输入不匹配",
icon: "error"
});
return;
}
this.setData({
deleteDialogVisible: false
});
this.executeDelete();
},
/** 执行删除 */
async executeDelete() {
if (!this.data.id) {
return;
}
wx.showLoading({ title: "删除中...", mask: true });
try {
await TravelLocationApi.delete(this.data.id);
wx.showToast({
title: "删除成功",
icon: "success"
});
setTimeout(() => {
wx.navigateBack();
}, 1500);
} catch (error) {
// 错误已由 Network 类处理
} finally {
wx.hideLoading();
}
},
/** 提交/保存 */ /** 提交/保存 */
submit() { submit() {
// 验证必填字段 // 验证必填字段
@ -425,7 +513,12 @@ Page({
lng: this.data.lng, lng: this.data.lng,
amount: this.data.amount, amount: this.data.amount,
requireIdCard: this.data.requireIdCard, requireIdCard: this.data.requireIdCard,
requireAppointment: this.data.requireAppointment,
firstTraveledAt: this.data.firstTraveledAt,
lastTraveledAt: this.data.lastTraveledAt,
travelCount: this.data.travelCount,
score: this.data.score, score: this.data.score,
importance: this.data.importance,
tempFileIds tempFileIds
}); });
wx.showToast({ wx.showToast({
@ -479,7 +572,12 @@ Page({
lng: this.data.lng, lng: this.data.lng,
amount: this.data.amount, amount: this.data.amount,
requireIdCard: this.data.requireIdCard, requireIdCard: this.data.requireIdCard,
requireAppointment: this.data.requireAppointment,
firstTraveledAt: this.data.firstTraveledAt,
lastTraveledAt: this.data.lastTraveledAt,
travelCount: this.data.travelCount,
score: this.data.score, score: this.data.score,
importance: this.data.importance,
attachmentIds, attachmentIds,
tempFileIds tempFileIds
}); });

View File

@ -3,42 +3,25 @@
<text slot="left" bindtap="cancel">取消</text> <text slot="left" bindtap="cancel">取消</text>
</t-navbar> </t-navbar>
<scroll-view class="container" type="custom" scroll-y show-scrollbar="{{false}}"> <scroll-view class="travel-location-editor" type="custom" scroll-y show-scrollbar="{{false}}">
<view class="content"> <view class="content">
<view wx:if="{{isLoading}}" class="loading"> <view wx:if="{{isLoading}}" class="loading">
<t-loading theme="dots" size="40rpx" /> <t-loading theme="dots" size="40rpx" />
<text class="loading-text">加载中...</text> <text class="text">加载中...</text>
</view> </view>
<block wx:else> <block wx:else>
<t-cell-group class="section"> <t-cell-group class="section">
<t-cell title="地点类型"> <view slot="title" class="title">位置信息</view>
<view slot="right-icon"> <t-cell title="地点类型" arrow bind:click="showLocationTypePicker">
<picker <view slot="note" class="black">{{locationTypes[locationTypeIndex].label}}</view>
class="picker"
mode="selector"
range="{{locationTypes}}"
range-key="label"
value="{{locationTypeIndex}}"
bindchange="onChangeLocationType"
>
<view class="slot">
<text>{{locationTypes[locationTypeIndex].label}}</text>
<t-icon name="chevron-right" size="20px" class="icon" />
</view>
</picker>
</view>
</t-cell> </t-cell>
<t-cell title="位置" required bind:click="chooseLocation"> <t-cell class="location" required arrow bind:click="chooseLocation">
<view slot="right-icon"> <view slot="title" class="title">位置</view>
<view class="location-slot"> <view slot="note" class="black">{{location}}</view>
<text wx:if="{{location}}" class="location-text">{{location}}</text>
<text wx:else class="location-placeholder">点击选择位置</text>
<t-icon name="chevron-right" size="20px" class="icon" />
</view>
</view>
</t-cell> </t-cell>
</t-cell-group> </t-cell-group>
<t-cell-group class="section"> <t-cell-group class="section">
<view slot="title" class="title">基本信息</view>
<t-input <t-input
class="input" class="input"
placeholder="请输入地点名称" placeholder="请输入地点名称"
@ -57,6 +40,7 @@
</t-textarea> </t-textarea>
</t-cell-group> </t-cell-group>
<t-cell-group class="section"> <t-cell-group class="section">
<view slot="title" class="title">详细信息</view>
<t-input <t-input
model:value="{{amount}}" model:value="{{amount}}"
placeholder="0" placeholder="0"
@ -64,7 +48,27 @@
suffix="元" suffix="元"
align="right" align="right"
/> />
<t-cell title="必要评分"> <t-cell title="需要身份证">
<view slot="right-icon">
<switch checked="{{requireIdCard}}" bindchange="onChangeRequireIdCard" />
</view>
</t-cell>
<t-cell title="需要预约">
<view slot="right-icon">
<switch checked="{{requireAppointment}}" bindchange="onChangeRequireAppointment" />
</view>
</t-cell>
<t-cell title="重要程度">
<view slot="right-icon">
<t-rate
value="{{importance}}"
count="{{5}}"
size="24px"
bind:change="onChangeImportance"
/>
</view>
</t-cell>
<t-cell title="评分">
<view slot="right-icon"> <view slot="right-icon">
<t-rate <t-rate
value="{{score}}" value="{{score}}"
@ -74,11 +78,6 @@
/> />
</view> </view>
</t-cell> </t-cell>
<t-cell title="需要身份证">
<view slot="right-icon">
<switch checked="{{requireIdCard}}" bindchange="onChangeRequireIdCard" />
</view>
</t-cell>
</t-cell-group> </t-cell-group>
<view class="section media"> <view class="section media">
<view class="gallery"> <view class="gallery">
@ -187,7 +186,7 @@
<t-icon <t-icon
class="delete" class="delete"
name="close" name="close"
bindtap="deleteMedia" bindtap="deleteNewMedia"
data-index="{{index}}" data-index="{{index}}"
data-new-media="{{true}}" data-new-media="{{true}}"
/> />
@ -240,3 +239,34 @@
</block> </block>
</view> </view>
</scroll-view> </scroll-view>
<!-- 地点类型选择器 -->
<t-picker
visible="{{locationTypePickerVisible}}"
value="{{locationTypeIndex}}"
cancelBtn="取消"
confirmBtn="确认"
bind:confirm="onPickerConfirm"
bind:cancel="onPickerCancel"
>
<t-picker-item options="{{locationTypes}}" />
</t-picker>
<!-- 删除确认对话框 -->
<t-dialog
visible="{{deleteDialogVisible}}"
title="删除地点"
confirm-btn="{{ {content: '删除', variant: 'text', theme: 'danger'} }}"
cancel-btn="取消"
bind:confirm="confirmDelete"
bind:cancel="cancelDelete"
>
<view slot="content" class="delete-dialog">
<view class="tips">
<text>此地点的图片和视频也会同步删除,删除后无法恢复,请输入 "</text>
<text style="color: var(--theme-error)">确认删除</text>
<text>" 以继续</text>
</view>
<t-input placeholder="请输入:确认删除" model:value="{{deleteConfirmText}}" />
</view>
</t-dialog>

View File

@ -18,32 +18,31 @@
} }
} }
.travel-list { .travels {
width: 100vw; width: 100vw;
padding: 16rpx; padding: 16rpx;
min-height: 100vh;
box-sizing: border-box; box-sizing: border-box;
padding-bottom: 120rpx; padding-bottom: 120rpx;
.travel-card { .travel {
background: var(--theme-bg-card);
margin-bottom: 24rpx;
border-radius: 16rpx;
overflow: hidden; overflow: hidden;
box-shadow: 0 2px 12px var(--theme-shadow-light); box-shadow: 0 2px 12px var(--theme-shadow-light);
transition: all .3s; transition: all .3s;
background: var(--theme-bg-card);
margin-bottom: 24rpx;
border-radius: 16rpx;
&:active { &:active {
transform: scale(.98); transform: scale(.98);
box-shadow: 0 2px 8px var(--theme-shadow-light); box-shadow: 0 2px 8px var(--theme-shadow-light);
} }
.card-header { .header {
padding: 24rpx; padding: 24rpx;
border-bottom: 1px solid var(--theme-border-light); border-bottom: 1px solid var(--theme-border-light);
} }
.card-body { .body {
padding: 24rpx; padding: 24rpx;
.title { .title {
@ -71,7 +70,7 @@
display: flex; display: flex;
flex-wrap: wrap; flex-wrap: wrap;
.meta-item { .item {
gap: 8rpx; gap: 8rpx;
display: flex; display: flex;
align-items: center; align-items: center;

View File

@ -47,25 +47,24 @@
</view> </view>
<!-- 旅行列表 --> <!-- 旅行列表 -->
<view class="travel-list"> <view class="travels">
<!-- 空状态 --> <!-- 空状态 -->
<t-empty <t-empty
wx:if="{{!isFetching && list.length === 0}}" wx:if="{{!isFetching && list.length === 0}}"
icon="travel" icon="travel"
description="暂无旅行计划" description="暂无旅行计划"
/> />
<!-- 列表内容 --> <!-- 列表内容 -->
<view <view
wx:for="{{list}}" wx:for="{{list}}"
wx:for-item="travel" wx:for-item="travel"
wx:for-index="travelIndex" wx:for-index="travelIndex"
wx:key="id" wx:key="id"
class="travel-card" class="travel"
bind:tap="toDetail" bind:tap="toDetail"
data-id="{{travel.id}}" data-id="{{travel.id}}"
> >
<view class="card-header"> <view class="header">
<t-tag <t-tag
theme="{{travel.status === 'PLANNING' ? 'default' : travel.status === 'ONGOING' ? 'warning' : 'success'}}" theme="{{travel.status === 'PLANNING' ? 'default' : travel.status === 'ONGOING' ? 'warning' : 'success'}}"
variant="light" variant="light"
@ -74,31 +73,25 @@
{{statusLabels[travel.status]}} {{statusLabels[travel.status]}}
</t-tag> </t-tag>
</view> </view>
<view class="body">
<view class="card-body">
<view class="title">{{travel.title || '未命名旅行'}}</view> <view class="title">{{travel.title || '未命名旅行'}}</view>
<view wx:if="{{travel.content}}" class="content">{{travel.content}}</view> <view wx:if="{{travel.content}}" class="content">{{travel.content}}</view>
<view class="meta"> <view class="meta">
<view class="meta-item"> <view class="item">
<t-icon name="time" size="16px" class="icon" /> <t-icon name="time" size="16px" class="icon" />
<text class="text">{{travel.travelDate}} {{travel.travelTime}}</text> <text class="text">{{travel.travelDate}} {{travel.travelTime}}</text>
</view> </view>
<view wx:if="{{travel.days}}" class="item">
<view wx:if="{{travel.days}}" class="meta-item">
<t-icon name="calendar" size="16px" class="icon" /> <t-icon name="calendar" size="16px" class="icon" />
<text class="text">{{travel.days}} 天</text> <text class="text">{{travel.days}} 天</text>
</view> </view>
<view wx:if="{{travel.transportationType}}" class="item">
<view wx:if="{{travel.transportationType}}" class="meta-item">
<t-icon name="{{travel.transportationType === 'PLANE' ? 'flight-takeoff' : travel.transportationType === 'TRAIN' ? 'map-route' : travel.transportationType === 'SELF_DRIVING' ? 'control-platform' : 'location'}}" size="16px" class="icon" /> <t-icon name="{{travel.transportationType === 'PLANE' ? 'flight-takeoff' : travel.transportationType === 'TRAIN' ? 'map-route' : travel.transportationType === 'SELF_DRIVING' ? 'control-platform' : 'location'}}" size="16px" class="icon" />
<text class="text">{{transportLabels[travel.transportationType]}}</text> <text class="text">{{transportLabels[travel.transportationType]}}</text>
</view> </view>
</view> </view>
</view> </view>
</view> </view>
<!-- 加载完成提示 --> <!-- 加载完成提示 -->
<view wx:if="{{isFinished && 0 < list.length}}" class="finished">没有更多了</view> <view wx:if="{{isFinished && 0 < list.length}}" class="finished">没有更多了</view>
</view> </view>

View File

@ -1,14 +1,14 @@
{ {
"light": { "light": {
"navigationBarBackgroundColor": "#FFFFFF", "navigationBarBackgroundColor": "#FFF",
"navigationBarTextStyle": "black", "navigationBarTextStyle": "black",
"backgroundColor": "#FFFFFF", "backgroundColor": "#EDEDED",
"backgroundTextStyle": "dark", "backgroundTextStyle": "#111",
"backgroundColorTop": "#FFFFFF", "backgroundColorTop": "#F00",
"backgroundColorBottom": "#FFFFFF", "backgroundColorBottom": "#EDEDED",
"tabBarColor": "#8a8a8a", "tabBarColor": "#8A8A8A",
"tabBarSelectedColor": "#07C160", "tabBarSelectedColor": "#07C160",
"tabBarBackgroundColor": "#ffffff", "tabBarBackgroundColor": "#FFF",
"tabBarBorderStyle": "white", "tabBarBorderStyle": "white",
"tabBarIconJournal": "assets/icon/light/journal.png", "tabBarIconJournal": "assets/icon/light/journal.png",
"tabBarIconJournalActive": "assets/icon/light/journal_active.png", "tabBarIconJournalActive": "assets/icon/light/journal_active.png",
@ -24,11 +24,11 @@
"dark": { "dark": {
"navigationBarBackgroundColor": "#1A1A1A", "navigationBarBackgroundColor": "#1A1A1A",
"navigationBarTextStyle": "white", "navigationBarTextStyle": "white",
"backgroundColor": "#1A1A1A", "backgroundColor": "#111",
"backgroundTextStyle": "light", "backgroundTextStyle": "light",
"backgroundColorTop": "#1A1A1A", "backgroundColorTop": "#1A1A1A",
"backgroundColorBottom": "#1A1A1A", "backgroundColorBottom": "#1A1A1A",
"tabBarColor": "#aaaaaa", "tabBarColor": "#AAA",
"tabBarSelectedColor": "#07C160", "tabBarSelectedColor": "#07C160",
"tabBarBackgroundColor": "#1A1A1A", "tabBarBackgroundColor": "#1A1A1A",
"tabBarBorderStyle": "black", "tabBarBorderStyle": "black",

View File

@ -9,6 +9,7 @@ page {
--theme-wx: #07C160; --theme-wx: #07C160;
/* === 背景色 === */ /* === 背景色 === */
--theme-bg-wx: #EDEDED;
--theme-bg-primary: #FFF; --theme-bg-primary: #FFF;
--theme-bg-secondary: #F5F5F5; --theme-bg-secondary: #F5F5F5;
--theme-bg-card: #FFF; --theme-bg-card: #FFF;
@ -63,6 +64,7 @@ page {
--theme-wx: #07C160; --theme-wx: #07C160;
/* === 背景色 === */ /* === 背景色 === */
--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: #2C2C2C;

59
miniprogram/timi-web.less Normal file
View File

@ -0,0 +1,59 @@
@tuiColors: {
red: #F33;
pink: #FF7A9B;
black: #111;
blue: #006EFF;
light-blue: #00A6FF;
green: GREEN;
orange: #E7913B;
gray: #666;
light-gray: #AAA;
white: #FFF;
dark-white: #E7EAEF;
yellow: #FF0;
purple: PURPLE;
}
:root {
--tui-font: SimSun, PingFang SC, Microsoft YaHei, Arial Regular;
--tui-cur-default: url("../img/default.png"), default;
--tui-cur-pointer: url("../img/link.png"), pointer;
--tui-cur-text: url("../img/input.png"), text;
--tui-shadow: 3px 3px 0 var(--tui-shadow-color);
--tui-bezier: cubic-bezier(.19, .1, .22, 1);
--tui-shadow-color: rgba(0, 0, 0, .2);
each(@tuiColors, {
--tui-@{key}: @value;
});
--tui-border: 1px solid var(--tui-light-gray);
/* 等级对应颜色 */
--tui-level-0: #BFBFBF;
--tui-level-1: #BFBFBF;
--tui-level-2: #95DDB2;
--tui-level-3: #92D1E5;
--tui-level-4: #FFB37C;
--tui-level-5: #FF6C00;
--tui-level-6: #F00;
--tui-level-7: #E52FEC;
--tui-level-8: #841CF9;
--tui-level-9: #151515;
--tui-page-padding: .5rem 1rem;
}
// 字体颜色
each(@tuiColors, {
.@{key} {
color: @value;
}
});
// 背景颜色
each(@tuiColors, {
.bg-@{key} {
background: @value;
}
});

View File

@ -137,9 +137,24 @@ export interface TravelLocation extends Model {
/** 是否需要身份证 */ /** 是否需要身份证 */
requireIdCard?: boolean; requireIdCard?: boolean;
/** 必要评分 */ /** 是否需要预约 */
requireAppointment?: boolean;
/** 首次出行时间戳 */
firstTraveledAt?: number;
/** 上次出行时间戳 */
lastTraveledAt?: number;
/** 出行次数 */
travelCount?: number;
/** 评分 */
score?: number; score?: number;
/** 重要程度 */
importance?: number;
/** 附件 */ /** 附件 */
items?: Attachment[]; items?: Attachment[];