zouzexu 1 день назад
Родитель
Сommit
6adae1671c

+ 45 - 0
src/api/api.type.d.ts

@@ -2435,6 +2435,7 @@ namespace Api {
      */
     actualTotal?: number
     platformVolume?: number
+
   }
 
   export interface RechargeLevel {
@@ -2498,6 +2499,50 @@ namespace Api {
     [property: string]: any
   }
 
+  export interface UserVehicleVO {
+    /**
+     * 车辆品牌
+     */
+    brand?: string
+    /**
+     * 车辆颜色
+     */
+    color?: string
+    /**
+     * 创建时间
+     */
+    createTime?: string
+    /**
+     * 车辆ID
+     */
+    id?: number
+    /**
+     * 是否默认车辆(0-否 1-是)
+     */
+    isDefault?: number
+    /**
+     * 车牌号
+     */
+    licensePlate?: string
+    /**
+     * 车辆型号
+     */
+    model?: string
+    /**
+     * 备注
+     */
+    remark?: string
+    /**
+     * 用户ID
+     */
+    userId?: number
+    /**
+     * 车辆类型(1-新能源 2-燃油车 3-混合动力)
+     */
+    vehicleType?: number
+    [property: string]: any
+  }
+
   interface videoRightHomePage {
     list?: VideoProductVo[]
   }

+ 5 - 0
src/api/apiDefinitions.ts

@@ -104,6 +104,11 @@ export default {
   'charge.wxJsApiPay': ['POST', '/smqjh-oms/service/pay/jsapi'],
   'charge.getPurchaseRecordPage': ['POST', '/smqjh-system/applet/v1/purchaseRecord/getPurchaseRecordPage'],
   'charge.userCouponRefund':['GET','/smqjh-oms/api/v1/order_cd/userCouponRefund'],
+  'charge.default':['GET','/smqjh-system/applet/v1/vehicle/default'],
+  'charge.vehicleList':['GET','/smqjh-system/applet/v1/vehicle/list'],
+  'charge.addVehicle':['POST','/smqjh-system/applet/v1/vehicle'],
+  'charge.setDefault':['PUT','/smqjh-system/applet/v1/vehicle/default/{id}'],
+  'charge.deleteVehicle':['DELETE','/smqjh-system/applet/v1/vehicle/{id}'],
 
   'videoRight.findAppByPage': ['GET', '/smqjh-pms/app-api/v1/videoProduct/findAppByPage'],
   'videoRight.goodsDetail': ['GET', '/smqjh-pms/app-api/v1/videoProduct/findById'],

+ 45 - 0
src/api/globals.d.ts

@@ -973,6 +973,51 @@ declare global {
       >(
         config: Config
       ): Alova2Method<apiResData<any>, 'charge.userCouponRefund', Config>;
+
+      default<
+        Config extends Alova2MethodConfig<apiResData<Api.UserVehicleVO>> & {}
+      >(
+        config: Config
+      ): Alova2Method<apiResData<Api.UserVehicleVO>, 'charge.default', Config>;
+
+      vehicleList<
+        Config extends Alova2MethodConfig<apiResData<Api.UserVehicleVO[]>> & {}
+      >(
+        config: Config
+      ): Alova2Method<apiResData<Api.UserVehicleVO[]>, 'charge.vehicleList', Config>;
+
+      addVehicle<
+        Config extends Alova2MethodConfig<listData<any>> & {
+          data: {
+            /**
+             * 车牌号
+             */
+            licensePlate: string;
+            /**
+            * 用户ID
+            */
+            userId: number;
+          }
+        }
+      >(
+        config: Config
+      ): Alova2Method<listData<any>, 'charge.addVehicle', Config>;
+
+      setDefault<
+        Config extends Alova2MethodConfig<apiResData<any>> & {
+          pathParams: { id: string };
+        }
+      >(
+        config: Config
+      ): Alova2Method<apiResData<any>, 'charge.setDefault', Config>;
+
+      deleteVehicle<
+        Config extends Alova2MethodConfig<apiResData<any>> & {
+          pathParams: { id: string };
+        }
+      >(
+        config: Config
+      ): Alova2Method<apiResData<any>, 'charge.deleteVehicle', Config>;
     }
 
     videoRight: {

+ 2 - 2
src/config/index.ts

@@ -7,13 +7,13 @@ const mapEnvVersion = {
   // develop: 'http://192.168.0.157:8080',
   // develop: 'http://192.168.1.253:8080',
   // develop: 'http://192.168.0.19:8080', // 邓
-  develop: 'http://192.168.0.217:8080', // 黄
+  // develop: 'http://192.168.0.217:8080', // 黄
   // develop: 'http://192.168.0.11:8080', // 王
   // develop: 'http://192.168.1.89:8080', // 田
   // develop: 'http://74949mkfh190.vicp.fun', // 付
   // develop: 'http://47.109.84.152:8081',
   // develop: 'https://5ed0f7cc.r9.vip.cpolar.cn',
-  // develop: 'https://smqjh.api.zswlgz.com',
+  develop: 'https://smqjh.api.zswlgz.com',
   /**
    * 体验版
    */

+ 47 - 0
src/pages.json

@@ -397,6 +397,14 @@
     {
       "root": "subPack-charge",
       "pages": [
+        {
+          "path": "chargeAddPlate/chargeAddPlate",
+          "name": "charge-add-plate",
+          "islogin": false,
+          "style": {
+            "navigationBarTitleText": "添加车牌"
+          }
+        },
         {
           "path": "chargeBuyaTicketList/chargeBuyaTicketList",
           "name": "charge-buy-a-ticket-list",
@@ -450,6 +458,14 @@
             "navigationStyle": "custom"
           }
         },
+        {
+          "path": "chargePlateList/chargePlateList",
+          "name": "charge-plate-list",
+          "islogin": true,
+          "style": {
+            "navigationBarTitleText": "车牌管理"
+          }
+        },
         {
           "path": "chargeSearchList/chargeSearchList",
           "name": "cahrge-search-list",
@@ -632,6 +648,37 @@
           }
         }
       ]
+    },
+    {
+      "root": "subPack-attractions",
+      "pages": [
+        {
+          "path": "attractionsDetail/attractionsDetail",
+          "name": "attractions-detail",
+          "islogin": false,
+          "style": {
+            "navigationBarTitleText": "",
+            "navigationStyle": "custom"
+          }
+        },
+        {
+          "path": "attractionsReservation/attractionsReservation",
+          "name": "attractions-reservation-info",
+          "islogin": true,
+          "style": {
+            "navigationBarTitleText": "预定信息"
+          }
+        },
+        {
+          "path": "commonTab/index",
+          "name": "attractions-tabbar",
+          "islogin": false,
+          "style": {
+            "navigationBarTitleText": "",
+            "navigationStyle": "custom"
+          }
+        }
+      ]
     }
   ]
 }

+ 1 - 1
src/pages/index/index.vue

