Browse Source

📦 非遗路线优化

快乐的梦鱼 3 weeks ago
parent
commit
a43958f0ba

File diff suppressed because it is too large
+ 13 - 0
src/App.vue


+ 1 - 1
src/api/traval/TravalContent.ts

@@ -41,7 +41,7 @@ export class TravalContentApi extends CommonContentApi {
       pid: 8768,
       modelId: 17,
     });
-    const res = await this.getContentList(params, 1, 100, GetContentDetailItem);
+    const res = await this.getContentList(params, 1, 100);
     res.list.forEach((p) => {
       if (p.scenicSpotsList) {
         p.scenicSpotsList = transformArrayDataModel<TravalListItem>(TravalListItem, p.scenicSpotsList as any[], '路线景点列表', true);

+ 9 - 8
src/components/dialog/BottomSheet.vue

@@ -20,11 +20,7 @@
     >
       <view 
         v-if="enableDrag"
-        :style="{
-          display: 'flex',
-          justifyContent: 'center',
-          alignItems: 'center',
-        }"
+        :style="themeStyles.dragHandleContainer.value"
         @touchstart="onDragStart"
         @touchmove="onDragMove"
         @touchend="onDragEnd"
@@ -113,7 +109,7 @@ const emit = defineEmits([ 'close', 'select' ]);
 const props = withDefaults(defineProps<BottomSheetProps>(), {
   enableDrag: true,
   dragHandleColor: () => propGetThemeVar('BottomSheetDragHandleColor', 'grey'),
-  dragHandleSize: () => propGetThemeVar('BottomSheetDragHandleSize', 100),
+  dragHandleSize: () => propGetThemeVar('BottomSheetDragHandleSize', 200),
   centerWidth: () => propGetThemeVar('BottomSheetCenterWidth', '600rpx'),
   height: () => propGetThemeVar('BottomSheetHeight', 300),
   dragMaxHeight: () => propGetThemeVar('BottomSheetDragMaxHeight', 1000),
@@ -126,11 +122,16 @@ const themeStyles = themeContext.useThemeStyles({
     borderTopRightRadius: DynamicSize('BottomSheetBorderRadius', 10),
     backgroundColor: DynamicColor('BottomSheetBackgroundColor', 'white'),
   },
+  dragHandleContainer: {
+    display: 'flex',
+    justifyContent: 'center',
+    alignItems: 'center',
+  },
   dragHandle: {
     height: DynamicSize('BottomSheetDragHandleSize', 10),
     borderRadius: DynamicSize('BottomSheetDragHandleBorderRadius', 10),
-    marginTop: DynamicSize('BottomSheetDragHandleMarginVertical', 15),
-    marginBottom: DynamicSize('BottomSheetDragHandleMarginVertical', 15),
+    marginTop: DynamicSize('BottomSheetDragHandleMarginVertical', 25),
+    marginBottom: DynamicSize('BottomSheetDragHandleMarginVertical', 25),
   },
 });
 

+ 6 - 0
src/pages.json

@@ -63,6 +63,12 @@
       }
     },
     {
+      "path": "pages/travel/route/details",
+      "style": {
+        "navigationBarTitleText": "非遗路线详情"
+      }
+    },
+    {
       "path": "pages/travel/route/travel-route",
       "style": {
         "navigationBarTitleText": "路线地图",

+ 50 - 0
src/pages/travel/route/components/NextBestWay.vue

@@ -0,0 +1,50 @@
+<template>
+  <FlexRow align="center" :margin="[0,0,10,100]" :gap="12">
+    <template v-if="canShow">
+      <template v-if="userPreferredWay">
+        <Tag :text="getRouteToNextBestWayText(finalWay)" touchable @click="emit('focusRoute')" />
+        <Text :text="formatDistance(toNextRoute[finalWay]?.distance)" />
+        <Text :text="formatDuration(toNextRoute[finalWay]?.duration)" />
+      </template>
+      <template v-else>
+        <Tag :text="'推荐 ' + getRouteToNextBestWayText(finalWay)" touchable @click="emit('focusRoute')" />
+        <Text :text="formatDistance(toNextRoute[finalWay]?.distance)" />
+        <Text :text="formatDuration(toNextRoute[finalWay]?.duration)" />
+      </template>
+    </template>
+  </FlexRow>
+  <slot name="extra" :finalWay="finalWay" />
+</template>
+
+<script setup lang="ts">
+import { formatDistance, formatDuration, getRouteToNextBestWayText, type RouteInfo, type RouteToNextBestWay } from '@/api/traval/RouteApi';
+import Tag from '@/components/display/Tag.vue';
+import FlexRow from '@/components/layout/FlexRow.vue';
+import { computed } from 'vue';
+
+const props = defineProps<{
+  toNextRoute: RouteInfo['items'][number]['toNextRoute'],
+  toNextBestWay?: RouteToNextBestWay|'',
+  userPreferredWay?: RouteToNextBestWay|'',
+}>();
+
+const emit = defineEmits([ 'focusRoute' ]);
+
+const canShow = computed(() => props.toNextRoute && Object.keys(props.toNextRoute).length > 0);
+const finalWay = computed(() => {
+  let way = props.userPreferredWay || props.toNextBestWay || 'transit';
+  if (!props.toNextRoute[way] 
+    || !props.toNextRoute[way].steps?.length 
+    || (props.toNextRoute[way].steps.length ) < 2
+  ) {
+    const keys = Object.keys(props.toNextRoute);
+    for (const key of keys) {
+      if ((props.toNextRoute[key as RouteToNextBestWay].steps?.length || 0) > 1) {
+        way = key as RouteToNextBestWay;
+        break;
+      }
+    }
+  }
+  return way;
+});
+</script>

+ 48 - 0
src/pages/travel/route/details.vue

@@ -0,0 +1,48 @@
+<template>
+  <!-- 景点详情 -->
+  <FlexCol v-if="currentScenicSpot" position="relative" :padding="30" :gap="20">
+    <FlexRow :gap="20" justify="space-between" align="center">
+      <Image :src="currentScenicSpot.image" :radius="20" :width="80" :height="80" mode="aspectFill" />
+      <FlexCol>
+        <Text :fontSize="33" bold>{{ currentScenicSpot.name }}</Text>
+        <Text :fontSize="26" color="text.second">{{ currentScenicSpot.address }}</Text>
+      </FlexCol>
+      <Button
+        text="导航"
+        icon="map"
+        size="small"
+        :radius="40"
+        @click="openLocation(currentScenicSpot)"
+      />
+    </FlexRow>
+    <ImageSwiper :images="currentScenicSpot.images" />
+    <Empty v-if="!currentScenicSpot.intro" :description="currentScenicSpot.name + '暂无简介'" />
+    <Parse v-else :content="currentScenicSpot.intro" />
+  </FlexCol>
+</template>
+
+<script setup lang="ts">
+import type { RouteInfo } from '@/api/traval/RouteApi';
+import Button from '@/components/basic/Button.vue';
+import Image from '@/components/basic/Image.vue';
+import Text from '@/components/basic/Text.vue';
+import Parse from '@/components/display/parse/Parse.vue';
+import Empty from '@/components/feedback/Empty.vue';
+import FlexCol from '@/components/layout/FlexCol.vue';
+import FlexRow from '@/components/layout/FlexRow.vue';
+import ImageSwiper from '@/pages/parts/ImageSwiper.vue';
+import { onMounted, ref } from 'vue';
+
+const currentScenicSpot = ref<RouteInfo['items'][number]['scenicSpot']|null>(null);
+
+function openLocation(item: RouteInfo['items'][number]['scenicSpot']) {
+  uni.openLocation({
+    latitude: item.latitude,
+    longitude: item.longitude,
+  });
+}
+
+onMounted(() => {
+  currentScenicSpot.value = uni.getStorageSync('travelRouteItem');
+});
+</script>

+ 8 - 5
src/pages/travel/route/list.vue

@@ -16,9 +16,9 @@
     </NavBar>
     <SimplePageContentLoader :loader="listLoader">
       <FlexCol backgroundColor="#fdfefe" :gap="30" :radius="35" :padding="40" :margin="[300,0,0,0]">
-        <FlexRow>
+        <FlexRow justify="space-between">
           <SimpleDropDownPicker v-model="selectedRegion" :columns="regionData.content.value" />
-          <SimpleDropDownPicker v-model="selectedType" :columns="typeData.content.value" />
+          <!-- <SimpleDropDownPicker v-model="selectedType" :columns="typeData.content.value" /> -->
           <SearchBar 
             v-model="searchValue"
             placeholder="搜索路线" 
@@ -28,14 +28,16 @@
             @cancel="doSearch" 
           />
         </FlexRow>
-        <FlexCol 
+        <Touchable           
           v-for="(item,index) in listLoader.content.value"
           backgroundColor="white"
           :key="item.id"
           :radius="20"
           :padding="[25,30]"
           :gap="10"
+          direction="column"
           shadow="default"
+          @click="handleGo(item)"
         >
           <FlexRow :gap="20">
             <FlexCol position="relative">
@@ -59,10 +61,10 @@
           </FlexRow>
           <Divider color="border.light" />
           <FlexRow justify="space-between">
-            <Text fontConfig="subSecondText" :text="`热度:${item.views}`" />
+            <Text fontConfig="subSecondText" :text="`${item.regionText} · ${item.keywords?.length || 0} 个景点`" />
             <Button size="small" type="primary" shape="round" :radius="40" text="Let's Go" @click="handleGo(item)" />
           </FlexRow>
-        </FlexCol>
+        </Touchable>
         <Empty
           v-if="listLoader.content.value?.length == 0"
           :description="listLoader.content.value?.length == 0 ? '暂无数据' : ''"
@@ -94,6 +96,7 @@ import { useSimpleDataLoader } from '@/common/composeabe/SimpleDataLoader';
 import CommonContent, { GetContentListParams } from '@/api/CommonContent';
 import Empty from '@/components/feedback/Empty.vue';
 import { waitTimeOut } from '@imengyu/imengyu-utils';
+import Touchable from '@/components/feedback/Touchable.vue';
 
 const listLoader = useSimplePageContentLoader(async () => {
   const res = (await TravalContent.getTravalRouteList(

+ 178 - 131
src/pages/travel/route/travel-route.vue

@@ -16,6 +16,9 @@
         leftButton="back"
       />
     </FlexCol>
+    <FlexCol position="absolute" :bottom="630" :right="30" :zIndex="100">
+      <Button icon="my-location" :iconProps="{ size: 40 }" @click="goToCurrentLocation" />
+    </FlexCol>
     <map
       id="map"
       :markers="markers"
@@ -36,123 +39,124 @@
       :dragSnapHeights="[ 80, 300, 700 ]"
     >
       <template #content> 
-        <!-- 景点详情 -->
-        <FlexCol v-if="currentScenicSpot" position="relative" :padding="30" :gap="20">
-          <FlexRow :gap="20" justify="space-between">
-            <IconButton 
-              icon="arrow-left-bold"
-              shape="round"
-              width="40"
-              height="40"
-              @click="currentScenicSpot = undefined"
-            />
-            <Image :src="currentScenicSpot.image" :radius="20" :width="80" :height="80" mode="aspectFill" />
-            <FlexCol>
-              <Text>{{ currentScenicSpot.name }}</Text>
-              <Text>{{ currentScenicSpot.address }}</Text>
+        <FlexCol v-if="travalDetail" :padding="25" :gap="20">
+          <H2>{{ travalDetail.title }}</H2>
+          <Text>{{ travalDetail.desc }}</Text>
+          <Tabs 
+            v-model:currentIndex="currentTabIndex"
+            :autoItemWidth="false"
+            :tabs="[
+              { text: '路线规划', width: 140 },
+              { text: '路线介绍', width: 140 },
+            ]"
+          />
+          <!-- 路线规划 -->
+          <FlexCol v-if="currentTabIndex === 0" :gap="20">
+            <FlexCol :gap="20">
+              <Text color="text.second">导航偏好</Text>
+              <scroll-view :scroll-x="true" :style="{ width: '100%' }">
+                <FlexRow :gap="10">
+                  <Button 
+                    v-for="way in preferredWays" 
+                    :key="way" 
+                    :text="getRouteToNextBestWayText(way as RouteToNextBestWay)"
+                    :type="way === userPreferredWay ? 'primary' : 'default'"
+                    @click="(userPreferredWay = way as RouteToNextBestWay)" 
+                  />
+                </FlexRow>
+              </scroll-view>
             </FlexCol>
-            <Button
-              text="导航"
-              icon="map"
-              size="large"
-              shape="round"
-              :radius="40"
-              @click="openLocation(currentScenicSpot)"
-            />
-          </FlexRow>
-          <scroll-view :scroll-x="true" :style="{ height: '100%' }">
-            <FlexRow :gap="10">
-              <Image 
-                v-for="image in currentScenicSpot.images" 
-                :key="image" 
-                :src="image"
-                :radius="20"
-                :width="600"
-                :height="280"  
-                mode="aspectFill"
-                touchable
-                @click="previewImage(image, currentScenicSpot.images)"
-              />
-            </FlexRow>
-          </scroll-view>
-          <Parse :content="currentScenicSpot.intro" />
-        </FlexCol>
-        <!-- 路线规划 -->
-        <FlexCol v-else-if="travalDetail" :padding="25" :gap="20">
-          <FlexCol>
-            <H2>{{ travalDetail.title }}</H2>
-            <Text>{{ travalDetail.desc }}</Text>
-          </FlexCol>
-          <FlexCol :gap="20">
-            <Text color="text.second">导航偏好</Text>
-            <scroll-view :scroll-x="true" :style="{ width: '100%' }">
-              <FlexRow :gap="10">
-                <Button 
-                  v-for="way in preferredWays" 
-                  :key="way" 
-                  :text="getRouteToNextBestWayText(way as RouteToNextBestWay)"
-                  :type="way === userPreferredWay ? 'primary' : 'default'"
-                  @click="(userPreferredWay = way as RouteToNextBestWay)" 
-                />
-              </FlexRow>
-            </scroll-view>
-          </FlexCol>
-          <FlexCol :gap="20">
-            <Text color="text.second">路线规划</Text>
-            <Touchable 
-              v-for="item in routeInfo?.items || []" :key="item.id" 
-              :gap="20"
-              direction="column" 
-              @click="onItemClick(item)"
-            >
-              <FlexRow :gap="20">
-                <Image :src="item.scenicSpot.image" :radius="20" :width="80" :height="80" mode="aspectFill" />
-                <FlexCol>
-                  <Text bold :color="routeColors[item.atDay]">第 {{ item.atDay }} 天</Text>
-                  <Text>{{ item.scenicSpot.name }}</Text>
-                  <Text color="text.second">{{ item.scenicSpot.address }}</Text>
-                </FlexCol>
-              </FlexRow>
-              <FlexRow align="center" :margin="[0,0,10,100]" :gap="12">
-                <template v-if="item.toNextRoute && item.toNextRoute['transit']">
-                  <template v-if="userPreferredWay">
-                    <Tag :text="getRouteToNextBestWayText(userPreferredWay || 'transit')" touchable @click="focusRoute(item)" />
-                    <Text :text="formatDistance(item.toNextRoute[userPreferredWay || 'transit']?.distance)" />
-                    <Text :text="formatDuration(item.toNextRoute[userPreferredWay || 'transit']?.duration)" />
-                  </template>
-                  <template v-else>
-                    <Tag :text="'推荐 ' + getRouteToNextBestWayText(item.toNextBestWay || 'transit')" touchable @click="focusRoute(item)" />
-                    <Text :text="formatDistance(item.toNextRoute[item.toNextBestWay || 'transit']?.distance)" />
-                    <Text :text="formatDuration(item.toNextRoute[item.toNextBestWay || 'transit']?.duration)" />
-                  </template>
-                </template>
-              </FlexRow>
-              <!-- 公交地铁路线规划 -->
+            <FlexCol :gap="20">
+              <Text color="text.second">路线规划</Text>
               <FlexCol 
-                v-if="item.toNextRoute && item.toNextRoute['transit'] && (userPreferredWay === 'transit' || item.toNextBestWay === 'transit' || !item.toNextBestWay)" 
-                :gap="8" 
-                :margin="[0,0,0,100]"
+                v-for="item in routeInfo?.items || []" :key="item.id" 
+                :gap="20"
               >
-                <FlexCol v-for="(step, stepIdx) in (item.toNextRoute.transit.steps || [])" :key="stepIdx" :gap="4">
-                  <template v-if="step.mode === 'WALKING'">
-                    <FlexRow :gap="8" align="center">
-                      <Text color="text.second">{{ stepIdx + 1 }}. 步行</Text>
-                      <Text>{{ formatDistance(step.distance) }} · 约 {{ formatDuration(step.duration) }}</Text>
-                    </FlexRow>
-                  </template>
-                  <template v-else-if="step.mode === 'TRANSIT' && step.lines?.length">
-                    <FlexCol v-for="(line, lineIdx) in step.lines" :key="lineIdx" :gap="2">
-                      <FlexRow :gap="8" align="center">
-                        <Text color="text.second">{{ stepIdx + 1 }}. {{ line.vehicle === 'BUS' ? '公交' : line.vehicle === 'SUBWAY' ? '地铁' : line.vehicle === 'RAIL' ? '火车' : '公交' }}</Text>
-                        <Text bold>{{ line.title }}</Text>
-                      </FlexRow>
-                      <Text color="text.second" :margin="[0,0,0,8]">{{ line.geton?.title }} → {{ line.getoff?.title }}</Text>
-                      <Text color="text.second">{{ formatDistance(line.distance) }} · 约 {{ formatDuration(line.duration) }} · {{ line.station_count || 0 }} 站</Text>
+                <Touchable direction="row" justify="space-between" :gap="20" @click="onItemClick(item)">
+                  <FlexRow :gap="20">
+                    <Image :src="item.scenicSpot.image" :radius="20" :width="80" :height="80" mode="aspectFill" />
+                    <FlexCol>
+                      <Text bold :color="routeColors[item.atDay]">第 {{ item.atDay }} 天</Text>
+                      <Text>{{ item.scenicSpot.name }}</Text>
+                      <Text color="text.second">{{ item.scenicSpot.address }}</Text>
+                    </FlexCol>
+                  </FlexRow>
+                  <FlexCol center :gap="10" :innerStyle="{ minWidth: '150rpx' }">
+                    <Button type="text" icon="navigation" text="导航" @click="openLocation(item)" />
+                    <template v-if="currentLocationGeted">
+                      <Text color="text.second" :fontSize="22">距您约</Text>
+                      <Text color="text.second" :fontSize="28">{{ getDistance(item) }}</Text>
+                    </template>
+                  </FlexCol>
+                </Touchable>
+                <NextBestWay 
+                  :toNextRoute="item.toNextRoute"
+                  :toNextBestWay="item.toNextBestWay"
+                  :userPreferredWay="userPreferredWay"
+                  @focusRoute="focusRoute(item)"
+                >
+                  <template #extra="{ finalWay }">
+                    <!-- 公交地铁路线规划 -->
+                    <FlexCol 
+                      v-if="item.toNextRoute && item.toNextRoute['transit'] && finalWay === 'transit'" 
+                      :gap="8" 
+                      :margin="[10,0,10,100]"
+                    >
+                      <FlexCol v-for="(step, stepIdx) in (item.toNextRoute.transit.steps || [])" :key="stepIdx" :gap="4">
+                        <template v-if="step.mode === 'WALKING'">
+                          <FlexRow :gap="8" align="center">
+                            <Tag type="primary" :text="(stepIdx + 1).toString()" />
+                            <Icon icon="walk" :size="40" />
+                            <Text color="text.second">步行</Text>
+                            <Text>{{ formatDistance(step.distance) }} · 约 {{ formatDuration(step.duration) }}</Text>
+                          </FlexRow>
+                        </template>
+                        <template v-else-if="step.mode === 'TRANSIT' && step.lines?.length">
+                          <FlexRow :gap="8" align="center">
+                            <Tag type="success" :text="(stepIdx + 1).toString()" />
+                            <Icon icon="bus" :size="40" />
+                            <Button 
+                              v-if="step.lines?.length > 1" 
+                              type="text"
+                              rightIcon="arrow-down"
+                              :text="step.lines?.length > 1 ? `可搭乘 ${step.lines[0].title} 等 ${step.lines?.length} 条线路` : ''"
+                              @click="(step as any).open=true"
+                            />
+                          </FlexRow>
+                          <CollapseBox :open="(step as any).open || step.lines?.length == 1">
+                            <FlexCol v-for="(line, lineIdx) in step.lines" :key="lineIdx" :gap="10">
+                              <FlexRow :gap="8" align="center">
+                                <template v-if="line.vehicle === 'BUS'">
+                                  <Icon v-if="line.vehicle === 'BUS'" icon="bus" :size="40" />
+                                  <Text color="text.second">公交</Text>
+                                </template>
+                                <template v-else-if="line.vehicle === 'SUBWAY'">
+                                  <Icon icon="subway" :size="40" />
+                                  <Text color="text.second">地铁</Text>
+                                </template>
+                                <template v-else-if="line.vehicle === 'RAIL'">
+                                  <Icon icon="rail" :size="40" />
+                                  <Text color="text.second">火车</Text>
+                                </template>
+                                <Text bold :color="(line as any).line_color || '#000'">{{ line.title }}</Text>
+                                <Text color="text.second" :margin="[0,0,0,8]">{{ line.geton?.title }} → {{ line.getoff?.title }}</Text>
+                              </FlexRow>
+                              <Text color="text.second">约 {{ formatDuration(line.duration) }} · {{ line.station_count || 0 }} 站</Text>
+                            </FlexCol>
+                          </CollapseBox>
+                        </template>
+                      </FlexCol>
                     </FlexCol>
                   </template>
-                </FlexCol>
+                </NextBestWay>
               </FlexCol>
-            </Touchable>
+            </FlexCol>
+            <Height :height="100" />
+          </FlexCol>
+          <!-- 路线介绍 -->
+          <FlexCol v-if="currentTabIndex === 1">
+            <Parse :content="travalDetail.content" />
+            <Height :height="100" />
           </FlexCol>
         </FlexCol>
       </template>
@@ -183,6 +187,12 @@ import Touchable from '@/components/feedback/Touchable.vue';
 import Parse from '@/components/display/parse/Parse.vue';
 import IconButton from '@/components/basic/IconButton.vue';
 import { alert } from '@/components/utils/DialogAction';
+import Tabs from '@/components/nav/Tabs.vue';
+import Icon from '@/components/basic/Icon.vue';
+import CollapseBox from '@/components/display/CollapseBox.vue';
+import NextBestWay from './components/NextBestWay.vue';
+import Height from '@/components/layout/space/Height.vue';
+import { navTo } from '@/components/utils/PageAction';
 
 const markers = ref<any[]>([]);
 /** 地图折线:每条 toNextRoute 一段,points 来自 toNextRoute.xxx.polyline (number[] 为 经度,纬度 交替) */
@@ -196,11 +206,13 @@ const polyline = ref<Array<{
 }>>([]);
 const circles = ref<any[]>([]);
 
+const currentTabIndex = ref(0);
 const mapCtx = uni.createMapContext('map');
+const myLocation = ref<[number, number]>([0, 0]);
 const currentLocation = ref<[number, number]>([0, 0]);
+const currentLocationGeted = ref(false);
 const travalDetail = ref<GetContentDetailItem>();
 const routeInfo = ref<RouteInfo>();
-const currentScenicSpot = ref<RouteInfo['items'][number]['scenicSpot'] | undefined>();
 const userPreferredWay = ref<RouteToNextBestWay|''>('');
 const bottomSheetRef = ref<BottomSheetExpose>();
 
@@ -216,6 +228,44 @@ function getMakerImage(day: number) {
   return `https://mncdn.wenlvti.net/app_static/minnan/images/IcoMaker${day}.png`;
 }
 
+/** 根据两点经纬度计算直线距离(米),Haversine 公式 */
+function getDistanceMeters(
+  lon1: number, lat1: number,
+  lon2: number, lat2: number
+): number {
+  const R = 6371000; // 地球半径 米
+  const dLat = (lat2 - lat1) * Math.PI / 180;
+  const dLon = (lon2 - lon1) * Math.PI / 180;
+  const a =
+    Math.sin(dLat / 2) * Math.sin(dLat / 2) +
+    Math.cos(lat1 * Math.PI / 180) * Math.cos(lat2 * Math.PI / 180) *
+    Math.sin(dLon / 2) * Math.sin(dLon / 2);
+  const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
+  return R * c;
+}
+
+function goToCurrentLocation() {
+  if (!myLocation.value[0] || !myLocation.value[1]) return;
+  currentLocation.value = myLocation.value;
+  mapCtx.moveToLocation({
+    latitude: myLocation.value[1],
+    longitude: myLocation.value[0],
+  });
+}
+function getDistance(item: RouteInfo['items'][number]): string {
+  const [lng, lat] = myLocation.value;
+  const meters = getDistanceMeters(lng, lat, item.scenicSpot.longitude, item.scenicSpot.latitude);
+  if (meters < 100) return '一百米内';
+  if (meters < 1000) return `${Math.round(meters / 100) * 100}米`;
+  return `${Math.round(meters / 1000)}km`;
+}
+function openLocation(item: RouteInfo['items'][number]) {
+  uni.openLocation({
+    latitude: item.scenicSpot.latitude,
+    longitude: item.scenicSpot.longitude,
+  });
+}
+
 /**
  * 腾讯地图 polyline 解压并转为地图组件 points
  * 文档:https://lbs.qq.com/service/webService/webServiceGuide/route/webServiceRoute#8
@@ -248,20 +298,19 @@ function collectTransitPolylines(transitData: RouteInfo['items'][0]['toNextRoute
   }
   return result;
 }
-
 /** 折线说明文案(用于地图 segmentTexts,不含 emoji) */
 function getSegmentLabel(way: RouteToNextBestWay, atDay: number, duration?: number): string {
   const wayName = { driving: '驾车', walking: '步行', bicycling: '骑行', ebicycling: '电动车', transit: '公交' }[way];
   const dur = duration != null ? ` 约${formatDuration(duration)}` : '';
   return `第${atDay}天 ${wayName}${dur}`;
 }
-
 /** 根据 toNextRoute 与 toNextBestWay 生成地图 polyline 数组(含 segmentTexts 折线说明) */
 function buildPolylineFromRouteInfo(info: RouteInfo) {
   const segments: Array<{
     points: { longitude: number; latitude: number }[];
     color: string;
     width: number;
+    level: string;
     segmentTexts?: { name: string; startIndex: number; endIndex: number }[];
     textStyle?: { textColor: string; strokeColor: string; fontSize: number };
   }> = [];
@@ -283,7 +332,7 @@ function buildPolylineFromRouteInfo(info: RouteInfo) {
         const points = polylineToPoints(polylines[k]);
         if (points.length < 2) continue;
         const segmentTexts = k === 0 ? [{ name: label, startIndex: 0, endIndex: points.length - 1 }] : undefined;
-        segments.push({ points, color, width, segmentTexts, textStyle: segmentTexts ? textStyle : undefined });
+        segments.push({ points, color, width, level: 'aboveroads', segmentTexts, textStyle: segmentTexts ? textStyle : undefined });
       }
     } else {
       const encoded = (routeData as { polyline?: number[]; duration?: number })?.polyline;
@@ -292,7 +341,14 @@ function buildPolylineFromRouteInfo(info: RouteInfo) {
       if (points.length < 2) continue;
       const duration = (routeData as { duration?: number })?.duration;
       const segmentTexts = [{ name: getSegmentLabel(way, item.atDay, duration), startIndex: 0, endIndex: points.length - 1 }];
-      segments.push({ points, color, width, segmentTexts, textStyle });
+      segments.push({ 
+        points, 
+        color, 
+        width, 
+        level: 'aboveroads',
+        segmentTexts, 
+        textStyle 
+      });
     }
   }
   polyline.value = segments;
@@ -301,22 +357,10 @@ function buildPolylineFromRouteInfo(info: RouteInfo) {
 function onMarkerClick(item: any) {
   console.log(item);
 }
-function openLocation(item: RouteInfo['items'][number]['scenicSpot']) {
-  uni.openLocation({
-    latitude: item.latitude,
-    longitude: item.longitude,
-  });
-}
 function onItemClick(item: RouteInfo['items'][number]) {
-  currentScenicSpot.value = item.scenicSpot;
   currentLocation.value = [item.scenicSpot.longitude, item.scenicSpot.latitude];
-  bottomSheetRef.value?.setDragHeightToMax();
-}
-function previewImage(image: string, images: string[]) {
-  uni.previewImage({
-    urls: images,
-    current: images.indexOf(image),
-  });
+  uni.setStorageSync('travelRouteItem', item.scenicSpot);
+  navTo('./details');
 }
 function focusRoute(item: RouteInfo['items'][number]) {
   bottomSheetRef.value?.setDragHeightToMin();
@@ -332,7 +376,7 @@ async function loadRoute() {
       if (error.code == 404) {
         alert({
           title: '路线不存在',
-          content: '路线未生成,请在服务器端生成路线',
+          content: '路线未生成,请在服务器端生成路线,ID: ' + querys.value.id,
         });
         return;
       }
@@ -386,8 +430,11 @@ onMounted(() => {
   uni.getLocation({
     type: 'wgs84',
     success: (res) => {
+      myLocation.value = [res.longitude, res.latitude];
       currentLocation.value = [res.longitude, res.latitude];
+      currentLocationGeted.value = true;
     },
+
   });
 });