瀏覽代碼

feat(cart): 添加购物车页面并集成地址定位功能

新增购物车页面,更新页面配置及组件引入。
添加地址 store 及获取地理位置逻辑,包含权限检查与授权引导流程。
更新底部导航栏配置,引入多个 Wot Design Uni 组件类型声明。
调整首页和个人中心页面结构,自定义导航栏样式,并引入相关静态资源图片。
zhangtao 2 周之前
父節點
當前提交
53b942ec15
共有 21 個文件被更改,包括 286 次插入12 次删除
  1. 8 2
      manifest.config.ts
  2. 2 0
      pages.config.ts
  3. 2 0
      src/auto-imports.d.ts
  4. 6 0
      src/components.d.ts
  5. 1 0
      src/composables/useTabbar.ts
  6. 9 2
      src/manifest.json
  7. 16 2
      src/pages.json
  8. 15 0
      src/pages/cart/index.vue
  9. 15 2
      src/pages/index/index.vue
  10. 120 3
      src/pages/my/index.vue
  11. 二進制
      src/static/my/1.png
  12. 二進制
      src/static/my/2.png
  13. 二進制
      src/static/my/3.png
  14. 二進制
      src/static/my/4.png
  15. 二進制
      src/static/my/5.png
  16. 二進制
      src/static/my/6.png
  17. 二進制
      src/static/my/7.png
  18. 二進制
      src/static/my/8.png
  19. 二進制
      src/static/my/9.png
  20. 90 0
      src/store/address.ts
  21. 2 1
      src/uni-pages.d.ts

+ 8 - 2
manifest.config.ts

