szr190 2 місяців тому
батько
коміт
cb4229085d

+ 1 - 3
src/App.vue

@@ -5,9 +5,7 @@ import { onLaunch, onShow, onHide } from '@dcloudio/uni-app'
5 5
 const userStore = user()
6 6
 
7 7
 onLaunch(() => {
8
-  if (userStore.token) {
9
-    //
10
-  }
8
+  //
11 9
 })
12 10
 onShow(() => {
13 11
   console.log('App Show')

+ 77 - 0
src/api/home.ts

@@ -48,6 +48,15 @@ export const getParkServiceBoy = (parkId: string) => {
48 48
   })
49 49
 }
50 50
 
51
+// 服务设施
52
+export const getParkServiceFacility = (data: any) => {
53
+  return http({
54
+    method: 'GET',
55
+    url: `/service/facility/park/list`,
56
+    data
57
+  })
58
+}
59
+
51 60
 // 我要吃饭列表
52 61
 export const getParkServiceEatList = (data: any) => {
53 62
   return http({
@@ -81,3 +90,71 @@ export const getParkServiceShopDetail = (id: string, parkId: string) => {
81 90
     url: `/service/shop/${id}/${parkId}`
82 91
   })
83 92
 }
93
+
94
+// 附近景区列表
95
+export const getParkServiceScenicList = (data: any) => {
96
+  return http({
97
+    method: 'GET',
98
+    url: `/service/scenic/park/list`,
99
+    data
100
+  })
101
+}
102
+
103
+// 附近景区详情
104
+export const getParkServiceScenicDetail = (id: string, parkId: string) => {
105
+  return http({
106
+    method: 'GET',
107
+    url: `/service/scenic/${id}/${parkId}`
108
+  })
109
+}
110
+
111
+// 出行提醒
112
+export const getParkServiceNoticeList = (data: any) => {
113
+  return http({
114
+    method: 'GET',
115
+    url: `/service/notice/park/list`,
116
+    data
117
+  })
118
+}
119
+
120
+// 出行提醒详情
121
+export const getParkServiceNoticeDetail = (id: string) => {
122
+  return http({
123
+    method: 'GET',
124
+    url: `/service/notice/park/${id}`
125
+  })
126
+}
127
+
128
+// 服务区
129
+export const getParkServiceList = (data: any) => {
130
+  return http({
131
+    method: 'GET',
132
+    url: `/service/service/park/list`,
133
+    data
134
+  })
135
+}
136
+
137
+// 服务区详情
138
+export const getParkServiceDetail = (id: string) => {
139
+  return http({
140
+    method: 'GET',
141
+    url: `/service/service/park/${id}`
142
+  })
143
+}
144
+
145
+// 常见问题列表
146
+export const getQuestionList = (data: any) => {
147
+  return http({
148
+    method: 'GET',
149
+    url: `/other/question/list/web`,
150
+    data
151
+  })
152
+}
153
+
154
+// 常见问题详情
155
+export const getQuestionDetail = (id: string) => {
156
+  return http({
157
+    method: 'GET',
158
+    url: `/other/question/web/${id}`
159
+  })
160
+}

+ 8 - 0
src/api/login.ts

@@ -152,3 +152,11 @@ export const phoneLoginApi = (data: { code: string }) => {
152 152
     data
153 153
   })
154 154
 }
155
+
156
+// 手机号登录
157
+export const h5PhoneLoginApi = () => {
158
+  return http({
159
+    method: 'POST',
160
+    url: `/h5PhoneLogin`
161
+  })
162
+}

+ 4 - 1
src/base/common/charge.vue

@@ -1,5 +1,5 @@
1 1
 <template>
2
-  <view class="common-details-container">
2
+  <view class="common-details-container" v-if="chargeInfo?.title">
3 3
     <view class="common-header-wrapper">
4 4
       <image :src="content.image" mode="scaleToFill" />
5 5
     </view>
@@ -30,6 +30,9 @@
30 30
       </view>
31 31
     </view>
32 32
   </view>
33
+  <view class="no-data-container" v-else>
34
+    <u-empty />
35
+  </view>
33 36
 </template>
34 37
 
35 38
 <script setup lang="ts">

+ 4 - 1
src/base/common/driversHome.vue

@@ -1,5 +1,5 @@
1 1
 <template>
2
-  <view class="common-details-container">
2
+  <view class="common-details-container" v-if="driverInfo?.title">
3 3
     <view class="common-header-wrapper">
4 4
       <image :src="content.image" mode="scaleToFill" />
5 5
     </view>
@@ -23,6 +23,9 @@
23 23
       </view>
24 24
     </view>
25 25
   </view>
26
+  <view class="no-data-container" v-else>
27
+    <u-empty />
28
+  </view>
26 29
 </template>
27 30
 
28 31
 <script setup lang="ts">

+ 4 - 1
src/base/common/join.vue

@@ -1,5 +1,5 @@
1 1
 <template>
2
-  <view class="common-details-container">
2
+  <view class="common-details-container" v-if="content?.intro || content?.image">
3 3
     <view class="common-header-wrapper join">
4 4
       <image :src="content.image" mode="scaleToFill" />
5 5
     </view>
@@ -11,6 +11,9 @@
11 11
       </view>
12 12
     </view>
13 13
   </view>
14
+  <view class="no-data-container" v-else>
15
+    <u-empty />
16
+  </view>
14 17
 </template>
15 18
 
16 19
 <script setup lang="ts">

+ 4 - 1
src/base/common/nursingRoom.vue

@@ -1,5 +1,5 @@
1 1
 <template>
2
-  <view class="common-details-container">
2
+  <view class="common-details-container" v-if="boyInfo?.title">
3 3
     <view class="common-header-wrapper">
4 4
       <image :src="content.image" mode="scaleToFill" />
5 5
     </view>
@@ -23,6 +23,9 @@
23 23
       </view>
24 24
     </view>
25 25
   </view>
26
+  <view class="no-data-container" v-else>
27
+    <u-empty />
28
+  </view>
26 29
 </template>
27 30
 
28 31
 <script setup lang="ts">

+ 4 - 1
src/base/common/refuel.vue

@@ -1,5 +1,5 @@
1 1
 <template>
2
-  <view class="common-details-container">
2
+  <view class="common-details-container" v-if="refuelInfo?.title">
3 3
     <view class="common-header-wrapper">
4 4
       <image :src="content.image" mode="scaleToFill" />
5 5
     </view>
@@ -21,6 +21,9 @@
21 21
       </view>
22 22
     </view>
23 23
   </view>
24
+  <view class="no-data-container" v-else>
25
+    <u-empty />
26
+  </view>
24 27
 </template>
25 28
 
26 29
 <script setup lang="ts">

+ 4 - 1
src/base/common/repair.vue

@@ -1,5 +1,5 @@
1 1
 <template>
2
-  <view class="common-details-container">
2
+  <view class="common-details-container" v-if="carInfo?.title">
3 3
     <view class="common-header-wrapper">
4 4
       <image :src="content.image" mode="scaleToFill" />
5 5
     </view>
@@ -18,6 +18,9 @@
18 18
       </view>
19 19
     </view>
20 20
   </view>
21
+  <view class="no-data-container" v-else>
22
+    <u-empty />
23
+  </view>
21 24
 </template>
22 25
 
23 26
 <script setup lang="ts">

+ 36 - 15
src/base/common/spot.vue

@@ -1,34 +1,28 @@
1 1
 <template>
2 2
   <view class="common-details-container">
3 3
     <view class="common-header-wrapper">
4
-      <image src="" mode="scaleToFill" />
4
+      <image :src="content.image" mode="scaleToFill" />
5 5
     </view>
6 6
     <view class="common-content-wrapper">
7 7
       <view class="common-card">
8 8
         <!-- common title -->
9 9
         <view class="common-card-title">
10
-          <view class="text">少海风景区</view>
11
-          <view class="card-tag">4A景区</view>
10
+          <view class="text">{{ details.title }}</view>
11
+          <view class="card-tag">{{ content.level }}</view>
12 12
         </view>
13 13
         <!-- sub title -->
14
-        <view class="common-sub-title">开放时间:24小时</view>
14
+        <view class="common-sub-title">开放时间:{{ details?.businessAt }}</view>
15 15
         <view class="common-sub-title">
