@@ -1,395 +0,0 @@
< script setup lang = "ts" >
import type { PoiResolved } from '@/domain/poi'
import PageState from '@/components/poi/PageState.vue'
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'
poi . value = null
try {
if ( ! poiId . trim ( ) ) {
state . value = 'invalid'
return
}
const result = getPoiRepository ( ) . getPoiById ( poiId )
if ( ! result ) {
state . value = 'invalid'
return
}
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 ) {
console . error ( 'Failed to load POI detail' , error )
state . value = 'error'
}
}
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 : ( ) => {
uni . reLaunch ( { url : '/pages/map/index' } )
} ,
} )
}
function formatCoordinate ( value : number ) : string {
return value . toFixed ( 6 )
}
function formatUpdatedAt ( value : string ) : string {
const date = new Date ( value )
if ( Number . isNaN ( date . getTime ( ) ) )
return '待确认'
const year = date . getFullYear ( )
const month = String ( date . getMonth ( ) + 1 ) . padStart ( 2 , '0' )
const day = String ( date . getDate ( ) ) . padStart ( 2 , '0' )
return ` ${ year } - ${ month } - ${ day } `
}
onLoad ( ( query ) => {
const rawPoiId = typeof query ? . poiId === 'string' ? query . poiId : ''
try {
loadPoi ( decodeURIComponent ( rawPoiId ) )
}
catch {
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 >
< view class = "poi-detail-page" >
< PageState v-if = "state === 'loading'" state="loading" title="正在加载点位信息…" / >
< PageState
v-else-if = "state === 'invalid'"
state = "empty"
title = "点位不存在或已下线"
description = "该点位可能已更新,请返回地图重新选择"
action -label = " 返回地图 "
@action ="returnToMap"
/ >
< PageState
v-else-if = "state === 'error'"
state = "error"
title = "点位信息加载失败"
description = "暂时无法读取该点位,请稍后返回重试"
action -label = " 返回地图 "
@action ="returnToMap"
/ >
< template v-else-if = "poi" >
< PoiHeroGallery :images = "poi.images" :name = "poi.name" / >
< view class = "poi-detail-page__content" >
< view class = "poi-detail-page__header" >
< text class = "poi-detail-page__category" > { { poi . category . name } } < / text >
< text class = "poi-detail-page__name" > { { poi . name } } < / text >
< view class = "poi-detail-page__recommendation" >
{ { formatRecommendationLabel ( poi . recommendationSource , poi . recommendationIndex ) } }
< / view >
< 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 >
< text class = "info-row__value" > { { poi . openingHoursText } } < / text >
< / view >
< view class = "info-row" >
< text class = "info-row__label" > 地址 < / text >
< text class = "info-row__value" > { { poi . address } } < / text >
< / view >
< view class = "info-row info-row--coordinates" >
< text class = "info-row__label" > 经纬度 < / text >
< view class = "info-row__value info-row__coordinates" >
< text > 经度 { { formatCoordinate ( poi . longitude ) } } < / text >
< text > 纬度 { { formatCoordinate ( poi . latitude ) } } < / text >
< text class = "info-row__coordinate-system" > 坐标系 { { poi . coordinateSystem } } < / text >
< / view >
< / view >
< view class = "info-row" >
< text class = "info-row__label" > 数据更新 < / text >
< text class = "info-row__value" > { { formatUpdatedAt ( poi . updatedAt ) } } < / text >
< / view >
< / view >
< view class = "description-section" >
< text class = "description-section__title" > 点位简介 < / text >
< text class = "description-section__body" > { { poi . description } } < / text >
< / view >
< view class = "poc-notice" >
< text class = "poc-notice__title" > POC 数据说明 < / text >
< text class = "poc-notice__body" > 当前内容用于产品和技术可行性验证 , 正式开放信息请以后续审核版本为准 。 < / text >
< / view >
< / view >
< / template >
< / view >
< / template >
< route lang = "yaml" >
layout : map
style :
navigationBarTitleText : 点位详情
navigationBarBackgroundColor : '#ffffff'
navigationBarTextStyle : black
backgroundColor : '#f5f7f6'
< / route >
< style scoped lang = "scss" >
. poi - detail - page {
min - height : 100 vh ;
background : # f5f7f6 ;
}
. poi - detail - page _ _content {
padding : 32 rpx 32 rpx calc ( 32 rpx + env ( safe - area - inset - bottom ) ) ;
}
. poi - detail - page _ _header {
display : flex ;
flex - direction : column ;
}
. poi - detail - page _ _category {
align - self : flex - start ;
padding : 8 rpx 18 rpx ;
font - size : 24 rpx ;
font - weight : 600 ;
line - height : 34 rpx ;
color : # 0 e6247 ;
background : # e8f4ee ;
border - radius : 24 rpx ;
}
. poi - detail - page _ _name {
margin - top : 16 rpx ;
font - size : 40 rpx ;
font - weight : 700 ;
line - height : 56 rpx ;
color : # 18201 d ;
}
. poi - detail - page _ _recommendation {
align - self : flex - start ;
padding : 8 rpx 16 rpx ;
margin - top : 16 rpx ;
font - size : 26 rpx ;
font - weight : 600 ;
line - height : 38 rpx ;
color : # 934 b00 ;
background : # fff3e0 ;
border - radius : 10 rpx ;
}
. poi - detail - page _ _tags {
margin - top : 18 rpx ;
}
. info - card ,
. check - in - entry ,
. description - section ,
. poc - notice {
padding : 28 rpx ;
margin - top : 24 rpx ;
background : # fff ;
border : 1 rpx solid # dde5e1 ;
border - radius : 20 rpx ;
}
. check - in - entry {
display : flex ;
gap : 18 rpx ;
align - items : center ;
padding : 24 rpx ;
}
. check - in - entry -- done {
background : # e8f4ee ;
border - color : # cfe4d9 ;
}
. check - in - entry _ _mark {
display : flex ;
flex : none ;
align - items : center ;
justify - content : center ;
width : 62 rpx ;
height : 62 rpx ;
font - size : 28 rpx ;
font - weight : 800 ;
color : # fff ;
background : # 167 a5b ;
border - radius : 50 % ;
}
. check - in - entry _ _copy {
display : flex ;
flex : 1 ;
flex - direction : column ;
min - width : 0 ;
font - size : 21 rpx ;
line - height : 32 rpx ;
color : # 5 e6b65 ;
}
. check - in - entry _ _title {
margin - bottom : 3 rpx ;
font - size : 27 rpx ;
font - weight : 750 ;
color : # 18201 d ;
}
. check - in - entry button {
flex : none ;
width : auto ;
height : 60 rpx ;
padding : 0 22 rpx ;
margin : 0 ;
font - size : 23 rpx ;
font - weight : 700 ;
line - height : 60 rpx ;
color : # fff ;
background : # 167 a5b ;
border : 0 ;
border - radius : 30 rpx ;
}
. check - in - entry button : : after {
border : 0 ;
}
. info - row {
display : flex ;
gap : 24 rpx ;
align - items : flex - start ;
padding : 20 rpx 0 ;
border - bottom : 1 rpx solid # edf1ef ;
}
. info - row : first - child {
padding - top : 0 ;
}
. info - row : last - child {
padding - bottom : 0 ;
border - bottom : 0 ;
}
. info - row _ _label {
flex : none ;
width : 128 rpx ;
font - size : 26 rpx ;
line - height : 40 rpx ;
color : # 5 e6b65 ;
}
. info - row _ _value {
flex : 1 ;
font - size : 28 rpx ;
line - height : 40 rpx ;
color : # 18201 d ;
}
. info - row _ _coordinates {
display : flex ;
flex - direction : column ;
min - width : 0 ;
}
. info - row _ _coordinate - system {
margin - top : 6 rpx ;
font - size : 24 rpx ;
color : # 8 a9690 ;
}
. description - section _ _title ,
. poc - notice _ _title {
display : block ;
font - size : 30 rpx ;
font - weight : 700 ;
line - height : 42 rpx ;
color : # 18201 d ;
}
. description - section _ _body ,
. poc - notice _ _body {
display : block ;
margin - top : 16 rpx ;
font - size : 28 rpx ;
line - height : 44 rpx ;
color : # 425049 ;
white - space : pre - wrap ;
}
. poc - notice {
background : # e8f4ee ;
border - color : # cfe4d9 ;
}
. poc - notice _ _title {
font - size : 26 rpx ;
color : # 0 e6247 ;
}
. poc - notice _ _body {
font - size : 24 rpx ;
line - height : 38 rpx ;
color : # 3 d5a4d ;
}
< / style >