@@ -61,7 +61,7 @@ const navList = computed(() => {
     { icon: `${StaticUrl}/smqjh-vip.png`, title: '视频权益', name: 'video-rights-tabbar', show: !isOnlineAudit.value },
     { icon: `${StaticUrl}/smqjh-djk.png`, title: '大健康', name: 'djk-homeTabbar', show: true },
     { icon: `${StaticUrl}/smqjh-jiayou.png`, title: '加油', name: 'refueling-tabbar', show: true }, // refueling-tabbar
-
+    { icon: `${StaticUrl}/smqjh-attractions.png`, title: '景区', name: 'attractions-tabbar', show: true },
     { icon: `${StaticUrl}/smqjh-diancan.png`, title: '大牌点餐', name: '', show: !isOnlineAudit.value },
     { icon: `${StaticUrl}/smqjh-jiudian.png`, title: '酒店民宿', name: '', show: !isOnlineAudit.value },
     { icon: `${StaticUrl}/smqjh-daijia.png`, title: '代驾', name: '', show: !isOnlineAudit.value },

+ 232 - 0
src/subPack-attractions/attractionsDetail/attractionsDetail.vue

@@ -0,0 +1,232 @@
+<script setup lang="ts">
+import DatePicker from '../components/DatePicker.vue'
+import { StaticUrl } from '@/config'
+import router from '@/router'
+
+const { statusBarHeight, opcity } = storeToRefs(useSysStore())
+
+// 轮播图数据
+const swiperList = ref([
+  'https://picsum.photos/400/300?random=1',
+  'https://picsum.photos/400/300?random=2',
+  'https://picsum.photos/400/300?random=3',
+])
+const currentSwiper = ref(0)
+
+// 选中的日期
+const selectedDate = ref(new Date(2026, 2, 21)) // 默认选中2026年3月21日
+
+// 日历数据(按月份存储,key为 'YYYY-MM')
+const calendarDataMap = ref<Record<string, Array<{ day: number, status?: string, price?: number, selected?: boolean }>>>({
+  '2026-03': [
+    { day: 21, status: '充足', price: 290 },
+    { day: 27, status: '售罄', price: 290 },
+  ],
+  '2026-04': [
+    { day: 1, status: '充足', price: 280 },
+    { day: 5, status: '紧张', price: 300 },
+    { day: 10, status: '售罄', price: 280 },
+  ],
+})
+
+// 当前显示的月份
+const currentYearMonth = ref({ year: 2026, month: 3 })
+
+// 获取当前月份的日期数据
+const currentMonthDays = computed(() => {
+  const key = `${currentYearMonth.value.year}-${String(currentYearMonth.value.month).padStart(2, '0')}`
+  return calendarDataMap.value[key] || []
+})
+
+// 景区信息
+const attractionInfo = ref({
+  name: '贵州黄果树风景名胜区',
+  address: '镇宁布依族苗族自治县-黄果树镇贵黄公路',
+  price: 290,
+  title: '日场门票+观光车+飞越黄果树观影票+吉祥物+冰箱贴 经典必打卡',
+})
+
+definePage({
+  name: 'attractions-detail',
+  islogin: false,
+  style: {
+    navigationBarTitleText: '',
+    navigationStyle: 'custom',
+  },
+})
+
+onMounted(() => {
+  opcity.value = 0
+})
+
+onPageScroll((e) => {
+  const calculatedOpacity = e.scrollTop / 100
+  opcity.value = Math.min(1, Math.max(0.1, calculatedOpacity))
+})
+
+function handleNav() {
+  uni.openLocation({
+    latitude: 25.9913,
+    longitude: 105.6687,
+    name: attractionInfo.value.name,
+    address: attractionInfo.value.address,
+  })
+}
+
+function handleService() {
+  uni.makePhoneCall({
+    phoneNumber: '400-123-4567',
+  })
+}
+
+function handleOrder() {
+  router.push({ name: 'attractions-order' })
+}
+
+function handleBook() {
+  router.push({ name: 'attractions-reservation-info' })
+}
+
+// 处理日期选择
+function handleDateSelect(e: { date: Date, day: number, item?: any }) {
+  selectedDate.value = e.date
+  console.log('选中日期:', e.date, '日期数据:', e.item)
+}
+
+// 处理月份变化
+function handleMonthChange(e: { year: number, month: number }) {
+  currentYearMonth.value = { year: e.year, month: e.month }
+  console.log('切换月份:', e.year, '年', e.month, '月')
+}
+</script>
+
+<template>
+  <view class="min-h-screen bg-#F5F5F5">
+    <wd-navbar
+      title="景区门票"
+      :custom-style="`background-color: rgba(226, 255, 145, ${opcity})`"
+      :bordered="false"
+      :z-index="9999"
+      safe-area-inset-top
+      left-arrow
+      fixed
+      @click-left="router.back()"
+    />
+    <!-- 轮播图 -->
+    <wd-swiper
+      v-model:current="currentSwiper"
+      :list="swiperList"
+      :indicator="false"
+      :height="300"
+      class="w-full"
+    />
+
+    <!-- 价格标题区域 -->
+    <view class="relative z-10 rounded-t-32rpx bg-white px24rpx pt30rpx -mt-30rpx">
+      <view class="flex items-baseline text-#FF4D3A">
+        <text class="text-24rpx">
+          ¥
+        </text>
+        <text class="mx4rpx text-48rpx font-bold">
+          {{ attractionInfo.price }}
+        </text>
+        <text class="text-24rpx text-gray">
+          起
+        </text>
+      </view>
+      <view class="mt16rpx text-32rpx font-semibold line-height-[1.4]">
+        {{ attractionInfo.title }}
+      </view>
+
+      <!-- 地址导航卡片 -->
+      <view
+        class="mt24rpx h160rpx flex items-center justify-between bg-cover bg-center px24rpx"
+        :style="{ backgroundImage: `url(${StaticUrl}/djk-shop-nav-bg.png)` }"
+      >
+        <view class="w450rpx">
+          <view class="line-clamp-1 text-32rpx font-semibold">
+            {{ attractionInfo.name }}
+          </view>
+          <view class="line-clamp-1 mt12rpx flex items-center text-24rpx text-gray">
+            {{ attractionInfo.address }}
+          </view>
+        </view>
+        <view class="flex flex-col items-center justify-center" @click="handleNav">
+          <image
+            :src="`${StaticUrl}/djk-shop-dh.png`"
+            class="h40rpx w40rpx"
+          />
+          <view class="mt8rpx text-24rpx">
+            导航
+          </view>
+        </view>
+      </view>
+
+      <!-- 选择日期 -->
+      <view class="mt24rpx">
+        <DatePicker
+          v-model="selectedDate"
+          title="选择日期"
+          :day-data="currentMonthDays"
+          class="mt24rpx"
+          @select="handleDateSelect"
+          @month-change="handleMonthChange"
+        />
+      </view>
+
+      <!-- 预定须知 -->
+      <view class="mt24rpx pb40rpx">
+        <view class="text-32rpx font-semibold">
+          预定须知
+        </view>
+        <view class="mt16rpx text-28rpx font-semibold">
+          退款说明
+        </view>
+        <view class="mt12rpx text-26rpx text-gray line-height-[1.6]">
+          <view>使用日期当天23:59(含)之前申请取消,不收取损失费</view>
+          <view class="mt8rpx">
+            使用日期当天23:59之后申请取消,收取100%损失费;激活后不可退
+          </view>
+          <view class="mt8rpx">
+            如使用优惠,则损失费用按照优惠前金额的比例收取,最高不超过实付金额
+          </view>
+          <view class="mt8rpx">
+            产品不支持部分退
+          </view>
+        </view>
+      </view>
+    </view>
+
+    <!-- 底部占位 -->
+    <view class="h120rpx" />
+
+    <!-- 底部操作栏 -->
+    <view
+      class="fixed bottom-0 left-0 z-100 box-border w-full flex items-center justify-between border-t border-#eee bg-white px24rpx py16rpx"
+      :style="{ paddingBottom: `${(Number(statusBarHeight) || 44) - 20}px` }"
+    >
+      <view class="flex items-center border-t-#EEEEEE">
+        <view class="mr-40rpx flex flex-col items-center" @click="handleService">
+          <wd-icon name="service" size="22px" color="#666" />
+          <text class="mt4rpx text-22rpx text-#666">
+            客服
+          </text>
+        </view>
+        <view class="flex flex-col items-center" @click="handleOrder">
+          <wd-icon name="list" size="22px" color="#666" />
+          <text class="mt4rpx text-22rpx text-#666">
+            订单
+          </text>
+        </view>
+      </view>
+      <view
+        class="h80rpx w-400rpx flex items-center justify-center rounded-40rpx bg-#9ED605 px80rpx text-28rpx text-#FFF font-semibold"
+        @click="handleBook"
+      >
+        立即预定
+      </view>
+    </view>
+  </view>
+</template>
+
+<style lang="scss" scoped></style>

+ 138 - 0
src/subPack-attractions/attractionsReservation/attractionsReservation.vue

@@ -0,0 +1,138 @@
+<script setup lang="ts">
+definePage({
+  name: 'attractions-reservation-info',
+  islogin: true,
+  style: {
+    navigationBarTitleText: '预定信息',
+  },
+})
+const columns = ref(['选项1', '选项2', '选项3', '选项4', '选项5', '选项6', '选项7'])
+const value = ref('选项1')
+
+function handleConfirm({ value }) {
+  value.value = value
+}
+</script>
+
+<template>
+  <view class="reservation-info-page">
+    <view class="px-24rpx">
+      <view class="h-20rpx" />
+      <view class="rounded-16rpx bg-#FFF p-24rpx">
+        <view class="text-32rpx font-bold">
+          日场门票+观光车+飞越黄果树观影票+吉祥物+冰箱贴 经典必打卡
+        </view>
+        <view class="mt-24rpx h-2rpx w-full bg-#F0F0F0" />
+        <view class="mt-24rpx flex items-center gap-10rpx text-28rpx">
+          <wd-icon name="calendar" size="22px" />
+          <text>选定日期:2026年3月22日</text>
+        </view>
+      </view>
+      <view class="mt-20rpx flex items-center justify-between rounded-16rpx bg-#FFF p-24rpx">
+        <view class="text-28rpx">
+          选择数量
+        </view>
+        <view class="flex items-center gap-24rpx">
+          <view class="h-36rpx w-36rpx rounded-50% bg-#F0F0F0 text-center text-28rpx text-#AAAAAA font-600 line-height-[36rpx]">
+            -
+          </view>
+          <view class="text-24rpx font-400">
+            10
+          </view>
+          <view class="h-36rpx w-36rpx rounded-50% bg-#E8FFA7 text-center text-28rpx text-#9ED605 font-600 line-height-[36rpx]">
+            +
+          </view>
+        </view>
+      </view>
+      <!-- 单游客start -->
+      <view class="mt-20rpx rounded-16rpx bg-#FFF p-24rpx">
+        <view class="text-28rpx font-bold">
+          联系人
+        </view>
+        <view>
+          <wd-input placeholder="请输入联系人姓名" label="联系人姓名" required />
+        </view>
+        <view class="h-2rpx w-full bg-#F0F0F0" />
+        <view>
+          <wd-picker v-model="value" required :columns="columns" label="证件类型" @confirm="handleConfirm" />
+        </view>
+        <view class="h-2rpx w-full bg-#F0F0F0" />
+        <view>
+          <wd-input placeholder="请输入证件号码" label="证件号码" required />
+        </view>
+        <view class="h-2rpx w-full bg-#F0F0F0" />
+        <view>
+          <wd-input placeholder="请输入手机号码" label="手机号码" required />
+        </view>
+      </view>
+      <!-- 单游客end -->
+      <!-- 多游客start -->
+      <view class="mt-20rpx rounded-16rpx bg-#FFF p-24rpx">
+        <view class="text-28rpx font-bold">
+          联系人
+        </view>
+        <view>
+          <wd-input placeholder="请输入联系人姓名" label="联系人姓名" required />
+        </view>
+        <view class="h-2rpx w-full bg-#F0F0F0" />
+        <view>
+          <wd-input placeholder="请输入手机号码" label="手机号码" required />
+        </view>
+      </view>
+      <view class="mt-20rpx rounded-16rpx bg-#FFF p-24rpx">
+        <view class="text-28rpx font-bold">
+          游客信息
+        </view>
+        <view>
+          <wd-input placeholder="请输入游客姓名" label="游客姓名1" required />
+        </view>
+        <view class="h-2rpx w-full bg-#F0F0F0" />
+        <view>
+          <wd-picker v-model="value" required :columns="columns" label="证件类型" @confirm="handleConfirm" />
+        </view>
+        <view class="h-2rpx w-full bg-#F0F0F0" />
+        <view>
+          <wd-input placeholder="请输入证件号码" label="证件号码" required />
+        </view>
+      </view>
+      <!-- 多游客end -->
+      <view class="mt-20rpx rounded-16rpx bg-#FFF">
+        <wd-input placeholder="如有特殊需要,请留言" label="订单备注" />
+      </view>
+      <view class="mt-20rpx flex items-center justify-between rounded-16rpx bg-#FFF p-24rpx">
+        <view class="text-28rpx">
+          当前可用积分
+        </view>
+        <view class="text-32rpx font-bold">
+          1000积分
+        </view>
+      </view>
+    </view>
+    <view class="fixed bottom-0 left-0 z-100 box-border h-174rpx w-full flex items-center justify-between bg-#FFF px-24rpx">
+      <view class="flex items-center">
+        <view class="text-24rpx">
+          合计
+        </view>
+        <view class="text-32rpx text-#FF4A39 font-bold">
+          <text class="text-26rpx">
+            ¥
+          </text>
+          290
+        </view>
+      </view>
+      <view class="flex items-center gap-20rpx">
+        <view class="flex items-center">
+          <text class="text-24rpx">
+            明细
+          </text>
+          <wd-icon name="arrow-up" size="18px" />
+        </view>
+        <view class="h-80rpx w-180rpx rounded-40rpx bg-#9ED605 text-center text-28rpx text-#FFF font-bold line-height-[80rpx]">
+          提交
+        </view>
+      </view>
+    </view>
+  </view>
+</template>
+
+<style lang="scss" scoped></style>

+ 80 - 0
src/subPack-attractions/commonTab/components/homeList.vue

@@ -0,0 +1,80 @@
+<script setup lang="ts">
+import router from '@/router'
+import { StaticUrl } from '@/config'
+
+const { statusBarHeight, MenuButtonHeight, opcity } = storeToRefs(useSysStore())
+
+onMounted(() => {
+  opcity.value = 0
+})
+</script>
+
+<template>
+  <view class="attractions-home-page">
+    <wd-navbar
+      title="景区门票"
+      :custom-style="`background-color: rgba(226, 255, 145, ${opcity})`"
+      :bordered="false"
+      :z-index="9999"
+      safe-area-inset-top
+      left-arrow
+      fixed
+      @click-left="router.back()"
+    />
+
+    <view class="relative h-624rpx w-full">
+      <image class="h-full w-full" :src="`${StaticUrl}/attractions-home-bg.png`" />
+      <view :style="{ height: `${(Number(statusBarHeight) || 44) + MenuButtonHeight + 12}px` }" />
+
+      <view class="absolute left-24rpx right-24rpx top-198rpx">
+        <view class="h-60rpx w-full flex items-center justify-between rounded-40rpx bg-white pr-6rpx">
+          <view class="flex items-center pb-14rpx pl-24rpx pt-16rpx">
+            <wd-icon name="search" size="14" color="#ccc" />
+            <view class="ml-12rpx text-24rpx text-gray">
+              霸王茶姬
+            </view>
+          </view>
+          <view
+            class="h-50rpx w-96rpx flex items-center justify-center rounded-26rpx bg-[var(--them-color)] text-24rpx text-white font-semibold"
+          >
+            搜索
+          </view>
+        </view>
+      </view>
+    </view>
+
+    <view class="relative px-24rpx">
+      <view>
+        <view v-for="value in 12" :key="value" class="mb-20rpx flex items-center gap-24rpx rounded-16rpx bg-#FFF p-24rpx" @click="router.push({ name: 'attractions-detail' })">
+          <view class="h-160rpx w-160rpx rounded-16rpx">
+            icon
+          </view>
+          <view class="flex-1">
+            <view class="text-32rpx font-bold">
+              日场门票+观光车+飞越黄果树观影票+吉祥物+冰箱贴
+            </view>
+            <view class="mt-24rpx flex items-center justify-between">
+              <view>
+                <text class="text-26rpx text-#FF4D3A">
+                  ¥
+                </text>
+                <text class="text-36rpx text-#FF4D3A font-bold">
+                  290
+                </text>
+                <text class="text-24rpx text-#AAA">
+                  起
+                </text>
+              </view>
+              <image
+                class="h-48rpx w-116rpx"
+                :src="`${StaticUrl}/attractions-home-btn.png`"
+              />
+            </view>
+          </view>
+        </view>
+      </view>
+    </view>
+  </view>
+</template>
+
+<style lang="scss" scoped></style>

+ 16 - 0
src/subPack-attractions/commonTab/components/orderList.vue

@@ -0,0 +1,16 @@
+<script setup lang="ts">
+const { statusBarHeight, MenuButtonHeight } = storeToRefs(useSysStore())
+</script>
+
+<template>
+  <view class="video-rights-order-page">
+    <wd-navbar
+      title="订单列表" custom-style="background-color:#FFF" :bordered="false" :z-index="99"
+      safe-area-inset-top fixed
+    />
+    <view :style="{ paddingTop: `${(Number(statusBarHeight) || 44) + MenuButtonHeight + 12}px` }" />
+    订单
+  </view>
+</template>
+
+<style lang="scss" scoped></style>

+ 51 - 0
src/subPack-attractions/commonTab/index.vue

@@ -0,0 +1,51 @@
+<script setup lang="ts">
+import homeList from './components/homeList.vue'
+import orderList from './components/orderList.vue'
+import { StaticUrl } from '@/config'
+
+const tabbar = ref(0)
+const { opcity } = storeToRefs(useSysStore())
+
+definePage({
+  name: 'attractions-tabbar',
+  islogin: false,
+  style: {
+    navigationBarTitleText: '',
+    navigationStyle: 'custom',
+  },
+})
+
+// 页面级滚动监听 - 必须在页面组件中才能生效
+onPageScroll((e) => {
+  // 只在首页 tab 时更新透明度
+  if (tabbar.value === 0) {
+    const calculatedOpacity = e.scrollTop / 100
+    opcity.value = Math.min(1, Math.max(0.1, calculatedOpacity))
+  }
+})
+</script>
+
+<template>
+  <view class="">
+    <home-list v-if="tabbar === 0" />
+    <order-list v-if="tabbar === 1" />
+    <view class="">
+      <wd-tabbar v-model="tabbar" safe-area-inset-bottom placeholder fixed :bordered="false" :z-index="99999">
+        <wd-tabbar-item title="列表" icon="goods">
+          <template #icon>
+            <image v-if="tabbar === 0" class="h-40rpx w-40rpx" :src="`${StaticUrl}/attractions-selHome-tabbar.png`" />
+            <image v-else class="h-40rpx w-40rpx" :src="`${StaticUrl}/attractions-home-tabbar.png`" />
+          </template>
+        </wd-tabbar-item>
+        <wd-tabbar-item title="订单记录" icon="list">
+          <template #icon>
+            <image v-if="tabbar === 1" class="h-40rpx w-40rpx" :src="`${StaticUrl}/attractions-selOrder-tabbar.png`" />
+            <image v-else class="h-40rpx w-40rpx" :src="`${StaticUrl}/attractions-order-tabbar.png`" />
+          </template>
+        </wd-tabbar-item>
+      </wd-tabbar>
+    </view>
+  </view>
+</template>
+
+<style lang="scss" scoped></style>

+ 237 - 0
src/subPack-attractions/components/DatePicker.vue

@@ -0,0 +1,237 @@
+<script setup lang="ts">
+/**
+ * 日期选择器组件
+ * @description 支持年月切换、日期选择、价格/库存状态展示
+ */
+
+interface DayItem {
+  day: number
+  status?: string
+  price?: number
+  selected?: boolean
+  disabled?: boolean
+}
+
+interface Props {
+  /** 当前选中的日期 */
+  modelValue?: Date
+  /** 标题 */
+  title?: string
+  /** 日期数据 */
+  dayData?: DayItem[]
+  /** 是否显示星期标题 */
+  showWeekHeader?: boolean
+  /** 自定义类名 */
+  customClass?: string
+}
+
+const props = withDefaults(defineProps<Props>(), {
+  modelValue: () => new Date(),
+  title: '选择日期',
+  dayData: () => [],
+  showWeekHeader: true,
+  customClass: '',
+})
+
+const emit = defineEmits<{
+  /** 选中日期变化 */
+  (e: 'update:modelValue', value: Date): void
+  /** 选中日期 */
+  (e: 'select', value: { date: Date, day: number, item?: DayItem }): void
+  /** 月份变化 */
+  (e: 'monthChange', value: { year: number, month: number }): void
+}>()
+
+// 星期标题
+const weekDays = ['一', '二', '三', '四', '五', '六', '日']
+
+// 当前显示的日期
+const currentDate = ref(new Date(props.modelValue))
+
+// 当前年月显示文本
+const currentMonthText = computed(() => {
+  return `${currentDate.value.getFullYear()}年${currentDate.value.getMonth() + 1}月`
+})
+
+// 当前年月值
+const currentYear = computed(() => currentDate.value.getFullYear())
+const currentMonth = computed(() => currentDate.value.getMonth())
+
+// 计算当前月日历数据
+const calendarDays = computed(() => {
+  const year = currentYear.value
+  const month = currentMonth.value
+
+  // 获取当月第一天是星期几 (0=周日, 1=周一...)
+  const firstDayOfMonth = new Date(year, month, 1).getDay()
+  // 转换为周一开头 (0=周一, 6=周日)
+  const firstDayIndex = firstDayOfMonth === 0 ? 6 : firstDayOfMonth - 1
+
+  // 获取当月天数
+  const daysInMonth = new Date(year, month + 1, 0).getDate()
+
+  // 获取上月天数(用于填充前置空白)
+  const daysInPrevMonth = new Date(year, month, 0).getDate()
+
+  const days: Array<{ day: number, isCurrentMonth: boolean, item?: DayItem, date: Date }> = []
+
+  // 填充上月日期(灰色显示)
+  for (let i = firstDayIndex - 1; i >= 0; i--) {
+    const day = daysInPrevMonth - i
+    days.push({
+      day,
+      isCurrentMonth: false,
+      date: new Date(year, month - 1, day),
+    })
+  }
+
+  // 填充当月日期
+  for (let day = 1; day <= daysInMonth; day++) {
+    // 查找对应的日期数据
+    const dayItem = props.dayData.find(d => d.day === day)
+    const date = new Date(year, month, day)
+
+    // 判断是否选中
+    const isSelected = props.modelValue
+      && date.getFullYear() === props.modelValue.getFullYear()
+      && date.getMonth() === props.modelValue.getMonth()
+      && date.getDate() === props.modelValue.getDate()
+
+    days.push({
+      day,
+      isCurrentMonth: true,
+      item: dayItem ? { ...dayItem, selected: isSelected } : undefined,
+      date,
+    })
+  }
+
+  // 填充下月日期(补全到6行或5行)
+  const remainingCells = 42 - days.length // 6行 x 7列 = 42
+  for (let day = 1; day <= remainingCells; day++) {
+    days.push({
+      day,
+      isCurrentMonth: false,
+      date: new Date(year, month + 1, day),
+    })
+  }
+
+  return days
+})
+
+// 切换到上月
+function prevMonth() {
+  const newDate = new Date(currentDate.value)
+  newDate.setMonth(newDate.getMonth() - 1)
+  currentDate.value = newDate
+  emit('monthChange', { year: newDate.getFullYear(), month: newDate.getMonth() + 1 })
+}
+
+// 切换到下月
+function nextMonth() {
+  const newDate = new Date(currentDate.value)
+  newDate.setMonth(newDate.getMonth() + 1)
+  currentDate.value = newDate
+  emit('monthChange', { year: newDate.getFullYear(), month: newDate.getMonth() + 1 })
+}
+
+// 选择日期
+function selectDay(dayInfo: typeof calendarDays.value[0]) {
+  if (!dayInfo.isCurrentMonth)
+    return
+
+  const newDate = new Date(currentDate.value)
+  newDate.setDate(dayInfo.day)
+
+  emit('update:modelValue', newDate)
+  emit('select', {
+    date: newDate,
+    day: dayInfo.day,
+    item: dayInfo.item,
+  })
+}
+
+// 监听外部modelValue变化
+watch(() => props.modelValue, (newVal) => {
+  if (newVal) {
+    currentDate.value = new Date(newVal)
+  }
+})
+</script>
+
+<template>
+  <view class="rounded-16rpx bg-#F6F6F6 p-20rpx" :class="customClass">
+    <view class="text-32rpx font-semibold">
+      {{ title }}
+    </view>
+
+    <!-- 年月切换 -->
+    <view class="mt20rpx flex items-center justify-between">
+      <view class="h60rpx w60rpx flex items-center justify-center" @click="prevMonth">
+        <wd-icon name="arrow-left" size="20px" color="#999" />
+      </view>
+      <view class="rounded-24rpx bg-#F5F5F5 px32rpx py12rpx text-28rpx">
+        {{ currentMonthText }}
+      </view>
+      <view class="h60rpx w60rpx flex items-center justify-center" @click="nextMonth">
+        <wd-icon name="arrow-right" size="20px" color="#999" />
+      </view>
+    </view>
+
+    <!-- 星期标题 -->
+    <view v-if="showWeekHeader" class="mt20rpx flex items-center justify-between px12rpx">
+      <view
+        v-for="day in weekDays"
+        :key="day"
+        class="h60rpx w60rpx flex items-center justify-center text-28rpx"
+        :class="day === '六' || day === '日' ? 'text-#52c41a' : 'text-#333'"
+      >
+        {{ day }}
+      </view>
+    </view>
+
+    <!-- 日期网格 -->
+    <view class="mt12rpx flex flex-wrap">
+      <view
+        v-for="(item, index) in calendarDays"
+        :key="index"
+        class="mb16rpx h90rpx w-14.28% flex flex-col items-center justify-center"
+        @click="selectDay(item)"
+      >
+        <view
+          class="h100rpx w76rpx flex flex-col items-center justify-center rounded-16rpx"
+          :class="[
+            item.item?.selected ? 'bg-#E2FF91' : '',
+            item.item?.status === '售罄' || item.item?.disabled ? 'bg-#F5F5F5' : '',
+            !item.isCurrentMonth ? 'opacity-30' : '',
+          ]"
+        >
+          <text
+            v-if="item.item?.status && item.isCurrentMonth"
+            class="mt4rpx text-20rpx"
+            :class="item.item.status === '充足' ? 'text-#52c41a' : 'text-#999'"
+          >
+            {{ item.item.status }}
+          </text>
+          <text
+            class="text-28rpx"
+            :class="[
+              item.item?.selected ? 'text-#333 font-semibold' : 'text-#333',
+              item.item?.status === '售罄' || item.item?.disabled ? 'text-#999' : '',
+              !item.isCurrentMonth ? 'text-#999' : '',
+            ]"
+          >
+            {{ item.day }}
+          </text>
+          <text
+            v-if="item.item?.price && item.isCurrentMonth && item.item.status !== '售罄'"
+            class="mt4rpx text-20rpx text-#FF4D3A"
+          >
+            ¥{{ item.item.price }}
+          </text>
+        </view>
+      </view>
+    </view>
+  </view>
+</template>
+
+<style lang="scss" scoped></style>