16
-          <view class="text">地址:山东省青岛市胶州市香港路1号</view>
17
-          <view class="sub-img-wrap">
16
+          <view class="text">地址:{{ content?.address }}</view>
17
+          <view class="sub-img-wrap" @click="goToNavigation(content?.longitude, content?.latitude, content?.title)">
18 18
             <image :src="minioUrl + '/icon_go.png'" mode="scaleToFill" />
19 19
           </view>
20 20
         </view>
21 21
       </view>
22 22
       <view class="common-card">
23
-        <view class="common-card-title">景区介绍</view>
24
-        <view class="common-card-content"
25
-          >少海风景区位于胶州市区东南侧,地处胶州湾与大沽河交汇处,规划面积22.91平方公里
26
-          。景区由南湖、北湖及云溪河构成水域体系,湿地覆盖率83.9%,拥有166种野生脊椎动物和93种维管束植物
27
-          ,形成"两湖、一带、三岛"的生态格局。作为山东省首家国家级湿地公园(2012年挂牌),景区融合16公里环湖景观带与仿古建筑群,复建板桥镇、慈云寺等文化地标
28
-          。设有游艇俱乐部、观景台等设施,2019年获评国家4A级旅游景区。</view
29
-        >
30
-        <view class="common-card-title u-margin-top-45">门票政策</view>
31
-        <view class="common-card-content">免费开放</view>
23
+        <view class="common-card-content">
24
+          <u-parse :html="content?.intro" />
25
+        </view>
32 26
       </view>
33 27
     </view>
34 28
   </view>
@@ -36,8 +30,35 @@
36 30
 
37 31
 <script setup lang="ts">
38 32
 import { ref } from 'vue'
33
+import { onLoad } from '@dcloudio/uni-app'
34
+import { getParkServiceScenicDetail } from '@/api/home'
39 35
 import { useGlobal } from '../../composables/index'
40 36
 const { minioUrl } = useGlobal()
37
+
38
+const id = ref<string>('')
39
+const details = ref<any>({})
40
+const content = ref<any>({})
41
+onLoad(async (options) => {
42
+  id.value = options?.id || ''
43
+  const parkId = uni.getStorageSync('parkId')
44
+  const res = await getParkServiceScenicDetail(id.value, parkId)
45
+  if (res.code === 200) {
46
+    details.value = res.data
47
+    content.value = JSON.parse(details.value.content)
48
+  }
49
+})
50
+
51
+// 导航功能
52
+const goToNavigation = (longitude: string, latitude: string, name: string) => {
53
+  uni.openLocation({
54
+    latitude: Number(latitude),
55
+    longitude: Number(longitude),
56
+    name: name,
57
+    success: () => {
58
+      console.log('导航功能调用成功')
59
+    }
60
+  })
61
+}
41 62
 </script>
42 63
 
43 64
 <style lang="scss" scoepd>

+ 61 - 0
src/base/shop/detailsShop.vue

@@ -0,0 +1,61 @@
1
+<template>
2
+  <view class="common-details-container">
3
+    <view class="common-header-wrapper">
4
+      <image :src="content.image" mode="scaleToFill" />
5
+    </view>
6
+    <view class="common-content-wrapper">
7
+      <view class="common-card">
8
+        <!-- common title -->
9
+        <view class="common-card-title">{{ details.title }}</view>
10
+        <!-- rate -->
11
+        <view class="common-card-rate">
12
+          <uni-rate allow-half :value="content.rate" color="#ddd" activeColor="#ff5702" />
13
+          <text>{{ content.rate }}</text>
14
+        </view>
15
+        <!-- sub title -->
16
+        <view class="common-sub-title">营业时间: {{ details.businessAt }}</view>
17
+      </view>
18
+      <view class="common-card">
19
+        <view class="common-card-title">店铺简介</view>
20
+        <view class="common-card-content">
21
+          <u-parse :html="content?.intro" />
22
+        </view>
23
+        <view class="common-card-title u-margin-top-30">精品推荐</view>
24
+        <view class="common-card-list" v-if="content.facilities.length > 0">
25
+          <view class="card-list-item" v-for="(item, index) in content.facilities" :key="index">
26
+            <view class="item-img-wrap">
27
+              <image :src="item.img" mode="scaleToFill" />
28
+            </view>
29
+            <view class="list-text">{{ item.name }}</view>
30
+          </view>
31
+        </view>
32
+        <view class="common-empty" v-else>
33
+          <u-empty text="暂无推荐" />
34
+        </view>
35
+      </view>
36
+    </view>
37
+  </view>
38
+</template>
39
+
40
+<script lang="ts" setup>
41
+import { ref } from 'vue'
42
+import { onLoad } from '@dcloudio/uni-app'
43
+import { getParkServiceShopDetail } from '@/api/home'
44
+
45
+const id = ref<string>('')
46
+const details = ref<any>({})
47
+const content = ref<any>({})
48
+onLoad(async (options) => {
49
+  id.value = options?.id || ''
50
+  const parkId = uni.getStorageSync('parkId')
51
+  const res = await getParkServiceShopDetail(id.value, parkId)
52
+  if (res.code === 200) {
53
+    details.value = res.data
54
+    content.value = JSON.parse(details.value.content)
55
+  }
56
+})
57
+</script>
58
+
59
+<style lang="scss" scoepd>
60
+@import '../base.scss';
61
+</style>

+ 95 - 0
src/base/suggest/details.vue

@@ -0,0 +1,95 @@
1
+<template>
2
+  <view class="notice-detail-container">
3
+    <!-- 详情内容 -->
4
+    <view class="notice-detail-content">
5
+      <view class="detail-content-header">
6
+        <view class="title">{{ detailInfo.title }}</view>
7
+      </view>
8
+      <view class="content-text-wrap">{{ detailInfo.content }}</view>
9
+    </view>
10
+  </view>
11
+</template>
12
+
13
+<script lang="ts" setup>
14
+import { ref } from 'vue'
15
+import { onLoad } from '@dcloudio/uni-app'
16
+import { getQuestionDetail } from '@/api/home'
17
+
18
+// 详情数据
19
+const detailInfo = ref<any>({})
20
+const id = ref('')
21
+onLoad((options) => {
22
+  id.value = options?.id || ''
23
+  getDetail()
24
+})
25
+
26
+// 获取详情
27
+const getDetail = async () => {
28
+  const res = await getQuestionDetail(id.value)
29
+  if (res.code === 200) {
30
+    detailInfo.value = res.data
31
+  }
32
+}
33
+</script>
34
+
35
+<style lang="scss" scoped>
36
+.notice-detail-container {
37
+  box-sizing: border-box;
38
+  padding: 24rpx 30rpx 0 30rpx;
39
+  .notice-detail-content {
40
+    width: 688rpx;
41
+    border-radius: 23rpx;
42
+    background-color: #fff;
43
+    padding: 30rpx;
44
+    .content-text-wrap {
45
+      padding-top: 24rpx;
46
+      font-size: 28rpx;
47
+      color: rgba(0, 0, 0, 0.6);
48
+      line-height: 43rpx;
49
+    }
50
+    .detail-content-header {
51
+      padding-bottom: 24rpx;
52
+      border-bottom: 2rpx solid #f2f3f5;
53
+      .title {
54
+        width: 628rpx;
55
+        font-size: 30rpx;
56
+        color: #1d2129;
57
+        line-height: 49rpx;
58
+      }
59
+      .sub-wrap {
60
+        display: flex;
61
+        align-items: center;
62
+        justify-content: space-between;
63
+        margin-top: 14rpx;
64
+        .sub-text {
65
+          font-size: 28rpx;
66
+          color: #9ca2a9;
67
+        }
68
+      }
69
+    }
70
+  }
71
+  .notice-submit-wrapper {
72
+    width: 100%;
73
+    height: 123rpx;
74
+    background-color: #fff;
75
+    display: flex;
76
+    align-items: center;
77
+    justify-content: center;
78
+    position: fixed;
79
+    bottom: 0;
80
+    left: 0;
81
+    .save-btn {
82
+      width: 688rpx;
83
+      height: 77rpx;
84
+      background-color: #ed2424;
85
+      color: #fff;
86
+      border-radius: 38rpx;
87
+      font-size: 28rpx;
88
+      font-weight: 500;
89
+      display: flex;
90
+      align-items: center;
91
+      justify-content: center;
92
+    }
93
+  }
94
+}
95
+</style>

+ 33 - 3
src/base/suggest/index.vue

@@ -21,11 +21,11 @@
21 21
     </view>
22 22
     <HyCard title="常见问题">
