Add check-in feature and update travel planner UI

This commit is contained in:
周瑞哲
2026-07-31 12:50:14 +08:00
parent ac4179086c
commit 3ef7266591
44 changed files with 2919 additions and 144 deletions
+108
View File
@@ -5,9 +5,12 @@ import PoiHeroGallery from '@/components/poi/PoiHeroGallery.vue'
import PoiTagList from '@/components/poi/PoiTagList.vue'
import { getPoiRepository } from '@/data/poi'
import { formatRecommendationLabel } from '@/domain/poi'
import { getCheckInProfile } from '@/services/check-in'
const state = ref<'loading' | 'ready' | 'invalid' | 'error'>('loading')
const poi = ref<PoiResolved | null>(null)
const hasCheckedIn = ref(false)
const checkInStorageError = ref(false)
function loadPoi(poiId: string) {
state.value = 'loading'
@@ -26,6 +29,15 @@ function loadPoi(poiId: string) {
}
poi.value = result
try {
hasCheckedIn.value = getCheckInProfile().checkIns.some(record => record.poiId === result.id)
checkInStorageError.value = false
}
catch (error) {
console.error('Failed to load POI check-in state', error)
hasCheckedIn.value = false
checkInStorageError.value = true
}
state.value = 'ready'
}
catch (error) {
@@ -34,6 +46,16 @@ function loadPoi(poiId: string) {
}
}
function openCheckIn() {
if (!poi.value)
return
uni.navigateTo({ url: `/pages/check-in/index?poiId=${encodeURIComponent(poi.value.id)}` })
}
function openCheckInRecords() {
uni.navigateTo({ url: '/pages/check-in/records' })
}
function returnToMap() {
uni.navigateBack({
fail: () => {
@@ -65,6 +87,20 @@ onLoad((query) => {
state.value = 'invalid'
}
})
onShow(() => {
if (!poi.value)
return
try {
hasCheckedIn.value = getCheckInProfile().checkIns.some(record => record.poiId === poi.value?.id)
checkInStorageError.value = false
}
catch (error) {
console.error('Failed to refresh POI check-in state', error)
hasCheckedIn.value = false
checkInStorageError.value = true
}
})
</script>
<template>
@@ -100,6 +136,15 @@ onLoad((query) => {
<PoiTagList class="poi-detail-page__tags" :tags="poi.tags" />
</view>
<view class="check-in-entry" :class="{ 'check-in-entry--done': hasCheckedIn }">
<view class="check-in-entry__mark">{{ hasCheckedIn ? '✓' : '◎' }}</view>
<view class="check-in-entry__copy">
<text class="check-in-entry__title">{{ checkInStorageError ? '打卡数据需要处理' : hasCheckedIn ? '已留下光明足迹' : '到点打卡' }}</text>
<text>{{ checkInStorageError ? '本机打卡数据异常,请先处理' : hasCheckedIn ? '该点位已在本机完成打卡' : '到达点位附近后,使用本次真实定位打卡' }}</text>
</view>
<button @click="checkInStorageError ? openCheckInRecords() : openCheckIn()">{{ checkInStorageError ? '去处理' : hasCheckedIn ? '查看' : '去打卡' }}</button>
</view>
<view class="info-card">
<view class="info-row">
<text class="info-row__label">开放时间</text>
@@ -197,6 +242,7 @@ style:
}
.info-card,
.check-in-entry,
.description-section,
.poc-notice {
padding: 28rpx;
@@ -206,6 +252,68 @@ style:
border-radius: 20rpx;
}
.check-in-entry {
display: flex;
gap: 18rpx;
align-items: center;
padding: 24rpx;
}
.check-in-entry--done {
background: #e8f4ee;
border-color: #cfe4d9;
}
.check-in-entry__mark {
display: flex;
flex: none;
align-items: center;
justify-content: center;
width: 62rpx;
height: 62rpx;
font-size: 28rpx;
font-weight: 800;
color: #fff;
background: #167a5b;
border-radius: 50%;
}
.check-in-entry__copy {
display: flex;
flex: 1;
flex-direction: column;
min-width: 0;
font-size: 21rpx;
line-height: 32rpx;
color: #5e6b65;
}
.check-in-entry__title {
margin-bottom: 3rpx;
font-size: 27rpx;
font-weight: 750;
color: #18201d;
}
.check-in-entry button {
flex: none;
width: auto;
height: 60rpx;
padding: 0 22rpx;
margin: 0;
font-size: 23rpx;
font-weight: 700;
line-height: 60rpx;
color: #fff;
background: #167a5b;
border: 0;
border-radius: 30rpx;
}
.check-in-entry button::after {
border: 0;
}
.info-row {
display: flex;
gap: 24rpx;