+ 74 - 0
src/subPack-charge/chargeAddPlate/chargeAddPlate.vue

@@ -0,0 +1,74 @@
+<script setup lang="ts">
+import { ref } from 'vue'
+import addPlate from '@/subPack-charge/components/plate/index.vue'
+import router from '@/router'
+
+const globalToast = useGlobalToast()
+definePage({
+  name: 'charge-add-plate',
+  islogin: false,
+  style: {
+    navigationBarTitleText: '添加车牌',
+  },
+})
+
+const plateNumber = ref<(string | number)[]>(Array.from({ length: 8 }, () => ''))
+
+const { userInfo } = storeToRefs(useUserStore())
+
+const plateRef = ref<InstanceType<typeof addPlate> | null>(null)
+
+function handlePlateChange(value: (string | number)[]) {
+  plateNumber.value = value
+}
+
+// 保存
+async function handleSave() {
+  const result = plateRef.value?.validatePlateNumber()
+  if (!result?.valid) {
+    globalToast.warning(result?.message || '请输入正确的车牌号')
+    return
+  }
+
+  const plateString = plateRef.value?.getPlateString()?.replace(/·/g, '') || ''
+
+  await Apis.charge.addVehicle({
+    data: {
+      licensePlate: plateString,
+      userId: userInfo.value.id!,
+    },
+  })
+  setTimeout(() => {
+    router.back()
+  }, 500)
+}
+</script>
+
+<template>
+  <view class="box-border px24rpx">
+    <view class="h-20rpx" />
+    <view class="rounded-16rpx bg-#FFF p-20rpx shadow-[0_4rpx_20rpx_0_rgba(101,108,106,0.1)]">
+      <view class="mb-24rpx">
+        <text class="text-32rpx font-600">
+          车牌号码
+        </text>
+        <text class="text-#9ED605">
+          *
+        </text>
+      </view>
+      <addPlate
+        ref="plateRef"
+        :plate-number="plateNumber"
+        @my-plate-change="handlePlateChange"
+      />
+      <view
+        class="mt-24rpx h-80rpx w-664rpx rounded-16rpx bg-[linear-gradient(90deg,#DBFC81_0%,#9ED605_100%)] text-center text-28rpx font-800 line-height-[80rpx] shadow-[inset_0rpx_6rpx_20rpx_2rpx_#FFFFFF]"
+        @click="handleSave"
+      >
+        保存
+      </view>
+    </view>
+  </view>
+</template>
+
+<style lang="scss" scoped></style>