23 23
       <view class="suggest-list-wrapper">
24
-        <view class="list-item" v-for="index in 3" :key="index">
25
-          <view class="text">我想要修车,怎么联系服务区修车店?</view>
24
+        <view class="list-item" v-for="(item, index) in questionList" :key="index" @click="toDetails(item.id)">
25
+          <view class="text">{{ item.title }}</view>
26 26
           <image :src="minioUrl + '/icon_kefubz_05.png'" mode="scaleToFill" />
27 27
         </view>
28
-        <view class="refresh-wrap">
28
+        <view class="refresh-wrap" @click="tapRefresh">
29 29
           <image :src="minioUrl + '/icon_kefubz_04.png'" mode="scaleToFill" />
30 30
           <text>换一换</text>
31 31
         </view>
@@ -36,10 +36,40 @@
36 36
 
37 37
 <script lang="ts" setup>
38 38
 import { ref } from 'vue'
39
+import { onLoad } from '@dcloudio/uni-app'
40
+import { getQuestionList } from '@/api/home'
39 41
 import { useGlobal } from '@/composables/index'
40 42
 
41 43
 const { minioUrl } = useGlobal()
42 44
 
45
+const questionList = ref<any>([])
46
+const query = ref<any>({
47
+  pageNum: 1,
48
+  pageSize: 3
49
+})
50
+
51
+onLoad(() => {
52
+  getQuestionListData()
53
+})
54
+
55
+const getQuestionListData = async () => {
56
+  const res = await getQuestionList(query.value)
57
+  if (res.code === 200) {
58
+    questionList.value = res.rows
59
+  }
60
+}
61
+
62
+const tapRefresh = () => {
63
+  query.value.pageNum++
64
+  getQuestionListData()
65
+}
66
+
67
+const toDetails = (id: string) => {
68
+  uni.navigateTo({
69
+    url: '/base/suggest/details?id=' + id
70
+  })
71
+}
72
+
43 73
 const toSuggest = () => {
44 74
   uni.navigateTo({
45 75
     url: '/base/suggest/suggest'

+ 98 - 0
src/base/travel/details.vue

@@ -0,0 +1,98 @@
1
+<template>
2
+  <view class="notice-detail-container">
3
+    <!-- 详情内容 -->
4
+    <view class="notice-detail-content">
5
+      <view class="detail-content-header">
6
+        <view class="title">{{ detailInfo.title }}</view>
7
+        <view class="sub-wrap">
8
+          <view class="sub-text time">{{ detailInfo.publishAt }}</view>
9
+        </view>
10
+      </view>
11
+      <view class="content-text-wrap">{{ detailInfo.content }}</view>
12
+    </view>
13
+  </view>
14
+</template>
15
+
16
+<script lang="ts" setup>
17
+import { ref } from 'vue'
18
+import { onLoad } from '@dcloudio/uni-app'
19
+import { getParkServiceNoticeDetail } from '@/api/home'
20
+
21
+// 详情数据
22
+const detailInfo = ref<any>({})
23
+const id = ref('')
24
+onLoad((options) => {
25
+  id.value = options?.id || ''
26
+  getDetail()
27
+})
28
+
29
+// 获取详情
30
+const getDetail = async () => {
31
+  const res = await getParkServiceNoticeDetail(id.value)
32
+  if (res.code === 200) {
33
+    detailInfo.value = res.data
34
+  }
35
+}
36
+</script>
37
+
38
+<style lang="scss" scoped>
39
+.notice-detail-container {
40
+  box-sizing: border-box;
41
+  padding: 24rpx 30rpx 0 30rpx;
42
+  .notice-detail-content {
43
+    width: 688rpx;
44
+    border-radius: 23rpx;
45
+    background-color: #fff;
46
+    padding: 30rpx;
47
+    .content-text-wrap {
48
+      padding-top: 24rpx;
49
+      font-size: 28rpx;
50
+      color: rgba(0, 0, 0, 0.6);
51
+      line-height: 43rpx;
52
+    }
53
+    .detail-content-header {
54
+      padding-bottom: 24rpx;
55
+      border-bottom: 2rpx solid #f2f3f5;
56
+      .title {
57
+        width: 628rpx;
58
+        font-size: 30rpx;
59
+        color: #1d2129;
60
+        line-height: 49rpx;
61
+      }
62
+      .sub-wrap {
63
+        display: flex;
64
+        align-items: center;
65
+        justify-content: space-between;
66
+        margin-top: 14rpx;
67
+        .sub-text {
68
+          font-size: 28rpx;
69
+          color: #9ca2a9;
70
+        }
71
+      }
72
+    }
73
+  }
74
+  .notice-submit-wrapper {
75
+    width: 100%;
76
+    height: 123rpx;
77
+    background-color: #fff;
78
+    display: flex;
79
+    align-items: center;
80
+    justify-content: center;
81
+    position: fixed;
82
+    bottom: 0;
83
+    left: 0;
84
+    .save-btn {
85
+      width: 688rpx;
86
+      height: 77rpx;
87
+      background-color: #ed2424;
88
+      color: #fff;
89
+      border-radius: 38rpx;
90
+      font-size: 28rpx;
91
+      font-weight: 500;
92
+      display: flex;
93
+      align-items: center;
94
+      justify-content: center;
95
+    }
96
+  }
97
+}
98
+</style>

+ 63 - 22
src/base/travel/tips.vue

@@ -1,34 +1,74 @@
1 1
 <template>
2
-  <view class="travel-tips-container">
3
-    <!-- tabs -->
4
-    <u-tabs
5
-      v-model="tabActive"
6
-      :list="list"
7
-      :is-scroll="false"
8
-      inactive-color="#333"
9
-      active-color="#333"
10
-      :bar-style="{
11
-        backgroundColor: '#4a8cfe'
12
-      }"
13
-    ></u-tabs>
14
-    <!-- list -->
15
-    <view class="travel-tips-list">
16
-      <view class="tips-list-item">
17
-        <view class="title u-line-2">沈海高速海口方向K31+900M~K29+000M处养护施工的公告</view>
18
-        <view class="content"
19
-          >沈海高速山东段青岛往海口方向K31+900M~K29+000M处正在养护施工,占用主车道及应急车道,该路段目前超车道单道通行,请途经车辆谨慎驾驶,提前减速。</view
20
-        >
21
-        <view class="time">2025-10-12 12:36:50</view>
2
+  <mescroll-body @init="mescrollInit" :down="{ use: true, minAngle: angle }" @down="downCallback" @up="upCallback">
3
+    <view class="travel-tips-container">
4
+      <!-- tabs -->
5
+      <u-tabs
6
+        v-model="tabActive"
7
+        :list="list"
8
+        :is-scroll="false"
9
+        inactive-color="#333"
10
+        active-color="#333"
11
+        :bar-style="{
12
+          backgroundColor: '#4a8cfe'
13
+        }"
14
+        @change="handleTabChange"
15
+      ></u-tabs>
16
+      <!-- list -->
17
+      <view class="travel-tips-list">
18
+        <view class="tips-list-item" v-for="(item, index) in listData" :key="index" @click="toDetail(item.id)">
19
+          <view class="title u-line-2">{{ item.title }}</view>
20
+          <view class="content">{{ item.content }}</view>
21
+          <view class="time">{{ item.publishAt }}</view>
22
+        </view>
22 23
       </view>
23 24
     </view>
24
-  </view>
25
+  </mescroll-body>
25 26
 </template>
26 27
 
27 28
 <script lang="ts" setup>
28 29
 import { ref } from 'vue'
30
+import { onPageScroll } from '@dcloudio/uni-app'
31
+import { getParkServiceNoticeList } from '@/api/home'
32
+import { useListLoader } from '@/composables/useListLoader'
29 33
 
30
-const list = ref([{ name: '路况信息' }, { name: '优惠促销' }, { name: '天气预警' }])
34
+const angle = ref(45)
35
+// 监听页面滚动
36
+onPageScroll((e) => {
37
+  if (e.scrollTop > 80) {
38
+    angle.value = 90
39
+  } else {
40
+    angle.value = 45
41
+  }
42
+})
43
+
44
+const list = ref([
45
+  { name: '路况信息', status: 1 },
46
+  { name: '优惠促销', status: 2 },
47
+  { name: '天气预警', status: 3 }
48
+])
31 49
 const tabActive = ref(0)
50
+
51
+// 切换tab
52
+const handleTabChange = (val: any) => {
53
+  tabActive.value = val
54
+  params.value.categoryId = list.value[tabActive.value].status
55
+  handleSearch()
56
+}
57
+
58
+// 跳转详情
59
+const toDetail = (id: string) => {
60
+  uni.navigateTo({
61
+    url: '/base/travel/details?id=' + id
62
+  })
63
+}
64
+
65
+// 列表加载
66
+const { listData, upCallback, downCallback, handleSearch, mescrollInit, params } = useListLoader({
67
+  apiFn: getParkServiceNoticeList,
68
+  initialParams: {
69
+    categoryId: list.value[tabActive.value].status
70
+  }
71
+})
32 72
 </script>
33 73
 
34 74
 <style lang="scss" scoped>
@@ -45,6 +85,7 @@ const tabActive = ref(0)
45 85
       box-sizing: border-box;
46 86
       padding: 33rpx;
47 87
       background-color: #fff;
88
+      margin-bottom: 24rpx;
48 89
       .title {
49 90
         font-size: 28rpx;
50 91
         color: #333;

+ 15 - 0
src/components/HySelect.vue

@@ -59,6 +59,19 @@ const getParkWeb = async () => {
59 59
   const res = await getParkWebAPI()
60 60
   if (res.code === 200) {
61 61
     parkList.value = res.rows
62
+
63
+    // 当数据加载完成且没有选中值时,默认选择第一项
64
+    if (parkList.value.length > 0 && !selectedValue.value) {
65
+      const firstItem = parkList.value[0]
66
+      selectedValue.value = firstItem.id
67
+
68
+      // 存储到本地缓存
69
+      uni.setStorageSync('parkId', firstItem.id)
70
+
71
+      // 触发事件通知父组件
72
+      emit('update:modelValue', firstItem.id)
73
+      emit('change', firstItem)
74
+    }
62 75
   }
63 76
 }
64 77
 
@@ -84,6 +97,8 @@ const closeDropdown = () => {
84 97
 
85 98
 // 选择选项
86 99
 const selectItem = (item: { id: string; name: string }) => {
100
+  // 存储到本地缓存
101
+  uni.setStorageSync('parkId', item.id)
87 102
   selectedValue.value = item.id
88 103
   dropdownVisible.value = false
89 104
 

+ 12 - 6
src/components/HyService.vue

@@ -1,16 +1,16 @@
1 1
 <template>
2 2
   <view class="service-item-wrapper">
3 3
     <view class="item-left-wrap">
4
-      <image :src="minioUrl + '/icon_service_01.png'" mode="scaleToFill" />
5
-      <text>停车场</text>
4
+      <image :src="item.content.image" mode="scaleToFill" />
5
+      <text>{{ item.title }}</text>
6 6
     </view>
7 7
     <view class="item-right-wrap" :style="{ backgroundImage: `url(${minioUrl}/bg_service_03.png)` }">
8
-      <!-- <view class="main-text">淋浴/洗衣</view> -->
9
-      <view class="block-content">
8
+      <view class="main-text" v-if="item.title === '司机之家'">淋浴/洗衣</view>
9
+      <view class="block-content" v-else>
10 10
         <view class="label">空闲</view>
11 11
         <view class="number-box">
12
-          <view class="left-num">30</view>
13
-          <view class="right-num">/60</view>
12
+          <view class="left-num">{{ item.content.availableCount }}</view>
13
+          <view class="right-num">/{{ item.content.totalCount }}</view>
14 14
         </view>
15 15
       </view>
16 16
     </view>
@@ -20,6 +20,12 @@
20 20
 <script lang="ts" setup>
21 21
 import { useGlobal } from '@/composables/index'
22 22
 const { minioUrl } = useGlobal()
23
+const props = defineProps({
24
+  item: {
25
+    type: Object,
26
+    default: () => ({})
27
+  }
28
+})
23 29
 </script>
24 30
 
25 31
 <style lang="scss" scoped>

+ 10 - 4
src/components/HyShopItem.vue

@@ -21,7 +21,7 @@
21 21
 </template>
22 22
 
23 23
 <script lang="ts" setup>
24
-defineProps({
24
+const props = defineProps({
25 25
   price: {
26 26
     type: Boolean,
27 27
     default: false
@@ -33,9 +33,15 @@ defineProps({
33 33
 })
34 34
 
35 35
 const navigateToDetails = (item: any) => {
36
-  uni.navigateTo({
37
-    url: '/base/shop/details?id=' + item.id
38
-  })
36
+  if (props.price) {
37
+    uni.navigateTo({
38
+      url: '/base/shop/details?id=' + item.id
39
+    })
40
+  } else {
41
+    uni.navigateTo({
42
+      url: '/base/shop/detailsShop?id=' + item.id
43
+    })
44
+  }
39 45
 }
40 46
 </script>
41 47
 

+ 9 - 0
src/manifest.json

@@ -56,6 +56,15 @@
56 56
         "router" : {
57 57
             "base" : "/",
58 58
             "mode" : "history"
59
+        },
60
+        "sdkConfigs" : {
61
+            "maps" : {
62
+                "amap" : {
63
+                    "key" : "b0adb9cb4cae0dd05fae8d444c251492",
64
+                    "securityJsCode" : "337c8dbd6c3e800641d22c85ddb78b9e",
65
+                    "serviceHost" : ""
66
+                }
67
+            }
59 68
         }
60 69
     },
61 70
     /* 小程序特有相关 */

+ 22 - 1
src/pages.json

@@ -4,7 +4,7 @@
4 4
       "path": "pages/index",
5 5
       "style": {
6 6
         "navigationBarTitleText": "首页",
7
-        "enablePullDownRefresh": true,
7
+        "enablePullDownRefresh": false,
8 8
         "navigationStyle": "custom"
9 9
       }
10 10
     },
@@ -44,6 +44,13 @@
44 44
           }
45 45
         },
46 46
         {
47
+          "path": "suggest/details",
48
+          "style": {
49
+            "navigationBarTitleText": "常见问题详情",
50
+            "enablePullDownRefresh": false
51
+          }
52
+        },
53
+        {
47 54
           "path": "suggest/record",
48 55
           "style": {
49 56
             "navigationBarTitleText": "反馈历史",
@@ -65,6 +72,13 @@
65 72
           }
66 73
         },
67 74
         {
75
+          "path": "travel/details",
76
+          "style": {
77
+            "navigationBarTitleText": "详情",
78
+            "enablePullDownRefresh": false
79
+          }
80
+        },
81
+        {
68 82
           "path": "shop/index",
69 83
           "style": {
70 84
             "navigationBarTitleText": "精选商家",
@@ -86,6 +100,13 @@
86 100
           }
87 101
         },
88 102
         {
103
+          "path": "shop/detailsShop",
104
+          "style": {
105
+            "navigationBarTitleText": "详情",
106
+            "enablePullDownRefresh": false
107
+          }
108
+        },
109
+        {
89 110
           "path": "common/spot",
90 111
           "style": {
91 112
             "navigationBarTitleText": "景点",

+ 323 - 178
src/pages/index.vue

@@ -1,10 +1,32 @@
1 1
 <script setup lang="ts">
2
-import { ref } from 'vue'
3
-import { onLoad } from '@dcloudio/uni-app'
4
-import { getParkServiceOil, getParkServicePower, getParkServiceShopList } from '@/api/home'
2
+import { ref, computed } from 'vue'
3
+import { onLoad, onPageScroll, onShow } from '@dcloudio/uni-app'
4
+import {
5
+  getParkServiceOil,
6
+  getParkServicePower,
7
+  getParkServiceShopList,
8
+  getParkServiceScenicList,
9
+  getParkServiceFacility,
10
+  getParkServiceNoticeList
11
+} from '@/api/home'
12
+import { getParkWebAPI } from '@/api/system'
13
+import { useListLoader } from '@/composables/useListLoader'
5 14
 import { useGlobal } from '@/composables/index'
15
+import { user } from '@/stores/modules/user'
16
+
17
+const userStore = user()
6 18
 const { minioUrl } = useGlobal()
7 19
 
20
+const angle = ref(45)
21
+// 监听页面滚动
22
+onPageScroll((e) => {
23
+  if (e.scrollTop > 80) {
24
+    angle.value = 90
25
+  } else {
26
+    angle.value = 45
27
+  }
28
+})
29
+
8 30
 // 轮播图数据
9 31
 const list = ref([`${minioUrl}/home_banner.png`])
10 32
 
@@ -33,16 +55,42 @@ const isRescueShow = ref(false)
33 55
 const isLostFoundShow = ref(false)
34 56
 
35 57
 onLoad(() => {
36
-  const parkId = '1998662443205042178'
37
-  selectedValue.value = parkId
38
-  // 存储到本地缓存中
39
-  uni.setStorageSync('parkId', parkId)
58
+  // 页面加载时从本地缓存获取园区ID
59
+  const cachedParkId = uni.getStorageSync('parkId')
60
+  if (cachedParkId) {
61
+    selectedValue.value = cachedParkId
62
+    params.value.parkId = cachedParkId
63
+  }
64
+})
65
+
66
+const init = () => {
40 67
   // 获取我要加油数据
41 68
   getOilList()
42 69
   // 获取我要充电数据
43 70
   getPowerList()
44 71
   // 获取精选商家数据
45 72
   getShopList()
73
+  // 获取服务设施数据
74
+  getFacilityList()
75
+  // 获取出行提醒数据
76
+  getNoticeList()
77
+}
78
+
79
+// 处理园区选择变化
80
+const handleParkChange = () => {
81
+  params.value.parkId = selectedValue.value
82
+  oilInfo.value = {}
83
+  listOli.value = []
84
+  powerInfo.value = {}
85
+  // 初始化数据
86
+  init()
87
+  // 重新加载列表数据
88
+  handleSearch()
89
+}
90
+
91
+onShow(() => {
92
+  // 初始化数据
93
+  init()
46 94
 })
47 95
 
48 96
 // 获取我要加油数据
@@ -50,31 +98,35 @@ const oilInfo = ref<any>({})
50 98
 const listOli = ref<any>([])
51 99
 const getOilList = async () => {
52 100
   const res = (await getParkServiceOil(selectedValue.value)) as any
53
-  oilInfo.value = JSON.parse(res.data.content)
54
-  const {
55
-    oil92ActivityPrice,
56
-    oil92OriginalPrice,
57
-    oil95ActivityPrice,
58
-    oil95OriginalPrice,
59
-    oil89ActivityPrice,
60
-    oil89OriginalPrice,
61
-    dieselActivityPrice,
62
-    dieselOriginalPrice
63
-  } = oilInfo.value
64
-  const _list = [
65
-    { name: '92#', activityPrice: oil92ActivityPrice, originalPrice: oil92OriginalPrice },
66
-    { name: '95#', activityPrice: oil95ActivityPrice, originalPrice: oil95OriginalPrice },
67
-    { name: '89#', activityPrice: oil89ActivityPrice, originalPrice: oil89OriginalPrice },
68
-    { name: '柴油#', activityPrice: dieselActivityPrice, originalPrice: dieselOriginalPrice }
69
-  ]
70
-  listOli.value = _list
101
+  if (res.data) {
102
+    oilInfo.value = JSON.parse(res.data.content)
103
+    const {
104
+      oil92ActivityPrice,
105
+      oil92OriginalPrice,
106
+      oil95ActivityPrice,
107
+      oil95OriginalPrice,
108
+      oil89ActivityPrice,
109
+      oil89OriginalPrice,
110
+      dieselActivityPrice,
111
+      dieselOriginalPrice
112
+    } = oilInfo.value
113
+    const _list = [
114
+      { name: '92#', activityPrice: oil92ActivityPrice, originalPrice: oil92OriginalPrice },
115
+      { name: '95#', activityPrice: oil95ActivityPrice, originalPrice: oil95OriginalPrice },
116
+      { name: '89#', activityPrice: oil89ActivityPrice, originalPrice: oil89OriginalPrice },
117
+      { name: '柴油#', activityPrice: dieselActivityPrice, originalPrice: dieselOriginalPrice }
118
+    ]
119
+    listOli.value = _list
120
+  }
71 121
 }
72 122
 
73 123
 // 获取我要充电数据
74 124
 const powerInfo = ref<any>({})
75 125
 const getPowerList = async () => {
76 126
   const res = (await getParkServicePower(selectedValue.value)) as any
77
-  powerInfo.value = JSON.parse(res.data.content)
127
+  if (res.data) {
128
+    powerInfo.value = JSON.parse(res.data.content)
129
+  }
78 130
 }
79 131
 
80 132
 // 获取精选商家数据
@@ -87,6 +139,46 @@ const getShopList = async () => {
87 139
   }))
88 140
 }
89 141
 
142
+// 获取服务设施数据
143
+const facilityList = ref<any>([])
144
+const getFacilityList = async () => {
145
+  const res = (await getParkServiceFacility({
146
+    parkId: selectedValue.value
147
+  })) as any
148
+  facilityList.value = res.rows
149
+    .slice(0, 6)
150
+    .reverse()
151
+    .map((item: any) => ({
152
+      ...item,
153
+      content: JSON.parse(item.content)
154
+    }))
155
+}
156
+
157
+// 获取附近景区数据
158
+const scenicList = computed(() => {
159
+  return listData.value.map((item: any) => ({
160
+    ...item,
161
+    content: JSON.parse(item.content)
162
+  }))
163
+})
164
+
165
+// 获取出行提醒数据
166
+const noticeList = ref<any>([])
167
+const getNoticeList = async () => {
168
+  const res = (await getParkServiceNoticeList({
169
+    top: 1
170
+  })) as any
171
+  noticeList.value = res.rows.map((v: any) => v.title)
172
+}
173
+
174
+// 列表加载
175
+const { listData, upCallback, downCallback, handleSearch, mescrollInit, params } = useListLoader({
176
+  apiFn: getParkServiceScenicList,
177
+  initialParams: {
178
+    parkId: selectedValue.value
179
+  }
180
+})
181
+
90 182
 // 金刚区点击事件
91 183
 const navClick = (item: { path: string; type?: string; text?: string }) => {
92 184
   if (item.type === 'popup') {
@@ -118,22 +210,17 @@ const navigateToShop = () => {
118 210
 }
119 211
 
120 212
 // 景点点击事件
121
-const navigateToSpot = () => {
213
+const navigateToSpot = (id: string) => {
122 214
   uni.navigateTo({
123
-    url: '/base/common/spot'
215
+    url: '/base/common/spot?id=' + id
124 216
   })
125 217
 }
126 218
 
127
-// 导航功能实现
128
-const goToNavigation = () => {
129
-  // 这里需要替换为实际的景点坐标
130
-  const latitude = 35.86166 // 示例纬度(琅琊台风景区)
131
-  const longitude = 119.97444 // 示例经度(琅琊台风景区)
132
-  const name = '琅琊台风景区'
133
-  // 使用uni-app的地图API打开系统地图进行导航
219
+// 导航功能
220
+const goToNavigation = (longitude: string, latitude: string, name: string) => {
134 221
   uni.openLocation({
135
-    latitude: latitude,
136
-    longitude: longitude,
222
+    latitude: Number(latitude),
223
+    longitude: Number(longitude),
137 224
     name: name,
138 225
     success: () => {
139 226
       console.log('导航功能调用成功')
@@ -150,170 +237,223 @@ const callPhone = (phone: string) => {
150 237
 </script>
151 238
 
152 239
 <template>
153
-  <view class="index-container">
154
-    <!-- 头部背景 -->
155
-    <view class="header-bg" :style="{ backgroundImage: 'url(' + minioUrl + '/index_bg.png)' }"></view>
156
-    <!-- header -->
157
-    <view class="index-header-wrapper">
158
-      <image :src="minioUrl + '/logo.png'" mode="scaleToFill" class="logo" />
159
-      <view class="header-right-wrap">
160
-        <view class="top-title-box">
161
-          <HySelect v-model="selectedValue" />
162
-        </view>
163
-        <image class="star-img" :src="minioUrl + '/star.png'" mode="scaleToFill" />
164
-        <view class="bottom-box">
165
-          <!-- 天气 -->
166
-          <iframe
167
-            scrolling="no"
168
-            src="https://widget.tianqiapi.com/?style=tv&skin=cucumber&color=333"
169
-            frameborder="0"
170
-            width="130"
171
-            height="25"
172
-            allowtransparency="true"
173
-          ></iframe>
174
-          <!-- 营业状态 -->
175
-          <view class="bottom-tag-item">正常营业</view>
240
+  <mescroll-body
241
+    @init="mescrollInit"
242
+    :down="{ use: true, minAngle: angle }"
243
+    :bottombar="false"
244
+    :up="{ bgColor: '#f6f7fb', textColor: '#999', empty: { use: false } }"
245
+    @down="downCallback"
246
+    @up="upCallback"
247
+  >
248
+    <view class="index-container">
249
+      <!-- 头部背景 -->
250
+      <view class="header-bg" :style="{ backgroundImage: 'url(' + minioUrl + '/index_bg.png)' }"></view>
251
+      <!-- header -->
252
+      <view class="index-header-wrapper">
253
+        <image :src="minioUrl + '/logo.png'" mode="scaleToFill" class="logo" />
254
+        <view class="header-right-wrap">
255
+          <view class="top-title-box">
256
+            <HySelect v-model="selectedValue" @change="handleParkChange" />
257
+          </view>
258
+          <image class="star-img" :src="minioUrl + '/star.png'" mode="scaleToFill" />
259
+          <view class="bottom-box">
260
+            <!-- 天气 -->
261
+            <iframe
262
+              scrolling="no"
263
+              src="https://widget.tianqiapi.com/?style=tv&skin=cucumber&color=333"
264
+              frameborder="0"
265
+              width="130"
266
+              height="25"
267
+              allowtransparency="true"
268
+            ></iframe>
269
+            <!-- 营业状态 -->
270
+            <view class="bottom-tag-item">正常营业</view>
271
+          </view>
176 272
         </view>
177 273
       </view>
178
-    </view>
179
-    <!-- 轮播图 -->
180
-    <u-swiper :height="306" :list="list"></u-swiper>
181
-    <!-- 金刚区 -->
182
-    <view class="index-nav-wrapper">
183
-      <view class="nav-item" v-for="(item, index) in navList" :key="index" @click="navClick(item)">
184
-        <view class="nav-image">
185
-          <image :src="item.image" mode="scaleToFill" :style="{ width: item.w, height: item.h }" />
274
+      <!-- 轮播图 -->
275
+      <u-swiper :height="306" :list="list"></u-swiper>
276
+      <!-- 金刚区 -->
277
+      <view class="index-nav-wrapper">
278
+        <view class="nav-item" v-for="(item, index) in navList" :key="index" @click="navClick(item)">
279
+          <view class="nav-image">
280
+            <image :src="item.image" mode="scaleToFill" :style="{ width: item.w, height: item.h }" />
281
+          </view>
282
+          <view class="nav-item-text">{{ item.text }}</view>
186 283
         </view>
187
-        <view class="nav-item-text">{{ item.text }}</view>
188 284
       </view>
189
-    </view>
190
-    <!-- 出行提示 -->
191
-    <view class="index-tips-wrapper" @click="navigateToTips">
192
-      <image class="tips-img" :src="minioUrl + '/icon_chuxing.png'" mode="scaleToFill" />
193
-      <view class="tips-line"></view>
194
-      <view class="tips-content">临时交通管制的通知</view>
195
-    </view>
196
-    <!-- 加油充电 -->
197
-    <view class="index-tabs-wrapper">
198
-      <u-tabs
199
-        v-model="tabActive"
200
-        :list="tabs"
201
-        active-color="#333"
202
-        :font-size="31"
203
-        :height="60"
204
-        :gutter="20"
205
-        :bar-style="{
206
-          width: '30rpx',
207
-          height: '6rpx',
208
-          backgroundImage: 'linear-gradient(90deg, #0f91f8 0%, #32a9c8 68%, #54c197 100%), linear-gradient(#6c85fc, #6c85fc)',
209
-          backgroundBlendMode: 'normal, normal',
210
-          borderRadius: '2rpx'
211
-        }"
212
-      ></u-tabs>
213
-      <template v-if="tabActive === 0">
214
-        <view class="tab-station-wrap">
215
-          <HyStation v-for="(item, index) in listOli" :key="index" :item="item"></HyStation>
216
-        </view>
217
-      </template>
218
-      <template v-if="tabActive === 1">
219
-        <view class="tab-station-wrap column">
220
-          <HyCharge title="快充" :freeNum="powerInfo?.fastChargeFree" :totalNum="powerInfo?.fastChargeTotal" :status="powerInfo?.fastChargeStatus" />
221
-          <HyCharge title="慢充" :freeNum="powerInfo?.slowChargeFree" :totalNum="powerInfo?.slowChargeTotal" :status="powerInfo?.slowChargeStatus" />
222
-        </view>
223
-      </template>
224
-    </view>
225
-    <!-- 服务设施 -->
226
-    <HyCard title="服务设施" :marginBottom="24">
227
-      <view class="index-service-wrapper">
228
-        <HyService v-for="index in 6" :key="index"></HyService>
285
+      <!-- 出行提示 -->
286
+      <view class="index-tips-wrapper" @click="navigateToTips">
287
+        <image class="tips-img" :src="minioUrl + '/icon_chuxing.png'" mode="scaleToFill" />
288
+        <view class="tips-line"></view>
289
+        <u-notice-bar
290
+          mode="vertical"
291
+          style="width: 100%"
292
+          :list="noticeList"
293
+          :volume-icon="false"
294
+          type="none"
295
+          padding="10rpx"
296
+          font-size="25"
297
+          color="#333"
298
+        ></u-notice-bar>
229 299
       </view>
230
-    </HyCard>
231
-    <!-- 人气优选商家 -->
232
-    <HyCard title="人气优选商家" rightText="更多商家" isShowRight :marginBottom="24" @clickRight="navigateToShop">
233
-      <view class="index-shop-wrapper">
234
-        <view class="shop-item" v-for="(item, index) in shopList" :key="index">
235
-          <view class="img-wrap">
236
-            <image :src="item.content.image" mode="scaleToFill" />
300
+      <!-- 加油充电 -->
301
+      <view class="index-tabs-wrapper">
302
+        <u-tabs
303
+          v-model="tabActive"
304
+          :list="tabs"
305
+          active-color="#333"
306
+          :font-size="31"
307
+          :height="60"
308
+          :gutter="20"
309
+          :bar-style="{
310
+            width: '30rpx',
311
+            height: '6rpx',
312
+            backgroundImage: 'linear-gradient(90deg, #0f91f8 0%, #32a9c8 68%, #54c197 100%), linear-gradient(#6c85fc, #6c85fc)',
313
+            backgroundBlendMode: 'normal, normal',
314
+            borderRadius: '2rpx'
315
+          }"
316
+        ></u-tabs>
317
+        <template v-if="tabActive === 0">
318
+          <view class="tab-station-wrap" v-if="listOli.length > 0">
319
+            <HyStation v-for="(item, index) in listOli" :key="index" :item="item"></HyStation>
237 320
           </view>
238
-          <view class="item-text u-line-1">{{ item.title }}</view>
239
-        </view>
240
-      </view>
241
-    </HyCard>
242
-    <!-- 附近景点 -->
243
-    <HyCard title="附近景点" isBg :bgImg="minioUrl + '/index_spot_bg.png'">
244
-      <view class="index-spot-wrapper">
245
-        <view class="spot-item" v-for="index in 5" :key="index" @click="navigateToSpot">
246
-          <view class="spot-img-wrap">
247
-            <image src="" mode="scaleToFill" />
321
+          <view class="no-data-wrap" v-else>
322
+            <u-empty />
248 323
           </view>
249
-          <view class="spot-right-wrap">
250
-            <view class="right-header">
251
-              <view class="title">琅琊台风景区</view>
252
-              <view class="price">50元</view>
324
+        </template>
325
+        <template v-if="tabActive === 1">
326
+          <view class="tab-station-wrap column" v-if="powerInfo?.fastChargeTotal > 0">
327
+            <HyCharge
328
+              title="快充"
329
+              :freeNum="powerInfo?.fastChargeFree"
330
+              :totalNum="powerInfo?.fastChargeTotal"
331
+              :status="powerInfo?.fastChargeStatus"
332
+            />
333
+            <HyCharge
334
+              title="慢充"
335
+              :freeNum="powerInfo?.slowChargeFree"
336
+              :totalNum="powerInfo?.slowChargeTotal"
337
+              :status="powerInfo?.slowChargeStatus"
338
+            />
339
+          </view>
340
+          <view class="no-data-wrap" v-else>
341
+            <u-empty />
342
+          </view>
343
+        </template>
344
+      </view>
345
+      <!-- 服务设施 -->
346
+      <HyCard title="服务设施" :marginBottom="24">
347
+        <view class="index-service-wrapper" v-if="facilityList.length > 0">
348
+          <HyService v-for="(item, index) in facilityList" :key="index" :item="item"></HyService>
349
+        </view>
350
+        <view class="no-data-wrap" v-else>
351
+          <u-empty text="暂无服务设施数据" />
352
+        </view>
353
+      </HyCard>
354
+      <!-- 人气优选商家 -->
355
+      <HyCard title="人气优选商家" rightText="更多商家" isShowRight :marginBottom="24" @clickRight="navigateToShop">
356
+        <view class="index-shop-wrapper" v-if="shopList.length > 0">
357
+          <view class="shop-item" v-for="(item, index) in shopList" :key="index">
358
+            <view class="img-wrap">
359
+              <image :src="item.content.image" mode="scaleToFill" />
253 360
             </view>
254
-            <view class="right-tag-box">
255
-              <view class="s-tag-green">自然风光</view>
256
-              <view class="s-tag-green">历史遗迹</view>
257
-              <view class="s-tag-green">休闲度假</view>
361
+            <view class="item-text u-line-1">{{ item.title }}</view>
362
+          </view>
363
+        </view>
364
+        <view class="no-data-wrap" v-else>
365
+          <u-empty text="暂无商家数据" />
366
+        </view>
367
+      </HyCard>
368
+      <!-- 附近景点 -->
369
+      <HyCard title="附近景点" isBg :bgImg="minioUrl + '/index_spot_bg.png'">
370
+        <view class="index-spot-wrapper" v-if="scenicList.length > 0">
371
+          <view class="spot-item" v-for="(item, index) in scenicList" :key="index" @click="navigateToSpot(item.id)">
372
+            <view class="spot-img-wrap">
373
+              <image :src="item.content.image" mode="scaleToFill" />
258 374
             </view>
259
-            <view class="right-location-box">
260
-              <view class="left">
261
-                <image class="icon-address" :src="minioUrl + '/icon_address.png'" mode="scaleToFill" />
262
-                <view class="address-text-wrap">距您<text>30.5</text>公里,驾车预计<text>40</text>分钟</view>
375
+            <view class="spot-right-wrap">
376
+              <view class="right-header">
377
+                <view class="title">{{ item.title }}</view>
378
+                <view class="price">{{ item.content.isFree === 1 ? '免费' : item.content.price + '元' }}</view>
263 379
               </view>
264
-              <view class="right-go-icon" @click.stop="goToNavigation">
265
-                <image :src="minioUrl + '/icon_go.png'" mode="scaleToFill" />
380
+              <view class="right-tag-box">
381
+                <view class="s-tag-green" v-for="(tag, index) in item.content.tags" :key="index">{{ tag }}</view>
382
+              </view>
383
+              <view class="right-location-box">
384
+                <view class="left">
385
+                  <image class="icon-address" :src="minioUrl + '/icon_address.png'" mode="scaleToFill" />
386
+                  <view class="address-text-wrap"
387
+                    >距您<text>{{ item.content.distance }}</text
388
+                    >公里,驾车预计<text>{{ item.content.driveTime }}</text
389
+                    >分钟</view
390
+                  >
391
+                </view>
392
+                <view class="right-go-icon" @click.stop="goToNavigation(item.content.longitude, item.content.latitude, item.title)">
393
+                  <image :src="minioUrl + '/icon_go.png'" mode="scaleToFill" />
394
+                </view>
266 395
               </view>
267 396
             </view>
268 397
           </view>
269 398
         </view>
270
-      </view>
271
-    </HyCard>
272
-    <!-- 一键救援 -->
273
-    <HyPopup v-model="isRescueShow" width="700rpx" showClose>
274
-      <view class="popup-content-wrapper">
275
-        <view class="popup-content-item" @click="callPhone('96659')">
276
-          <view class="p-item-left">
277
-            <view class="left-title">车辆救援</view>
278
-            <view class="left-sub-title">山东高速集团出行服务热线</view>
399
+        <view class="no-data-wrap" v-else>
400
+          <u-empty text="暂无景点数据" />
401
+        </view>
402
+      </HyCard>
403
+      <!-- 一键救援 -->
404
+      <HyPopup v-model="isRescueShow" width="700rpx" showClose>
405
+        <view class="popup-content-wrapper">
406
+          <view class="popup-content-item" @click="callPhone('96659')">
407
+            <view class="p-item-left">
408
+              <view class="left-title">车辆救援</view>
409
+              <view class="left-sub-title">山东高速集团出行服务热线</view>
410
+            </view>
411
+            <view class="p-item-right">
412
+              <view class="mobile">96659</view>
413
+              <view class="icon-box">
414
+                <u-icon name="phone-fill" color="#fff" size="24"></u-icon>
415
+              </view>
416
+            </view>
279 417
           </view>
280
-          <view class="p-item-right">
281
-            <view class="mobile">96659</view>
282
-            <view class="icon-box">
283
-              <u-icon name="phone-fill" color="#fff" size="24"></u-icon>
418
+          <view class="popup-content-item" @click="callPhone('12122')">
419
+            <view class="p-item-left">
420
+              <view class="left-title">事故报警</view>
421
+              <view class="left-sub-title">山东高速交警报警热线</view>
422
+            </view>
423
+            <view class="p-item-right">
424
+              <view class="mobile">12122</view>
425
+              <view class="icon-box">
426
+                <u-icon name="phone-fill" color="#fff" size="24"></u-icon>
427
+              </view>
284 428
             </view>
285 429
           </view>
286 430
         </view>
287
-        <view class="popup-content-item" @click="callPhone('12122')">
288
-          <view class="p-item-left">
289
-            <view class="left-title">事故报警</view>
290
-            <view class="left-sub-title">山东高速交警报警热线</view>
291
-          </view>
292
-          <view class="p-item-right">
293
-            <view class="mobile">12122</view>
431
+      </HyPopup>
432
+      <!-- 失物招领 -->
433
+      <HyPopup v-model="isLostFoundShow" width="700rpx" showClose>
434
+        <view class="popup-content-wrapper">
435
+          <view class="popup-content-item column" @click="callPhone('0532-8889999')">
436
+            <view class="column-title">失物招领</view>
437
+            <view class="column-mobile">0532-8889999</view>
294 438
             <view class="icon-box">
295 439
               <u-icon name="phone-fill" color="#fff" size="24"></u-icon>
296 440
             </view>
297 441
           </view>
298 442
         </view>
299
-      </view>
300
-    </HyPopup>
301
-    <!-- 失物招领 -->
302
-    <HyPopup v-model="isLostFoundShow" width="700rpx" showClose>
303
-      <view class="popup-content-wrapper">
304
-        <view class="popup-content-item column" @click="callPhone('0532-8889999')">
305
-          <view class="column-title">失物招领</view>
306
-          <view class="column-mobile">0532-8889999</view>
307
-          <view class="icon-box">
308
-            <u-icon name="phone-fill" color="#fff" size="24"></u-icon>
309
-          </view>
310
-        </view>
311
-      </view>
312
-    </HyPopup>
313
-  </view>
443
+      </HyPopup>
444
+    </view>
445
+  </mescroll-body>
314 446
 </template>
315 447
 
316 448
 <style lang="scss" scoped>
449
+.no-data-wrap {
450
+  width: 100%;
451
+  height: 400rpx;
452
+  display: flex;
453
+  align-items: center;
454
+  justify-content: center;
455
+  box-sizing: border-box;
456
+}
317 457
 .popup-content-wrapper {
318 458
   height: 330rpx;
319 459
   background-color: #fff;
@@ -383,6 +523,9 @@ const callPhone = (phone: string) => {
383 523
     }
384 524
   }
385 525
 }
526
+:deep(.mescroll-upwarp) {
527
+  padding-top: 0;
528
+}
386 529
 .index-container {
387 530
   width: 100%;
388 531
   min-height: 100dvh;
@@ -498,7 +641,7 @@ const callPhone = (phone: string) => {
498 641
       width: 4rpx;
499 642
       height: 34rpx;
500 643
       background-color: #3866ae;
501
-      margin: 0 23rpx 0 13rpx;
644
+      margin: -1rpx 0 0 24rpx;
502 645
     }
503 646
     .tips-content {
504 647
       font-size: 25rpx;
@@ -607,6 +750,7 @@ const callPhone = (phone: string) => {
607 750
         .right-tag-box {
608 751
           display: flex;
609 752
           align-items: center;
753
+          flex-wrap: wrap;
610 754
           margin-top: 6rpx;
611 755
           .s-tag-green {
612 756
             width: 125rpx;
@@ -618,11 +762,12 @@ const callPhone = (phone: string) => {
618 762
             justify-content: center;
619 763
             font-size: 21rpx;
620 764
             color: #239450;
621
-            margin-right: 20rpx;
765
+            margin-right: 16rpx;
766
+            margin-bottom: 10rpx;
622 767
           }
623 768
         }
624 769
         .right-location-box {
625
-          margin-top: 50rpx;
770
+          margin-top: 8rpx;
626 771
           display: flex;
627 772
           align-items: center;
628 773
           justify-content: space-between;

+ 79 - 29
src/pages/service.vue

@@ -1,44 +1,92 @@
1 1
 <script setup lang="ts">
2
-import { ref } from 'vue'
2
+import { ref, computed } from 'vue'
3
+import { onLoad, onPageScroll, onShow } from '@dcloudio/uni-app'
4
+import { useListLoader } from '@/composables/useListLoader'
5
+import { getParkServiceList } from '@/api/home'
3 6
 import { useGlobal } from '@/composables/index'
4 7
 
5 8
 const { minioUrl } = useGlobal()
9
+
10
+const angle = ref(45)
11
+// 监听页面滚动
12
+onPageScroll((e) => {
13
+  if (e.scrollTop > 80) {
14
+    angle.value = 90
15
+  } else {
16
+    angle.value = 45
17
+  }
18
+})
19
+
20
+const rateMap = {
21
+  5: '五星级服务区',
22
+  4: '四星服务区',
23
+  3: '三星服务区',
24
+  2: '二星服务区',
25
+  1: '一星服务区'
26
+} as any
27
+
28
+const list = computed(() => {
29
+  return listData.value.map((item: any) => ({
30
+    ...item,
31
+    content: JSON.parse(item.content)
32
+  }))
33
+})
34
+
35
+// 列表加载
36
+const { listData, upCallback, downCallback, handleSearch, mescrollInit } = useListLoader({
37
+  apiFn: getParkServiceList,
38
+  initialParams: {}
39
+})
40
+
41
+// 导航功能
42
+const goToNavigation = (longitude: string, latitude: string, name: string) => {
43
+  uni.openLocation({
44
+    latitude: Number(latitude),
45
+    longitude: Number(longitude),
46
+    name: name,
47
+    success: () => {
48
+      console.log('导航功能调用成功')
49
+    }
50
+  })
51
+}
6 52
 </script>
7 53
 
8 54
 <template>
9
-  <view class="service-container">
10
-    <HyCard v-for="item in 5" :key="item" class="service-content-wrapper" marginBottom="36">
11
-      <view class="service-item">
12
-        <view class="item-header-wrap">
13
-          <view class="header-img-box">
14
-            <image src="" mode="scaleToFill" />
15
-            <view class="header-status-tag open-tag">开放中</view>
16
-          </view>
17
-          <view class="header-right-box">
18
-            <view class="u-line-1 title">黄岛服务区(沈海高速沈阳方向)</view>
19
-            <view class="rate-content">
20
-              <uni-rate :value="5" activeColor="#ff5702"></uni-rate>
21
-              <text class="rate-text">五星级服务区</text>
55
+  <mescroll-body @init="mescrollInit" :down="{ use: true, minAngle: angle }" @down="downCallback" @up="upCallback">
56
+    <view class="service-container">
57
+      <HyCard v-for="(item, index) in list" :key="index" class="service-content-wrapper" :marginBottom="36">
58
+        <view class="service-item">
59
+          <view class="item-header-wrap">
60
+            <view class="header-img-box">
61
+              <image :src="item.content.image" mode="scaleToFill" />
62
+              <view class="header-status-tag" :class="{ 'open-tag': item.content.status !== '建设中' }">{{ item.content.status }}</view>
22 63
             </view>
23
-            <view class="distance">55.6KM</view>
24
-            <view class="address-wrap">
25
-              <view class="block">
26
-                <view class="t">海口</view>
27
-                <image class="icon-arrive" :src="minioUrl + '/icon_arrive.png'" mode="scaleToFill" />
28
-                <view class="t">沈阳</view>
64
+            <view class="header-right-box">
65
+              <view class="u-line-1 title">{{ item.title }}</view>
66
+              <view class="rate-content">
67
+                <uni-rate :value="item.content.rate" activeColor="#ff5702"></uni-rate>
68
+                <text class="rate-text">{{ rateMap[item.content.rate] }}</text>
29 69
               </view>
30
-              <view class="icon-go">
31
-                <image :src="minioUrl + '/icon_blue_go.png'" mode="scaleToFill" />
70
+              <view class="distance">{{ item.content.distance }}KM</view>
71
+              <view class="address-wrap">
72
+                <view class="block">
73
+                  <view class="t">{{ item.content.startLocation }}</view>
74
+                  <image class="icon-arrive" :src="minioUrl + '/icon_arrive.png'" mode="scaleToFill" />
75
+                  <view class="t">{{ item.content.endLocation }}</view>
76
+                </view>
77
+                <view class="icon-go" @click="goToNavigation(item.content.endLongitude, item.content.endLatitude, item.content.title)">
78
+                  <image :src="minioUrl + '/icon_blue_go.png'" mode="scaleToFill" />
79
+                </view>
32 80
               </view>
33 81
             </view>
34 82
           </view>
83
+          <view class="item-bottom-wrap">
84
+            <HyTag needBorder v-for="(tag, i) in item.content.facilities" :key="i" :text="tag.name" :imgUrl="tag.img" />
85
+          </view>
35 86
         </view>
36
-        <view class="item-bottom-wrap">
37
-          <HyTag needBorder v-for="index in 8" :key="index" />
38
-        </view>
39
-      </view>
40
-    </HyCard>
41
-  </view>
87
+      </HyCard>
88
+    </view>
89
+  </mescroll-body>
42 90
 </template>
43 91
 
44 92
 <style lang="scss" scoped>
@@ -55,10 +103,12 @@ const { minioUrl } = useGlobal()
55 103
         width: 174rpx;
56 104
         height: 174rpx;
57 105
         position: relative;
106
+        border-radius: 14rpx;
58 107
         background-color: aliceblue;
59 108
         image {
60 109
           width: 174rpx;
61 110
           height: 174rpx;
111
+          border-radius: 14rpx;
62 112
         }
63 113
         .header-status-tag {
64 114
           position: absolute;
@@ -136,7 +186,7 @@ const { minioUrl } = useGlobal()
136 186
       }
137 187
     }
138 188
     .item-bottom-wrap {
139
-      width: 550rpx;
189
+      width: 580rpx;
140 190
       display: flex;
141 191
       flex-wrap: wrap;
142 192
       margin-top: 40rpx;

+ 6 - 1
src/stores/modules/user.ts

@@ -17,7 +17,7 @@ export const user = defineStore(
17 17
     const token = ref('')
18 18
     const role = ref(0) // 用户角色
19 19
     const permissions = ref<string[]>([]) // 权限
20
-
20
+    const parkId = ref('') // 服务区或者停车区id
21 21
     // 存储用户信息
22 22
     const setUserInfo = (val: useInfoResult) => {
23 23
       info.value = val
@@ -29,12 +29,16 @@ export const user = defineStore(
29 29
     const setPermissions = (val: string[]) => {
30 30
       permissions.value = val
31 31
     }
32
+    const setParkId = (val: string) => {
33
+      parkId.value = val
34
+    }
32 35
     // 清空所有信息
33 36
     const clearInfo = () => {
34 37
       info.value = {} as useInfoResult
35 38
       token.value = ''
36 39
       role.value = 0
37 40
       permissions.value = []
41
+      parkId.value = ''
38 42
     }
39 43
     return {
40 44
       info,
@@ -44,6 +48,7 @@ export const user = defineStore(
44 48
       setPermissions,
45 49
       setToken,
46 50
       setUserInfo,
51
+      setParkId,
47 52
       clearInfo
48 53
     }
49 54
   },

+ 8 - 0
src/styles/index.scss

@@ -1334,4 +1334,12 @@
1334 1334
     font-size: 26rpx;
1335 1335
     color: #333;
1336 1336
   }
1337
+}
1338
+
1339
+.no-data-container {
1340
+  display: flex;
1341
+  align-items: center;
1342
+  justify-content: center;
1343
+  width: 100%;
1344
+  min-height: 800rpx;
1337 1345
 }