@@ -60,8 +60,14 @@ export default defineManifestConfig({
       urlCheck: false,
     },
     usingComponents: true,
-    darkmode: true,
-    themeLocation: 'theme.json',
+    requiredPrivateInfos: ['getLocation', 'chooseLocation'],
+    permission: {
+      'scope.userLocation': {
+        desc: '你的位置信息将用于小程序推荐附近的商家和活动',
+      },
+    },
+    // darkmode: false,`
+    // themeLocation: 'theme.json',
   },
   'mp-alipay': {
     usingComponents: true,

+ 2 - 0
pages.config.ts

@@ -36,6 +36,8 @@ export default defineUniPages({
     borderStyle: '@tabBorderStyle',
     list: [{
       pagePath: 'pages/index/index',
+    }, {
+      pagePath: 'pages/cart/index',
     }, {
       pagePath: 'pages/my/index',
     }],

+ 2 - 0
src/auto-imports.d.ts

@@ -154,6 +154,7 @@ declare global {
   const unrefElement: typeof import('@vueuse/core')['unrefElement']
   const until: typeof import('@vueuse/core')['until']
   const useActiveElement: typeof import('@vueuse/core')['useActiveElement']
+  const useAddressStore: typeof import('./store/address')['useAddressStore']
   const useAnimate: typeof import('@vueuse/core')['useAnimate']
   const useArrayDifference: typeof import('@vueuse/core')['useArrayDifference']
   const useArrayEvery: typeof import('@vueuse/core')['useArrayEvery']
@@ -506,6 +507,7 @@ declare module 'vue' {
     readonly unrefElement: UnwrapRef<typeof import('@vueuse/core')['unrefElement']>
     readonly until: UnwrapRef<typeof import('@vueuse/core')['until']>
     readonly useActiveElement: UnwrapRef<typeof import('@vueuse/core')['useActiveElement']>
+    readonly useAddressStore: UnwrapRef<typeof import('./store/address')['useAddressStore']>
     readonly useAnimate: UnwrapRef<typeof import('@vueuse/core')['useAnimate']>
     readonly useArrayDifference: UnwrapRef<typeof import('@vueuse/core')['useArrayDifference']>
     readonly useArrayEvery: UnwrapRef<typeof import('@vueuse/core')['useArrayEvery']>

+ 6 - 0
src/components.d.ts

@@ -11,8 +11,14 @@ declare module 'vue' {
     GlobalMessage: typeof import('./components/GlobalMessage.vue')['default']
     GlobalToast: typeof import('./components/GlobalToast.vue')['default']
     PrivacyPopup: typeof import('./components/PrivacyPopup.vue')['default']
+    WdButton: typeof import('wot-design-uni/components/wd-button/wd-button.vue')['default']
+    WdCard: typeof import('wot-design-uni/components/wd-card/wd-card.vue')['default']
+    WdCell: typeof import('wot-design-uni/components/wd-cell/wd-cell.vue')['default']
+    WdCellGroup: typeof import('wot-design-uni/components/wd-cell-group/wd-cell-group.vue')['default']
     WdConfigProvider: typeof import('wot-design-uni/components/wd-config-provider/wd-config-provider.vue')['default']
+    WdIcon: typeof import('wot-design-uni/components/wd-icon/wd-icon.vue')['default']
     WdMessageBox: typeof import('wot-design-uni/components/wd-message-box/wd-message-box.vue')['default']
+    WdNavbar: typeof import('wot-design-uni/components/wd-navbar/wd-navbar.vue')['default']
     WdNotify: typeof import('wot-design-uni/components/wd-notify/wd-notify.vue')['default']
     WdPopup: typeof import('wot-design-uni/components/wd-popup/wd-popup.vue')['default']
     WdTabbar: typeof import('wot-design-uni/components/wd-tabbar/wd-tabbar.vue')['default']

+ 1 - 0
src/composables/useTabbar.ts

@@ -8,6 +8,7 @@ export interface TabbarItem {
 
 const tabbarItems = ref<TabbarItem[]>([
   { name: 'home', value: null, active: true, title: '首页', icon: 'home' },
+  { name: 'cart', value: null, active: false, title: '购物车', icon: 'cart' },
   { name: 'my', value: null, active: false, title: '我的', icon: 'user' },
 ])
 

+ 9 - 2
src/manifest.json

@@ -50,8 +50,15 @@
     "optimization": {
       "subPackages": true
     },
-    "darkmode": true,
-    "themeLocation": "theme.json"
+    "requiredPrivateInfos": [
+      "getLocation",
+      "chooseLocation"
+    ],
+    "permission": {
+      "scope.userLocation": {
+        "desc": "你的位置信息将用于小程序推荐附近的商家和活动"
+      }
+    }
   },
   "mp-alipay": {
     "usingComponents": true,

+ 16 - 2
src/pages.json

@@ -6,7 +6,17 @@
       "name": "home",
       "layout": "tabbar",
       "style": {
-        "navigationBarTitleText": "首页"
+        "navigationBarTitleText": "首页",
+        "navigationStyle": "custom"
+      }
+    },
+    {
+      "path": "pages/cart/index",
+      "type": "page",
+      "name": "cart",
+      "layout": "tabbar",
+      "style": {
+        "navigationBarTitleText": "购物车"
       }
     },
     {
@@ -15,7 +25,8 @@
       "name": "my",
       "layout": "tabbar",
       "style": {
-        "navigationBarTitleText": "我的"
+        "navigationBarTitleText": "个人中心",
+        "navigationStyle": "custom"
       }
     }
   ],
@@ -45,6 +56,9 @@
       {
         "pagePath": "pages/index/index"
       },
+      {
+        "pagePath": "pages/cart/index"
+      },
       {
         "pagePath": "pages/my/index"
       }

+ 15 - 0
src/pages/cart/index.vue

@@ -0,0 +1,15 @@
+<script setup lang="ts">
+definePage({
+  name: 'cart',
+  layout: 'tabbar',
+  style: {
+    navigationBarTitleText: '购物车',
+  },
+})
+</script>
+
+<template>
+  <view class="box-border py-3">
+    1234156
+  </view>
+</template>

+ 15 - 2
src/pages/index/index.vue

@@ -4,12 +4,25 @@ definePage({
   layout: 'tabbar',
   style: {
     navigationBarTitleText: '首页',
+    navigationStyle: 'custom',
   },
 })
+const addressStore = useAddressStore()
+onMounted(() => {
+  addressStore.getLocation()
+})
 </script>
 
 <template>
-  <view class="box-border py-3">
-    1234156
+  <view class="box-border">
+    <wd-navbar
+      title="" custom-style="background-color: transparent !important;" :bordered="false"
+      :z-index="99"
+      safe-area-inset-top placeholder fixed
+    >
+      <template #left>
+        <view>富力中心</view>
+      </template>
+    </wd-navbar>
   </view>
 </template>

+ 120 - 3
src/pages/my/index.vue

@@ -1,15 +1,132 @@
 <script setup lang="ts">
+import wait from '@/static/my/1.png'
+import wait1 from '@/static/my/2.png'
+import down from '@/static/my/6.png'
+import refund from '@/static/my/3.png'
+
 definePage({
   name: 'my',
   layout: 'tabbar',
   style: {
-    navigationBarTitleText: '我的',
+    navigationBarTitleText: '个人中心',
+    navigationStyle: 'custom',
   },
 })
+
+const tabList = ref([
+  { title: '待支付', icon: wait },
+  { title: '待收货', icon: wait1 },
+  { title: '已完成', icon: down },
+  { title: '退款售后', icon: refund },
+])
 </script>
 
 <template>
-  <view class="min-h-screen bg-gray-100 py-3 dark:bg-[var(--wot-dark-background)]">
-    我的
+  <view class="page-class bg-#F9F9F9 dark:bg-[var(--wot-dark-background)]">
+    <wd-navbar
+      title="个人中心" custom-style="background-color: transparent !important;" :bordered="false"
+      safe-area-inset-top fixed :z-index="99"
+    />
+    <view class="header relative h-408rpx rounded-18px">
+      <view class="absolute bottom-100rpx left-0 box-border w-full flex items-center justify-between pl48rpx pr58rpx">
+        <image src="@/static/my/9.png" alt="" class="h100rpx w100rpx" />
+        <view class="text-32rpx font-semibold">
+          请登录后使用完整功能
+        </view>
+        <wd-button custom-class="login-btn">
+          登录
+        </wd-button>
+      </view>
+    </view>
+    <view class="relative z-50 -mt48rpx">
+      <wd-card>
+        <template #title>
+          <view class="flex items-center justify-between">
+            <view class="text-32rpx font-semibold">
+              订单列表
+            </view>
+            <view class="flex items-center">
+              <view class="text-28rpx">
+                查看全部
+              </view>
+              <wd-icon name="arrow-right" size="18px" />
+            </view>
+          </view>
+        </template>
+        <view class="grid grid-cols-4 gap-4">
+          <view v-for="item in tabList" :key="item.title" class="flex flex-col items-center justify-center">
+            <image :src="item.icon" class="h56rpx w56rpx" />
+            <view class="mt20rpx text-24rpx">
+              {{ item.title }}
+            </view>
+          </view>
+        </view>
+        <view class="h20rpx" />
+      </wd-card>
+    </view>
+    <view class="item-cell mt20rpx">
+      <wd-card custom-class="card">
+        <wd-cell-group custom-class="cell-group">
+          <wd-cell title="收货地址" custom-title-class="cell-title" clickable is-link>
+            <template #icon>
+              <image src="@/static/my/4.png" class="h50rpx w50rpx" />
+            </template>
+          </wd-cell>
+          <wd-cell title="联系平台客服" custom-title-class="cell-title" clickable is-link>
+            <template #icon>
+              <image src="@/static/my/5.png" class="h50rpx w50rpx" />
+            </template>
+          </wd-cell>
+        </wd-cell-group>
+      </wd-card>
+    </view>
+    <view class="item-cell mt20rpx">
+      <wd-card custom-class="card">
+        <wd-cell-group custom-class="cell-group">
+          <wd-cell title="积分" custom-title-class="cell-title" clickable is-link>
+            <template #icon>
+              <image src="@/static/my/7.png" class="h50rpx w50rpx" />
+            </template>
+          </wd-cell>
+          <wd-cell title="评价" custom-title-class="cell-title" clickable is-link>
+            <template #icon>
+              <image src="@/static/my/8.png" class="h50rpx w50rpx" />
+            </template>
+          </wd-cell>
+        </wd-cell-group>
+      </wd-card>
+    </view>
   </view>
 </template>
+
+<style lang="scss" scoped>
+.header {
+  background: linear-gradient(113deg, #B8D9FF 0%, #92C5FF 25%, #BEDDFF 51%, #8FC4FF 83%, #DFEEFF 100%);
+}
+
+.page-class {
+  :deep() {
+    .login-btn {
+      min-width: 0 !important;
+      width: 180rpx;
+    }
+
+    .cell-title {
+      padding-left: 20rpx;
+    }
+  }
+
+}
+
+.item-cell {
+  :deep() {
+    .card {
+      padding: 0 !important;
+    }
+    .cell-group{
+      overflow: hidden;
+      border-radius: 12rpx;
+    }
+  }
+}
+</style>

二進制
src/static/my/1.png


二進制
src/static/my/2.png


二進制
src/static/my/3.png


二進制
src/static/my/4.png


二進制
src/static/my/5.png


二進制
src/static/my/6.png


二進制
src/static/my/7.png


二進制
src/static/my/8.png


二進制
src/static/my/9.png


+ 90 - 0
src/store/address.ts

@@ -0,0 +1,90 @@
+import { defineStore } from 'pinia'
+
+interface addressState {
+  Location: {
+    /**
+     * 纬度
+     */
+    latitude: number | null
+    /**
+     * 经度
+     */
+    longitude: number | null
+  }
+}
+export const useAddressStore = defineStore('address', {
+  state: (): addressState => ({
+    Location: {
+      latitude: null,
+      longitude: null,
+    },
+  }),
+  actions: {
+  // 获取地理位置的函数
+    getLocation() {
+      uni.showLoading({ mask: true })
+      // 1. 检查当前授权状态
+      uni.getSetting({
+        success: (res) => {
+          const locationAuth = res.authSetting['scope.userLocation']
+          // 2. 已授权 - 直接获取位置
+          if (locationAuth) {
+            this.fetchActualLocation()
+            uni.hideLoading()
+          }
+          // 3. 未授权 - 尝试请求授权
+          else {
+            uni.authorize({
+              scope: 'scope.userLocation',
+              success: () => this.fetchActualLocation(),
+              fail: () => this.showAuthGuide(), // 用户拒绝,显示引导
+              complete: () => uni.hideLoading(),
+            })
+          }
+        },
+      })
+    },
+
+    // 实际获取位置的方法
+    fetchActualLocation() {
+      uni.getLocation({
+        type: 'wgs84',
+        success: (res) => {
+          console.log('位置获取成功', res)
+          this.Location.latitude = res.latitude
+          this.Location.longitude = res.longitude
+        },
+        fail: (err) => {
+          console.log(err, '获取位置失败')
+
+          uni.showToast({ title: `获取位置失败${err}`, icon: 'none' })
+        },
+      })
+    },
+
+    // 显示授权引导
+    showAuthGuide() {
+      uni.showModal({
+        title: '需要位置权限',
+        content: '请开启位置授权以使用完整功能',
+        confirmText: '去设置',
+        success: (res) => {
+          if (res.confirm) {
+            // 4. 用户点击确认后打开设置页
+            wx.openSetting({
+              success: (settingRes) => {
+                // 5. 检查用户是否在设置页中开启了授权
+                if (settingRes.authSetting['scope.userLocation']) {
+                  this.fetchActualLocation()
+                }
+                else {
+                  console.log('用户仍未授权')
+                }
+              },
+            })
+          }
+        },
+      })
+    },
+  },
+})

+ 2 - 1
src/uni-pages.d.ts

@@ -5,6 +5,7 @@
 
 type _LocationUrl =
   "/pages/index/index" |
+  "/pages/cart/index" |
   "/pages/my/index";
 
 interface NavigateToOptions {
@@ -13,7 +14,7 @@ interface NavigateToOptions {
 interface RedirectToOptions extends NavigateToOptions {}
 
 interface SwitchTabOptions {
-  url: "/pages/index/index" | "/pages/my/index"
+  url: "/pages/index/index" | "/pages/cart/index" | "/pages/my/index"
 }
 
 type ReLaunchOptions = NavigateToOptions | SwitchTabOptions;