+ 1 - 1
src/subPack-charge/chargeBuyaTicketList/chargeBuyaTicketList.vue

@@ -41,7 +41,7 @@ function getStatusText(recordStatus?: number) {
     case 2:
       return '已到账'
     case 3:
-      return '退款'
+      return '退款'
     default:
       return '未知'
   }

+ 1 - 1
src/subPack-charge/chargeOrderDetail/chargeOrderDetail.vue

@@ -169,7 +169,7 @@ async function getOrderDetail() {
             积分扣减
           </view>
           <view class="text-24rpx text-#F44033">
-            {{ chargeOrderDetail?.realCost || '--' }}元
+            {{ chargeOrderDetail ? (chargeOrderDetail.actualTotal || 0) - (chargeOrderDetail.platformVolume || 0) : '--' }}元
           </view>
         </view>
         <view class="mt-28rpx flex items-center justify-between">

+ 106 - 0
src/subPack-charge/chargePlateList/chargePlateList.vue

@@ -0,0 +1,106 @@
+<script setup lang="ts">
+import router from '@/router'
+import { createGlobalLoadingMiddleware } from '@/api/core/middleware'
+
+definePage({
+  name: 'charge-plate-list',
+  islogin: true,
+  style: {
+    navigationBarTitleText: '车牌管理',
+  },
+})
+
+const { data: plateList, refresh } = usePagination(() => Apis.charge.vehicleList({}), {
+  immediate: false,
+  pageNum: 1,
+  pageSize: 10,
+  initialData: [],
+  data: res => res.data,
+  append: true,
+  middleware: createGlobalLoadingMiddleware(),
+})
+
+onShow(() => {
+  refresh()
+})
+
+// 设置默认车辆
+async function handleSetDefault(id: number) {
+  useGlobalMessage().confirm({
+    title: '提示',
+    msg: '确定要设置该车辆为默认车辆吗?',
+    success: async () => {
+      await Apis.charge.setDefault({
+        pathParams: { id: String(id) },
+      })
+      await refresh()
+    },
+  })
+}
+
+// 删除车辆
+function handleDelete(id: number) {
+  useGlobalMessage().confirm({
+    title: '提示',
+    msg: '确定要删除该车辆吗?',
+    success: async () => {
+      await Apis.charge.deleteVehicle({
+        pathParams: { id: String(id) },
+      })
+      await refresh()
+    },
+  })
+}
+</script>
+
+<template>
+  <view class="box-border px24rpx">
+    <view>
+      <view class="h-20rpx" />
+      <view v-for="item in plateList" :key="item.id" class="relative mb-20rpx h-180rpx w-full flex items-center justify-between rounded-16rpx bg-[#9ED605]/30">
+        <view class="ml-20rpx text-32rpx font-bold">
+          {{ item.licensePlate }}
+        </view>
+        <view class="mr-20rpx text-26rpx text-#666666">
+          <view v-if="item.isDefault === 1" class="absolute right-0 top-0 rounded-[0rpx_16rpx_0rpx_16rpx] bg-#9ED605 px-24rpx py-8rpx text-#FFF">
+            默认
+          </view>
+          <view v-else @click="handleSetDefault(item.id!)">
+            设为默认
+          </view>
+          <view class="mt-20rpx" @click="handleDelete(item.id!)">
+            <text>🗑 删除</text>
+          </view>
+        </view>
+      </view>
+      <StatusTip v-if="!plateList.length" tip="暂未绑定车辆" />
+    </view>
+    <view class="mt-100rpx">
+      <view class="text-32rpx font-bold">
+        规则说明
+      </view>
+      <view class="mt-16rpx text-28rpx text-#666666">
+        <view class="mt-10rpx">
+          1.先绑定车牌再开始充电,才能享受充电停车费减免,充电过程中绑定车牌的无法减免停车费;
+        </view>
+        <view class="mt-10rpx">
+          2.绑定车牌后,将按订单充电时长+30分钟离场时间进行减免停车费(例如:充电时长60分钟,系统自动延长30分钟离场时间,即离场时减免90分钟停车费);
+        </view>
+        <view class="mt-10rpx">
+          3.绑定多个车牌时,请在充电开始前,确认充电车辆已设为当前默认充电车辆后再开始充电,否则无法进行减免充电停车费;
+        </view>
+        <view class="mt-10rpx">
+          4.车牌绑定未按正确操作流程或车牌未对应现场充电车辆导致无法减免停车费,因此产生的一切损失与本平台无关。
+        </view>
+      </view>
+    </view>
+    <view
+      class="fixed bottom-66rpx left-24rpx h-100rpx w-702rpx rounded-16rpx bg-[linear-gradient(90deg,#DBFC81_0%,#9ED605_100%)] text-center text-28rpx font-800 line-height-[100rpx] shadow-[inset_0rpx_6rpx_20rpx_2rpx_#FFFFFF]"
+      @click="router.push({ name: 'charge-add-plate' })"
+    >
+      添加车辆
+    </view>
+  </view>
+</template>
+
+<style lang="scss" scoped></style>

+ 392 - 0
src/subPack-charge/components/plate/index.vue

@@ -0,0 +1,392 @@
+<script setup lang="ts">
+import { computed, ref, watch } from 'vue'
+
+// Props 定义
+const props = defineProps({
+  plateNumber: {
+    type: Array as () => (string | number)[],
+    default: () => Array.from({ length: 8 }, () => ''),
+  },
+})
+
+// Emits 定义
+const emit = defineEmits<{
+  myPlateChange: [plateNumber: (string | number)[]]
+}>()
+
+// 响应式数据
+const show = ref(false)
+const index = ref(-1)
+const areaDatas: string[] = [
+  '京',
+  '津',
+  '渝',
+  '沪',
+  '冀',
+  '晋',
+  '辽',
+  '吉',
+  '黑',
+  '苏',
+  '浙',
+  '皖',
+  '闽',
+  '赣',
+  '鲁',
+  '豫',
+  '鄂',
+  '湘',
+  '粤',
+  '琼',
+  '川',
+  '贵',
+  '云',
+  '陕',
+  '甘',
+  '青',
+  '蒙',
+  '桂',
+  '宁',
+  '新',
+  '藏',
+  '使',
+  '领',
+  '',
+  '',
+  '',
+  '',
+  '',
+  '',
+]
+const characterDatas: (number | string)[] = [
+  0,
+  1,
+  2,
+  3,
+  4,
+  5,
+  6,
+  7,
+  8,
+  9,
+  'A',
+  'B',
+  'C',
+  'D',
+  'E',
+  'F',
+  'G',
+  'H',
+  'J',
+  'K',
+  'L',
+  'M',
+  'N',
+  'P',
+  'Q',
+  'R',
+  'S',
+  'T',
+  'U',
+  'V',
+  'W',
+  'X',
+  'Y',
+  'Z',
+  '挂',
+  '警',
+  '学',
+  '港',
+  '澳',
+]
+
+// 内部车牌数组(用于本地操作)
+const localPlateNumber = ref<(string | number)[]>([...props.plateNumber])
+
+// 计算属性
+const currentDatas = computed(() => {
+  return index.value === 0 ? areaDatas : characterDatas
+})
+
+// 方法
+function handleChange(idx: number) {
+  index.value = idx
+  show.value = true
+}
+
+function handleClickKeyBoard(item: string | number, idx: number) {
+  // 索引为1时不能选择数字(0-9)
+  if (index.value === 1 && idx < 10)
+    return
+  // 索引2-5时不能选择特殊字符(索引>33)
+  if (index.value > 1 && index.value < 6 && idx > 33)
+    return
+
+  if (index.value < 8) {
+    localPlateNumber.value[index.value] = item
+    emit('myPlateChange', [...localPlateNumber.value])
+  }
+  if (index.value < 7) {
+    index.value++
+  }
+}
+
+// 重置
+function handleReset() {
+  index.value = 0
+  for (let i = 0; i < 8; i++) {
+    localPlateNumber.value[i] = ''
+  }
+  emit('myPlateChange', [...localPlateNumber.value])
+}
+
+// 删除
+function handleDelete() {
+  localPlateNumber.value[index.value] = ''
+  emit('myPlateChange', [...localPlateNumber.value])
+  if (index.value > 0) {
+    index.value--
+  }
+}
+
+// 获取完整车牌号字符串
+function getPlateString() {
+  let plate = ''
+  localPlateNumber.value.forEach((item, idx) => {
+    if (idx === 1) {
+      plate = `${plate}${item}·`
+    }
+    else {
+      plate += item
+    }
+  })
+  return plate
+}
+
+// 车牌号格式校验
+function validatePlateNumber() {
+  const plateNumber = getPlateString().replace(/·/g, '')
+
+  if (!plateNumber) {
+    return { valid: false, message: '请输入车牌号' }
+  }
+
+  // 长度校验:普通车牌7位,新能源8位
+  if (plateNumber.length < 7) {
+    return { valid: false, message: '车牌号长度不足,请输入完整车牌号' }
+  }
+  if (plateNumber.length > 8) {
+    return { valid: false, message: '车牌号长度超出限制' }
+  }
+
+  // 第一位必须是省份简称
+  const firstChar = plateNumber.charAt(0)
+  if (!areaDatas.includes(firstChar)) {
+    return { valid: false, message: '车牌号首位必须是省份简称' }
+  }
+
+  // 第二位必须是大写字母
+  const secondChar = plateNumber.charAt(1)
+  if (!/^[A-Z]$/.test(secondChar)) {
+    return { valid: false, message: '车牌号第二位必须是字母' }
+  }
+
+  // 普通车牌正则:省份+字母+5位字母数字(含特殊车牌字符)
+  const normalPlateReg = /^[\u4E00-\u9FA5][A-Z][A-Z0-9]{4}[A-Z0-9挂港澳使领警]$/
+  // 新能源车牌正则:省份+字母+6位字母数字
+  const newEnergyPlateReg = /^[\u4E00-\u9FA5][A-Z][A-Z0-9]{6}$/
+
+  if (!normalPlateReg.test(plateNumber) && !newEnergyPlateReg.test(plateNumber)) {
+    return { valid: false, message: '车牌号格式不正确' }
+  }
+
+  return { valid: true, message: '' }
+}
+
+// 暴露方法给父组件
+defineExpose({
+  validatePlateNumber,
+  getPlateString,
+  reset: handleReset,
+})
+
+// 监听 props 变化同步到本地
+watch(() => props.plateNumber, (newVal) => {
+  localPlateNumber.value = [...newVal]
+}, { deep: true })
+</script>
+
+<template>
+  <view>
+    <view class="plate" :class="{ show }">
+      <view class="item" :class="{ active: index === 0 }" @click="handleChange(0)">
+        {{ localPlateNumber[0] }}
+        <text class="triangle" />
+      </view>
+      <view class="item" :class="{ active: index === 1 }" @click="handleChange(1)">
+        {{ localPlateNumber[1] }}
+      </view>
+      <view class="point">
+        ●
+      </view>
+      <view class="item" :class="{ active: index === 2 }" @click="handleChange(2)">
+        {{ localPlateNumber[2] }}
+      </view>
+      <view class="item" :class="{ active: index === 3 }" @click="handleChange(3)">
+        {{ localPlateNumber[3] }}
+      </view>
+      <view class="item" :class="{ active: index === 4 }" @click="handleChange(4)">
+        {{ localPlateNumber[4] }}
+      </view>
+      <view class="item" :class="{ active: index === 5 }" @click="handleChange(5)">
+        {{ localPlateNumber[5] }}
+      </view>
+      <view class="item" :class="{ active: index === 6 }" @click="handleChange(6)">
+        {{ localPlateNumber[6] }}
+      </view>
+      <view class="item new-energy" :class="{ active: index === 7 }" @click="handleChange(7)">
+        <view v-if="localPlateNumber[7] || localPlateNumber[7] === 0">
+          <text>{{ localPlateNumber[7] }}</text>
+        </view>
+        <wd-icon v-else name="add" size="13px" color="#9ED605" />
+      </view>
+    </view>
+    <section class="panel" :class="{ show }">
+      <view class="header">
+        <view @click="handleReset">
+          重置
+        </view>
+        <view @click="show = false">
+          完成
+        </view>
+      </view>
+      <view class="panelList">
+        <view v-for="(item, idx) of currentDatas" :key="idx" class="item">
+          <view
+            v-if="item !== ''"
+            :class="{ disabled: (index === 1 && idx < 10) || (index > 1 && index < 6 && idx > 33) }"
+            @click="handleClickKeyBoard(item, idx)"
+          >
+            {{ item }}
+          </view>
+        </view>
+        <view class="item backspace" :class="{ special: index === 0 }" @click="handleDelete">
+          ×
+        </view>
+      </view>
+    </section>
+  </view>
+</template>
+
+<style scoped lang='less'>
+    .plate {
+        display: flex;
+        justify-content: space-between;
+        .item {
+            width: 64rpx;
+            height: 80rpx;
+            background-color: #F3F4F7;
+            border-radius: 8rpx;
+            text-align: center;
+            line-height: 80rpx;
+            font-size: 32rpx;
+            color: rgba(0,0,0,0.90);
+            font-weight: bold;
+            position: relative;
+            &.active {
+                background-color: #bbbbbb;
+            }
+        }
+        .new-energy {
+            box-sizing: border-box;
+            border: 2rpx dashed #9ED605;
+            font-weight: bold;
+            uni-icons {
+                display: flex;
+                align-items: center;
+                justify-content: center;
+            }
+        }
+        .point {
+            height: 80rpx;
+            text-align: center;
+            line-height: 80rpx;
+            color: #BDC4CC;
+            font-size: 18rpx;
+        }
+        .triangle {
+            width: 0;
+            height: 0;
+            border: 6rpx solid transparent;
+            border-right-color: #00C69D;
+            border-bottom-color: #00C69D;
+            border-radius: 1rpx 2rpx 1rpx;
+            position: absolute;
+            right: 6rpx;
+            bottom: 6rpx;
+        }
+    }
+    .panel {
+        position: fixed;
+        left: 0;
+        width: 100%;
+        bottom: 0;
+        z-index: 999;
+        box-sizing: border-box;
+        background-color: #F5F5F5;
+        transition: all 0.3s ease;
+        transform: translateY(100%);
+        &.show {
+            transform: translateX(0);
+        }
+        .header {
+            display: flex;
+            align-items: center;
+            justify-content: space-between;
+            padding: 0 24rpx;
+            height: 96rpx;
+            color: #9ED605;
+            font-size: 34rpx;
+        }
+        .panelList {
+            padding: 0 19rpx 20rpx;
+            .item {
+                display: inline-block;
+                width: calc(~"(100% - 72rpx) / 10");
+                height: 84rpx;
+                margin-right: 8rpx;
+                margin-bottom: 8rpx;
+                vertical-align: top;
+                view {
+                    width: 100%;
+                    height: 84rpx;
+                    line-height: 84rpx;
+                    border-radius: 6rpx;
+                    background: #FEFFFE;
+                    font-size: 32rpx;
+                    color: rgba(0,0,0,0.90);
+                    font-weight: bold;
+                    text-align: center;
+                    &.disabled {
+                        background-color: rgba(254, 255, 254, 0.6);
+                        color: rgba(0, 0, 0, 0.23);
+                    }
+                }
+                &:nth-of-type(10n) {
+                    margin-right: 0;
+                }
+            }
+            .backspace {
+                vertical-align: top;
+                font-size: 48rpx;
+                font-weight: bold;
+                text-align: center;
+                height: 84rpx;
+                line-height: 84rpx;
+                border-radius: 6rpx;
+                background: #FEFFFE;
+                color: rgba(0,0,0,0.90);
+            }
+        }
+    }
+</style>

+ 29 - 3
src/subPack-charge/index/index.vue

@@ -49,6 +49,7 @@ onMounted(() => {
 onShow(() => {
   refresh()
   getUserAccountInfo()
+  getDefaultVehicle()
 })
 onPageScroll((e) => {
   const calculatedOpacity = e.scrollTop / 100
@@ -68,6 +69,15 @@ async function getUserAccountInfo() {
   const res = await Apis.charge.getMemberInfo({})
   userAccountInfo.value = res.data
 }
+
+/**
+ * 获取用户默认车辆
+ */
+const defaultVehicle = ref<Api.UserVehicleVO>()
+async function getDefaultVehicle() {
+  const res = await Apis.charge.default({})
+  defaultVehicle.value = res.data
+}
 // 处理筛选项点击的方法
 function handleFilterClick(filterKey: number) {
   activeFilter.value = filterKey
@@ -228,9 +238,25 @@ function refund() {
           </view>
         </view>
       </view>
-      <!-- <view class="mt-24rpx">
-        <wd-swiper :list="swiperList" :height="100" :indicator="false" value-key="advertImg" />
-      </view> -->
+      <view class="mt-24rpx flex items-center justify-between rounded-16rpx bg-#FFF p-20rpx">
+        <view class="text-32rpx font-bold">
+          我的车辆
+        </view>
+        <!-- 无车辆时显示添加提示 -->
+        <view v-if="!defaultVehicle" class="flex items-center gap-10rpx" @click="router.push({ name: 'charge-plate-list' })">
+          <view class="text-26rpx">
+            添加车辆,享更多权益
+          </view>
+          <view class="h-50rpx w-50rpx rounded-50% bg-[linear-gradient(90deg,#DBFC81_0%,#9ED605_100%)] text-center text-30rpx text-#FFF font-bold line-height-[50rpx]">
+            +
+          </view>
+        </view>
+        <!-- 有车辆时显示车牌和管理入口 -->
+        <view v-else class="flex items-center gap-50rpx text-26rpx text-#9ED605" @click="router.push({ name: 'charge-plate-list' })">
+          <view>{{ defaultVehicle.licensePlate }}</view>
+          <view>管理>></view>
+        </view>
+      </view>
       <view class="mt-24rpx flex items-center gap-20rpx">
         <view
           v-for="option in filterOptions"

+ 1 - 1
src/subPack-videoRights/commonTab/components/home.vue

@@ -56,7 +56,7 @@ function clearSearch() {
     <view class="box-border px24rpx">
       <scroll-view scroll-y type="custom">
         <grid-view type="masonry" cross-axis-count="2" main-axis-gap="10" cross-axis-gap="10">
-          <view v-for="item in videoDataList" :key="item?.id" class="mt-18rpx rounded-16rpx bg-#FFF" @click="router.push({ name: 'video-rights-detail', params: { id: item.id } })">
+          <view v-for="item in videoDataList" :key="item?.id" class="mt-18rpx rounded-16rpx bg-#FFF" @click="router.push({ name: 'video-rights-detail', params: { id: item.id || '' } })">
             <view class="image-wrapper h-342rpx w-342rpx">
               <image
                 class="h-full w-full rounded-16rpx object-c"

+ 1 - 1
src/subPack-videoRights/commonTab/components/order.vue

@@ -55,7 +55,7 @@ function handleClick(e: any) {
         <wd-tab :title="item">
           <view class="box-border bg-#f6f6f6 px24rpx">
             <view class="h-4rpx" />
-            <view v-for=" order in orderList" :key="order.orderNumber" class="mt-28rpx rounded-16rpx bg-#FFF p-24rpx" @click="router.push({ name: 'video-rights-order-info', params: { orderNo: order.orderNumber } })">
+            <view v-for=" order in orderList" :key="order.orderNumber" class="mt-28rpx rounded-16rpx bg-#FFF p-24rpx" @click="router.push({ name: 'video-rights-order-info', params: { orderNo: order.orderNumber || '' } })">
               <view class="flex items-center justify-between">
                 <view class="text-28rpx">
                   {{ order.createTime }}

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

@@ -36,12 +36,14 @@ interface NavigateToOptions {
        "/subPack-film/order-detail/index" |
        "/subPack-film/select-time/index" |
        "/subPack-film/submit-order/index" |
+       "/subPack-charge/chargeAddPlate/chargeAddPlate" |
        "/subPack-charge/chargeBuyaTicketList/chargeBuyaTicketList" |
        "/subPack-charge/chargeDetail/chargeDetail" |
        "/subPack-charge/chargeing/chargeing" |
        "/subPack-charge/chargeMap/chargeMap" |
        "/subPack-charge/chargeOrderDetail/chargeOrderDetail" |
        "/subPack-charge/chargeOrderList/chargeOrderList" |
+       "/subPack-charge/chargePlateList/chargePlateList" |
        "/subPack-charge/chargeSearchList/chargeSearchList" |
        "/subPack-charge/chargeSiteDetail/chargeSiteDetail" |
        "/subPack-charge/chargeStart/chargeStart" |
@@ -60,7 +62,10 @@ interface NavigateToOptions {
        "/subPack-djk/welfare/index" |
        "/subPack-refueling/commonTab/index" |
        "/subPack-refueling/orderDetaile/index" |
-       "/subPack-refueling/webView/index";
+       "/subPack-refueling/webView/index" |
+       "/subPack-attractions/attractionsDetail/attractionsDetail" |
+       "/subPack-attractions/attractionsReservation/attractionsReservation" |
+       "/subPack-attractions/commonTab/index";
 }
 interface RedirectToOptions extends NavigateToOptions {}
 

+ 1 - 0
vite.config.ts

@@ -34,6 +34,7 @@ export default async () => {
           'subPack-videoRights',
           'subPack-djk',
           'subPack-refueling',
+          'subPack-attractions',
         ],
         /**
          * 排除的页面,相对于 dir 和 subPackages