ソースを参照

```
feat(attractions): 添加景区产品详情价格查询和订单功能

- 新增 ProductPriceResponseVo、LinePriceVo、PriceDetailVo 等价格相关接口类型定义
- 新增 CreateOrderRequest、PeopleItem、ScenicPayPreviewVo 等订单相关接口类型定义
- 新增 ScenicOrderListVo 景区订单列表项接口类型定义
- 添加 attractions.detail、attractions.price、attractions.createOrder、
attractions.payPreview、attractions.orderList 等API接口定义
- 在全局类型声明中添加对应的API方法签名
- 更新景区详情页面实现动态获取产品详情和价格信息
- 实现日历组件动态加载指定月份的价格数据
- 添加订单支付页面实现支付预览和支付功能
- 修改景区详情页需要登录访问
- 修复图片样式问题并优化页面布局
```

zouzexu 15 時間 前
コミット
471432d9eb

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

@@ -3242,5 +3242,427 @@ namespace Api {
     returnMoneyCodex?: string
     [property: string]: any
   }
+  /**
+   * 返回数据对象
+   *
+   * ProductPriceResponseVo
+   */
+  export interface ProductPriceResponseVo {
+    /**
+     * 产品ID
+     */
+    infoId?: number
+    /**
+     * 是否是多规格 0否 1是
+     */
+    isSpec?: number
+    /**
+     * 线路产品价格
+     */
+    linePrices?: LinePriceVo[]
+    /**
+     * 门票多规格价格
+     */
+    muskuPrices?: MuskuPriceVo[]
+    /**
+     * 抢购产品价格
+     */
+    snapupPrices?: SnapupPriceVo[]
+    /**
+     * 多规格商品价格
+     */
+    specProdPrices?: SpecProdPriceVo[]
+    /**
+     * 多规格信息
+     */
+    specs?: SpecVo[]
+    /**
+     * 门票产品价格
+     */
+    ticketPrices?: TicketPriceVo[]
+    [property: string]: any
+  }
+
+  /**
+   * com.smqjh.pms.model.vo.scenery.ProductPriceResponseVo.LinePriceVo
+   *
+   * LinePriceVo
+   */
+  export interface LinePriceVo {
+    /**
+     * 日期
+     */
+    date?: string
+    /**
+     * 最小团队人数
+     */
+    minTeamNum?: number
+    /**
+     * 价格明细列表
+     */
+    priceDetails?: PriceDetailVo[]
+    /**
+     * 剩余数量
+     */
+    remainNum?: number
+    /**
+     * 单房差价
+     */
+    singleRoomPrice?: number
+    /**
+     * 提前预订天数
+     */
+    startDay?: number
+    /**
+     * 起订数量
+     */
+    startNum?: number
+    /**
+     * 总数量
+     */
+    totalNum?: number
+    [property: string]: any
+  }
+
+  /**
+   * com.smqjh.pms.model.vo.scenery.ProductPriceResponseVo.PriceDetailVo
+   *
+   * PriceDetailVo
+   */
+  export interface PriceDetailVo {
+    /**
+     * 市场价
+     */
+    marketPrice?: number
+    /**
+     * 价格ID
+     */
+    priceId?: number
+    /**
+     * 价格名称
+     */
+    priceName?: string
+    /**
+     * 销售价
+     */
+    salePrice?: number
+    /**
+     * 结算价
+     */
+    settlementPrice?: number
+    [property: string]: any
+  }
 
+  /**
+   * com.smqjh.pms.model.vo.scenery.ProductPriceResponseVo.MuskuPriceVo
+   *
+   * MuskuPriceVo
+   */
+  export interface MuskuPriceVo {
+    /**
+     * 日期
+     */
+    date?: string
+    /**
+     * 剩余数量
+     */
+    num?: number
+    /**
+     * 价格明细列表
+     */
+    priceDetails?: PriceDetailVo[]
+    [property: string]: any
+  }
+
+  /**
+   * com.smqjh.pms.model.vo.scenery.ProductPriceResponseVo.SnapupPriceVo
+   *
+   * SnapupPriceVo
+   */
+  export interface SnapupPriceVo {
+    /**
+     * 总数量
+     */
+    allNum?: number
+    /**
+     * 市场价
+     */
+    marketPrice?: number
+    /**
+     * 剩余数量
+     */
+    num?: number
+    /**
+     * 销售价
+     */
+    salePrice?: number
+    /**
+     * 结算价
+     */
+    settlementPrice?: number
+    /**
+     * 规格ID
+     */
+    specId?: string
+    /**
+     * 规格名称
+     */
+    specName?: string
+    [property: string]: any
+  }
+
+  /**
+   * com.smqjh.pms.model.vo.scenery.ProductPriceResponseVo.SpecProdPriceVo
+   *
+   * SpecProdPriceVo
+   */
+  export interface SpecProdPriceVo {
+    /**
+     * 市场价
+     */
+    marketPrice?: number
+    /**
+     * 剩余数量
+     */
+    num?: number
+    /**
+     * 销售价
+     */
+    salePrice?: number
+    /**
+     * 结算价
+     */
+    settlementPrice?: number
+    /**
+     * 规格ID
+     */
+    specId?: string
+    /**
+     * 规格名称
+     */
+    specName?: string
+    [property: string]: any
+  }
+
+  /**
+   * com.smqjh.pms.model.vo.scenery.ProductDetailResponseVo.SpecVo
+   *
+   * SpecVo
+   */
+  export interface SpecVo {
+    /**
+     * 规格名称
+     */
+    specName?: string
+    /**
+     * 规格值列表
+     */
+    specValues?: { [key: string]: any }[]
+    [property: string]: any
+  }
+
+  /**
+   * com.smqjh.pms.model.vo.scenery.ProductPriceResponseVo.TicketPriceVo
+   *
+   * TicketPriceVo
+   */
+  export interface TicketPriceVo {
+    /**
+     * 日期
+     */
+    date?: string
+    /**
+     * 市场价
+     */
+    marketPrice?: number
+    /**
+     * 剩余数量
+     */
+    num?: number
+    /**
+     * 销售价
+     */
+    salePrice?: number
+    /**
+     * 时段列表
+     */
+    seats?: SeatVo[]
+    /**
+     * 结算价
+     */
+    settlementPrice?: number
+    /**
+     * 票余量状态:充足/紧张/售罄
+     */
+    ticketAvailability?: string
+    [property: string]: any
+  }
+
+  export interface CreateOrderRequest {
+    /**
+     * 结束游玩日期,格式yyyy-MM-dd
+     */
+    endTravelDate?: string
+    /**
+     * 联系人地址
+     */
+    linkAddress?: string
+    /**
+     * 联系人证件号
+     */
+    linkCreditNo?: string
+    /**
+     * 联系人证件类型
+     */
+    linkCreditType?: number
+    /**
+     * 联系人邮件
+     */
+    linkEmail?: string
+    /**
+     * 联系人姓名
+     */
+    linkMan?: string
+    /**
+     * 联系人手机号
+     */
+    linkPhone?: string
+    /**
+     * 预定数量
+     */
+    num: number
+    /**
+     * 订单备注
+     */
+    orderMemo?: string
+    /**
+     * 游玩人信息列表
+     */
+    peoples?: PeopleItem[]
+    /**
+     * 产品名称
+     */
+    productName?: string
+    /**
+     * 产品ID(pms_scenic_product.product_no)
+     */
+    productNo: number
+    /**
+     * 游玩日期,格式yyyy-MM-dd
+     */
+    travelDate: string
+    [property: string]: any
+  }
+  export interface PeopleItem {
+    /**
+     * 是否成人
+     */
+    adult?: boolean
+    /**
+     * 年龄,儿童必传
+     */
+    age?: number
+    /**
+     * 游玩人快递地址
+     */
+    linkAddress?: string
+    /**
+     * 游玩人证件号
+     */
+    linkCreditNo?: string
+    /**
+     * 游玩人证件类型
+     */
+    linkCreditType?: number
+    /**
+     * @Schema(description = "游玩人名")
+     * 游玩人姓名
+     */
+    linkMan?: string
+    /**
+     * 游玩人手机号
+     */
+    linkPhone?: string
+    /**
+     * 性别 1未知 2男 3女
+     */
+    linkSex?: number
+    [property: string]: any
+  }
+  export interface ScenicPayPreviewVo {
+    /**
+     * 用户可用积分
+     */
+    availablePoints?: number
+    /**
+     * 系统订单号
+     */
+    orderNumber?: string
+    /**
+     * 订单总金额(actualTotal)
+     */
+    orderTotal?: number
+    /**
+     * 积分可抵扣金额
+     */
+    pointsDeduct?: number
+    /**
+     * 微信支付金额
+     */
+    wxPayMoney?: number
+    [property: string]: any
+  }
+  export interface DataScenicOrderListVo {
+    list?: ScenicOrderListVo[]
+    total?: number
+    [property: string]: any
+  }
+
+  /**
+   * 景区订单列表项VO
+   *
+   * ScenicOrderListVo
+   */
+  export interface ScenicOrderListVo {
+    /**
+     * 用户实际支付金额
+     */
+    actualTotal?: number
+    /**
+     * 下单时间
+     */
+    createTime?: string
+    /**
+     * 结束游玩日期
+     */
+    endTravelDate?: string
+    /**
+     * 景点图片
+     */
+    img?: string
+    /**
+     * 系统订单号
+     */
+    orderNumber?: string
+    /**
+     * 产品名称
+     */
+    productName?: string
+    /**
+     * 游玩日期
+     */
+    travelDate?: string
+    /**
+     * 景点名称
+     */
+    viewName?: string
+    /**
+     * 订单状态0-待支付
+     * 60-订单已取消
+     * 70-已支付
+     * 80-订单已完成
+     */
+    hbOrderStatus?: number
+    [property: string]: any
+  }
 }

+ 6 - 1
src/api/apiDefinitions.ts

@@ -132,5 +132,10 @@ export default {
 
   'refueling.getPayCode': ['POST', '/smqjh-pms/api/v1/refueling/generateQrCode'],
 
-  'attractions.findAppByPage':['GET','/smqjh-pms/app-api/v1/sceneryProduct/findAppByPage']
+  'attractions.findAppByPage':['GET','/smqjh-pms/app-api/v1/sceneryProduct/findAppByPage'],
+  'attractions.detail':['GET','/smqjh-pms/app-api/v1/sceneryProduct/detail'],
+  'attractions.price':['POST','/smqjh-pms/app-api/v1/sceneryProduct/price'],
+  'attractions.createOrder':['POST','/smqjh-oms/app-api/v1/order/scenic/add'],
+  'attractions.payPreview':['GET','/smqjh-oms/app-api/v1/order/scenic/pay-preview'],
+  'attractions.orderList':['GET','/smqjh-oms/app-api/v1/order/scenic/list'],
 };

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

@@ -1313,6 +1313,76 @@ declare global {
       >(
         config: Config
       ): Alova2Method<apiResData<Api.DataAppProductDetailVo>, 'attractions.findAppByPage', Config>;
+
+      detail<
+        Config extends Alova2MethodConfig<apiResData<Api.AppProductDetailVo>> & {
+          data: {
+            /**
+            * 产品id
+            */
+            productNo?: number;
+          }
+        }
+      >(
+        config: Config
+      ): Alova2Method<apiResData<Api.AppProductDetailVo>, 'attractions.detail', Config>;
+
+      price<
+        Config extends Alova2MethodConfig<apiResData<Api.ProductPriceResponseVo>> & {
+          data: {
+            /**
+             * 产品ID
+             */
+            productNo?: number;
+            /**
+             * 开始日期
+             */
+            travelDate?: string;
+            /**
+             * 结束日期,不传默认只查本月的价格
+             */
+            endTravelDate?: string;
+          }
+        }
+      >(
+        config: Config
+      ): Alova2Method<apiResData<Api.ProductPriceResponseVo>, 'attractions.price', Config>;
+
+      createOrder<
+        Config extends Alova2MethodConfig<apiResData<any>> & {
+          data: Api.CreateOrderRequest;
+        }
+      >(
+        config: Config
+      ): Alova2Method<apiResData<any>, 'attractions.createOrder', Config>;
+
+      payPreview<
+        Config extends Alova2MethodConfig<apiResData<Api.ScenicPayPreviewVo>> & {
+          data: {
+            /**
+            * 订单号
+            */
+            orderNumber: string;
+          }
+        }
+      >(
+        config: Config
+      ): Alova2Method<apiResData<Api.ScenicPayPreviewVo>, 'attractions.payPreview', Config>;
+
+      orderList<
+        Config extends Alova2MethodConfig<apiResData<Api.DataScenicOrderListVo>> & {
+          data: {
+            pageNum: number;
+            pageSize: number;
+            /**
+             * 订单状态:0-全部 1-待支付 2-已支付 3-已取消 4-已完成
+             */
+            status: number;
+          }
+        }
+      >(
+        config: Config
+      ): Alova2Method<apiResData<Api.DataScenicOrderListVo>, 'attractions.orderList', Config>;
     }
   }
 }

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

@@ -47,6 +47,7 @@ declare global {
   const eagerComputed: typeof import('@vueuse/core')['eagerComputed']
   const effectScope: typeof import('vue')['effectScope']
   const extendRef: typeof import('@vueuse/core')['extendRef']
+  const fixImgStyle: typeof import('./utils/index')['fixImgStyle']
   const getActivePinia: typeof import('pinia')['getActivePinia']
   const getCurrentInstance: typeof import('vue')['getCurrentInstance']
   const getCurrentPath: typeof import('./utils/index')['getCurrentPath']
@@ -412,6 +413,7 @@ declare module 'vue' {
     readonly eagerComputed: UnwrapRef<typeof import('@vueuse/core')['eagerComputed']>
     readonly effectScope: UnwrapRef<typeof import('vue')['effectScope']>
     readonly extendRef: UnwrapRef<typeof import('@vueuse/core')['extendRef']>
+    readonly fixImgStyle: UnwrapRef<typeof import('./utils/index')['fixImgStyle']>
     readonly getActivePinia: UnwrapRef<typeof import('pinia')['getActivePinia']>
     readonly getCurrentInstance: UnwrapRef<typeof import('vue')['getCurrentInstance']>
     readonly getCurrentPath: UnwrapRef<typeof import('./utils/index')['getCurrentPath']>

+ 1 - 1
src/pages.json

@@ -655,7 +655,7 @@
         {
           "path": "attractionsDetail/attractionsDetail",
           "name": "attractions-detail",
-          "islogin": false,
+          "islogin": true,
           "style": {
             "navigationBarTitleText": "",
             "navigationStyle": "custom"

+ 101 - 62
src/subPack-attractions/attractionsDetail/attractionsDetail.vue

@@ -2,61 +2,40 @@
 import DatePicker from '../components/DatePicker.vue'
 import { StaticUrl } from '@/config'
 import router from '@/router'
+import { fixImgStyle } from '@/utils/index'
 
 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 selectedDate = ref()
+
+const calendarDataMap = ref<Record<string, Array<{ day: number, status?: string, price?: number, selected?: boolean }>>>({})
 
-// 当前显示的月份
-const currentYearMonth = ref({ year: 2026, month: 3 })
+const currentYearMonth = ref({ year: new Date().getFullYear(), month: new Date().getMonth() + 1 })
 
-// 获取当前月份的日期数据
 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,
+  islogin: true,
   style: {
     navigationBarTitleText: '',
     navigationStyle: 'custom',
   },
 })
 
+const productNo = ref('')
+onLoad((options: any) => {
+  productNo.value = options.productNo || ''
+})
+
 onMounted(() => {
   opcity.value = 0
+  getDetail()
 })
 
 onPageScroll((e) => {
@@ -64,39 +43,82 @@ onPageScroll((e) => {
   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,
+const attractionDetail = ref<Api.AppProductDetailVo | null>(null)
+
+const swiperList = computed(() => {
+  const imgs = attractionDetail.value?.imgs
+  return imgs ? imgs.split(',').filter(Boolean) : []
+})
+
+async function getDetail() {
+  const res = await Apis.attractions.detail({ data: { productNo: Number(productNo.value) } })
+  attractionDetail.value = res.data
+  await getPrice()
+}
+
+async function getPrice(year?: number, month?: number) {
+  const targetYear = year ?? currentYearMonth.value.year
+  const targetMonth = month ?? currentYearMonth.value.month
+  const startDate = `${targetYear}-${String(targetMonth).padStart(2, '0')}-01`
+  const lastDay = new Date(targetYear, targetMonth, 0).getDate()
+  const endDate = `${targetYear}-${String(targetMonth).padStart(2, '0')}-${lastDay}`
+  useGlobalLoading().loading({
+    msg: '加载中...',
+  })
+  const res = await Apis.attractions.price({
+    data: {
+      productNo: Number(productNo.value),
+      travelDate: startDate,
+      endTravelDate: endDate,
+    },
   })
+  useGlobalLoading().close()
+  const ticketPrices = res.data?.ticketPrices || []
+  const monthKey = `${targetYear}-${String(targetMonth).padStart(2, '0')}`
+  calendarDataMap.value[monthKey] = ticketPrices.map((item: Api.TicketPriceVo) => ({
+    day: item.date ? new Date(item.date).getDate() : 0,
+    status: item.ticketAvailability,
+    price: item.salePrice,
+  })).filter((item: { day: number }) => item.day > 0)
 }
 
-function handleService() {
-  uni.makePhoneCall({
-    phoneNumber: '400-123-4567',
+function handleNav() {
+  uni.openLocation({
+    latitude: Number(attractionDetail.value?.viewLatitude) || 0,
+    longitude: Number(attractionDetail.value?.viewLongitude) || 0,
+    name: attractionDetail.value?.viewName,
+    address: attractionDetail.value?.viewAddress,
   })
 }
 
 function handleOrder() {
-  router.push({ name: 'attractions-order' })
+  router.push({ name: 'attractions-tabbar', params: { tabbar: '1' } })
 }
 
 function handleBook() {
-  router.push({ name: 'attractions-reservation-info' })
+  router.push({
+    name: 'attractions-reservation-info',
+    params: {
+      productNo: productNo.value,
+      selectDate: selectedDate.value || new Date(),
+      productName: attractionDetail.value?.productName || '',
+      isSingle: String(attractionDetail.value?.isSingle),
+      price: String(attractionDetail.value?.salePrice),
+    },
+  })
 }
 
-// 处理日期选择
 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 }) {
+async function handleMonthChange(e: { year: number, month: number }) {
   currentYearMonth.value = { year: e.year, month: e.month }
-  console.log('切换月份:', e.year, '年', e.month, '月')
+  const monthKey = `${e.year}-${String(e.month).padStart(2, '0')}`
+  if (!calendarDataMap.value[monthKey]) {
+    await getPrice(e.year, e.month)
+  }
 }
 </script>
 
@@ -114,6 +136,7 @@ function handleMonthChange(e: { year: number, month: number }) {
     />
     <!-- 轮播图 -->
     <wd-swiper
+      v-if="swiperList.length"
       v-model:current="currentSwiper"
       :list="swiperList"
       :indicator="false"
@@ -128,14 +151,14 @@ function handleMonthChange(e: { year: number, month: number }) {
           ¥
         </text>
         <text class="mx4rpx text-48rpx font-bold">
-          {{ attractionInfo.price }}
+          {{ attractionDetail?.salePrice }}
         </text>
         <text class="text-24rpx text-gray">
         </text>
       </view>
       <view class="mt16rpx text-32rpx font-semibold line-height-[1.4]">
-        {{ attractionInfo.title }}
+        {{ attractionDetail?.productName }}
       </view>
 
       <!-- 地址导航卡片 -->
@@ -145,10 +168,10 @@ function handleMonthChange(e: { year: number, month: number }) {
       >
         <view class="w450rpx">
           <view class="line-clamp-1 text-32rpx font-semibold">
-            {{ attractionInfo.name }}
+            {{ attractionDetail?.viewName }}
           </view>
           <view class="line-clamp-1 mt12rpx flex items-center text-24rpx text-gray">
-            {{ attractionInfo.address }}
+            {{ attractionDetail?.viewAddress }}
           </view>
         </view>
         <view class="flex flex-col items-center justify-center" @click="handleNav">
@@ -179,19 +202,24 @@ function handleMonthChange(e: { year: number, month: number }) {
         <view class="text-32rpx font-semibold">
           预定须知
         </view>
-        <view class="mt16rpx text-28rpx font-semibold">
+        <!-- <view class="mt16rpx text-28rpx font-semibold">
           退款说明
-        </view>
+        </view> -->
         <view class="mt12rpx text-26rpx text-gray line-height-[1.6]">
-          <view>使用日期当天23:59(含)之前申请取消,不收取损失费</view>
+          <view>
+            <rich-text :nodes="fixImgStyle(attractionDetail?.content || '')" />
+          </view>
+          <view class="mt8rpx">
+            <rich-text :nodes="fixImgStyle(attractionDetail?.orderDesc || '')" />
+          </view>
           <view class="mt8rpx">
-            使用日期当天23:59之后申请取消,收取100%损失费;激活后不可退
+            {{ attractionDetail?.chargeInclude }}
           </view>
           <view class="mt8rpx">
-            如使用优惠,则损失费用按照优惠前金额的比例收取,最高不超过实付金额
+            {{ attractionDetail?.refundNote }}
           </view>
           <view class="mt8rpx">
-            产品不支持部分退
+            {{ attractionDetail?.userNote }}
           </view>
         </view>
       </view>
@@ -206,9 +234,10 @@ function handleMonthChange(e: { year: number, month: number }) {
       :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">
+        <view class="relative mr-40rpx flex flex-col items-center">
+          <button open-type="contact" class="zbutton" />
+          <wd-icon name="service" size="24px" color="#666" />
+          <text class="text-22rpx text-#666">
             客服
           </text>
         </view>
@@ -229,4 +258,14 @@ function handleMonthChange(e: { year: number, month: number }) {
   </view>
 </template>
 
-<style lang="scss" scoped></style>
+<style lang="scss" scoped>
+.zbutton{
+  all: unset;
+  width: 70rpx;
+  height: 70rpx;
+  position: absolute;
+  &::after{
+    border: none;
+  }
+}
+</style>

+ 43 - 8
src/subPack-attractions/attractionsOrderPay/attractionsOrderPay.vue

@@ -8,6 +8,41 @@ definePage({
     navigationBarTitleText: '订单支付',
   },
 })
+const productName = ref('')
+const num = ref(0)
+const orderNo = ref('')
+onLoad((options: any) => {
+  productName.value = options.productName || ''
+  num.value = Number(options.num) || 0
+  orderNo.value = options.orderNo || ''
+})
+
+onMounted(() => {
+  getPayPreview()
+})
+
+const payPreviewInfo = ref<Api.ScenicPayPreviewVo>()
+const payMethod = ref(['1']) // 默认选中微信支付
+async function getPayPreview() {
+  const res = await Apis.attractions.payPreview({ data: { orderNumber: orderNo.value } })
+  payPreviewInfo.value = res.data
+}
+
+async function submitPay() {
+  const res = await useUserStore().handleCommonPayMent(orderNo.value)
+  if (res.payType !== 1) {
+    try {
+      await useUserStore().getWxCommonPayment(res)
+      router.push({ name: 'attractions-order-detail' })
+    }
+    catch {
+      await useUserStore().payError('xsb-order', 'subPack-xsb/commonTab/index')
+    }
+  }
+  else {
+    router.push({ name: 'attractions-order-detail' })
+  }
+}
 </script>
 
 <template>
@@ -16,9 +51,9 @@ definePage({
       <view class="h-20rpx" />
       <view class="rounded-16rpx bg-#FFF p-24rpx">
         <view class="flex items-center justify-between gap-50rpx text-32rpx font-bold">
-          <view>日场门票+观光车+飞越黄果树观影票+吉祥物+冰箱贴 经典必打卡</view>
+          <view>{{ productName }}</view>
           <view class="text-28rpx font-normal">
-            x1
+            x{{ num }}
           </view>
         </view>
         <view class="mt-24rpx h-2rpx w-full bg-#F0F0F0" />
@@ -30,30 +65,30 @@ definePage({
             <text class="text-26rpx">
             </text>
-            290
+            {{ payPreviewInfo?.orderTotal }}
           </view>
         </view>
         <view class="mt-20rpx flex items-center justify-between text-24rpx">
           <view>积分扣减</view>
           <view class="text-#FF4A39 font-bold">
-            ¥10
+            ¥{{ payPreviewInfo?.pointsDeduct }}
           </view>
         </view>
         <view class="mt-20rpx flex items-center justify-between text-24rpx">
           <view>微信支付</view>
           <view class="text-#FF4A39 font-bold">
-            ¥280
+            ¥{{ payPreviewInfo?.wxPayMoney }}
           </view>
         </view>
       </view>
-      <view class="mt-20rpx rounded-16rpx bg-#FFF p-24rpx">
+      <view v-if="(payPreviewInfo?.wxPayMoney ?? 0) > 0" class="mt-20rpx rounded-16rpx bg-#FFF p-24rpx">
         <view class="text-32rpx font-bold">
           选择支付方式
         </view>
         <view class="mt-24rpx h-2rpx w-full bg-#F0F0F0" />
         <view>
           <wd-cell-group border>
-            <wd-checkbox-group size="large">
+            <wd-checkbox-group v-model="payMethod" size="large">
               <wd-cell title="微信支付" center clickable>
                 <view>
                   <wd-checkbox model-value="1" custom-style="margin:0;" />
@@ -65,7 +100,7 @@ definePage({
       </view>
     </view>
     <view class="fixed bottom-0 h-174rpx w-full border-[1rpx_solid_#EEEEEE] bg-#FFF px-24rpx">
-      <wd-button custom-class="w-702rpx mt-10rpx" block size="large" @click="router.push({ name: 'attractions-order-detail' })">
+      <wd-button custom-class="w-702rpx mt-10rpx" block size="large" @click="submitPay">
         立即支付
       </wd-button>
     </view>

+ 198 - 32
src/subPack-attractions/attractionsReservation/attractionsReservation.vue

@@ -1,4 +1,5 @@
 <script setup lang="ts">
+import { columns } from './reservation-data'
 import router from '@/router'
 
 definePage({
@@ -8,12 +9,176 @@ definePage({
     navigationBarTitleText: '预定信息',
   },
 })
-const columns = ref(['选项1', '选项2', '选项3', '选项4', '选项5', '选项6', '选项7'])
-const value = ref('选项1')
+
+const productNo = ref(0)
+const productName = ref('')
+const selectDate = ref('')
+const isSingle = ref()
+const price = ref(0)
+onLoad((options: any) => {
+  productNo.value = Number(options.productNo) || 0
+  productName.value = options.productName || ''
+  selectDate.value = options.selectDate || ''
+  price.value = Number(options.price)
+  isSingle.value = Number(options.isSingle)
+})
+
+// 数量选择
+const quantity = ref(1)
+function handleMinus() {
+  if (quantity.value > 1) {
+    quantity.value--
+  }
+}
+function handlePlus() {
+  quantity.value++
+}
+
+// 合计价格
+const totalPrice = computed(() => price.value * quantity.value)
+
+// 联系人信息
+const linkMan = ref('')
+const linkCreditType = ref(0)
+const linkCreditNo = ref('')
+const linkPhone = ref('')
+
+// 多游客信息列表
+interface PeopleInfo {
+  linkMan: string
+  linkCreditType: number
+  linkCreditNo: string
+}
+const peopleList = ref<PeopleInfo[]>([{ linkMan: '', linkCreditType: 0, linkCreditNo: '' }])
+
+// 监听数量变化,同步游客信息列表
+watch(quantity, (newVal) => {
+  if (isSingle.value === 1) {
+    const diff = newVal - peopleList.value.length
+    if (diff > 0) {
+      for (let i = 0; i < diff; i++) {
+        peopleList.value.push({ linkMan: '', linkCreditType: 0, linkCreditNo: '' })
+      }
+    }
+    else if (diff < 0) {
+      peopleList.value.splice(newVal)
+    }
+  }
+})
+
 const orderPopup = ref(false)
+const orderMemo = ref('')
+
+const formattedDate = computed(() => {
+  if (!selectDate.value)
+    return ''
+  const date = new Date(selectDate.value)
+  const year = date.getFullYear()
+  const month = String(date.getMonth() + 1).padStart(2, '0')
+  const day = String(date.getDate()).padStart(2, '0')
+  return `${year}年${month}月${day}日`
+})
+
+// 接口需要的日期格式 yyyy-MM-dd
+const travelDate = computed(() => {
+  if (!selectDate.value)
+    return ''
+  const date = new Date(selectDate.value)
+  const year = date.getFullYear()
+  const month = String(date.getMonth() + 1).padStart(2, '0')
+  const day = String(date.getDate()).padStart(2, '0')
+  return `${year}-${month}-${day}`
+})
 
-function handleConfirm({ value: selectedValue }: { value: string }) {
-  value.value = selectedValue
+const { data: info } = useRequest(() =>
+  Apis.xsb.findUserPoints({}),
+)
+
+function handleLinkCreditConfirm({ value: selectedValue }: { value: number }) {
+  linkCreditType.value = selectedValue
+}
+
+function handlePeopleCreditConfirm(index: number, { value: selectedValue }: { value: number }) {
+  peopleList.value[index].linkCreditType = selectedValue
+}
+
+// 表单校验
+function validateForm(): boolean {
+  // 联系人姓名校验
+  if (!linkMan.value.trim()) {
+    useGlobalToast().show({ msg: '请输入联系人姓名' })
+    return false
+  }
+
+  // 单游客模式需要校验证件号码
+  if (isSingle.value === 0) {
+    if (!linkCreditNo.value.trim()) {
+      useGlobalToast().show({ msg: '请输入证件号码' })
+      return false
+    }
+  }
+
+  // 手机号码校验
+  if (!linkPhone.value.trim()) {
+    useGlobalToast().show({ msg: '请输入手机号码' })
+    return false
+  }
+
+  // 多游客模式校验游客信息
+  if (isSingle.value === 1) {
+    for (let i = 0; i < peopleList.value.length; i++) {
+      const people = peopleList.value[i]
+      if (!people.linkMan.trim()) {
+        useGlobalToast().show({ msg: `请输入游客${i + 1}姓名` })
+        return false
+      }
+      if (!people.linkCreditNo.trim()) {
+        useGlobalToast().show({ msg: `请输入游客${i + 1}证件号码` })
+        return false
+      }
+    }
+  }
+
+  return true
+}
+
+// 提交订单
+async function handleSubmit() {
+  // 表单校验
+  if (!validateForm())
+    return
+
+  const peoples: Api.PeopleItem[] = isSingle.value === 1
+    ? peopleList.value.map(p => ({
+        linkMan: p.linkMan,
+        linkCreditType: p.linkCreditType,
+        linkCreditNo: p.linkCreditNo,
+      }))
+    : []
+
+  const params: Api.CreateOrderRequest = {
+    productNo: productNo.value,
+    productName: productName.value,
+    num: quantity.value,
+    travelDate: travelDate.value,
+    linkMan: linkMan.value,
+    linkCreditType: isSingle.value === 0 ? linkCreditType.value : undefined,
+    linkCreditNo: isSingle.value === 0 ? linkCreditNo.value : undefined,
+    linkPhone: linkPhone.value,
+    orderMemo: orderMemo.value,
+    peoples: isSingle.value === 1 ? peoples : undefined,
+  }
+
+  try {
+    useGlobalLoading().loading({ msg: '提交中...' })
+    const res = await Apis.attractions.createOrder({ data: params })
+    useGlobalLoading().close()
+    router.push({ name: 'attractions-order-pay', params: { productName: productName.value, num: String(quantity.value), orderNo: res?.data } })
+  }
+  catch (error) {
+    useGlobalLoading().close()
+    console.error('提交订单失败', error)
+  }
 }
 </script>
 
@@ -23,12 +188,12 @@ function handleConfirm({ value: selectedValue }: { value: string }) {
       <view class="h-20rpx" />
       <view class="rounded-16rpx bg-#FFF p-24rpx">
         <view class="text-32rpx font-bold">
-          日场门票+观光车+飞越黄果树观影票+吉祥物+冰箱贴 经典必打卡
+          {{ productName }}
         </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>
+          <text>选定日期:{{ formattedDate }}</text>
         </view>
       </view>
       <view class="mt-20rpx flex items-center justify-between rounded-16rpx bg-#FFF p-24rpx">
@@ -37,83 +202,87 @@ function handleConfirm({ value: selectedValue }: { value: string }) {
         </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]"
+            class="h-36rpx w-36rpx rounded-50% text-center text-28rpx font-600 line-height-[36rpx]"
+            :class="quantity > 1 ? 'bg-#E8FFA7 text-#9ED605' : 'bg-#F0F0F0 text-#AAAAAA'"
+            @click="handleMinus"
           >
             -
           </view>
           <view class="text-24rpx font-400">
-            10
+            {{ quantity }}
           </view>
           <view
             class="h-36rpx w-36rpx rounded-50% bg-#E8FFA7 text-center text-28rpx text-#9ED605 font-600 line-height-[36rpx]"
+            @click="handlePlus"
           >
             +
           </view>
         </view>
       </view>
       <!-- 单游客start -->
-      <view class="mt-20rpx rounded-16rpx bg-#FFF p-24rpx">
+      <view v-if="isSingle === 0" class="mt-20rpx rounded-16rpx bg-#FFF p-24rpx">
         <view class="text-28rpx font-bold">
           联系人
         </view>
         <view>
-          <wd-input placeholder="请输入联系人姓名" label="联系人姓名" required />
+          <wd-input v-model="linkMan" placeholder="请输入联系人姓名" label="联系人姓名" required />
         </view>
         <view class="h-2rpx w-full bg-#F0F0F0" />
         <view>
-          <wd-picker v-model="value" required :columns="columns" label="证件类型" @confirm="handleConfirm" />
+          <wd-picker v-model="linkCreditType" required :columns="columns" label="证件类型" @confirm="handleLinkCreditConfirm" />
         </view>
         <view class="h-2rpx w-full bg-#F0F0F0" />
         <view>
-          <wd-input placeholder="请输入证件号码" label="证件号码" required />
+          <wd-input v-model="linkCreditNo" placeholder="请输入证件号码" label="证件号码" required />
         </view>
         <view class="h-2rpx w-full bg-#F0F0F0" />
         <view>
-          <wd-input placeholder="请输入手机号码" label="手机号码" required />
+          <wd-input v-model="linkPhone" placeholder="请输入手机号码" label="手机号码" required />
         </view>
       </view>
       <!-- 单游客end -->
       <!-- 多游客start -->
-      <view class="mt-20rpx rounded-16rpx bg-#FFF p-24rpx">
+      <view v-if="isSingle === 1" class="mt-20rpx rounded-16rpx bg-#FFF p-24rpx">
         <view class="text-28rpx font-bold">
           联系人
         </view>
         <view>
-          <wd-input placeholder="请输入联系人姓名" label="联系人姓名" required />
+          <wd-input v-model="linkMan" placeholder="请输入联系人姓名" label="联系人姓名" required />
         </view>
         <view class="h-2rpx w-full bg-#F0F0F0" />
         <view>
-          <wd-input placeholder="请输入手机号码" label="手机号码" required />
+          <wd-input v-model="linkPhone" placeholder="请输入手机号码" label="手机号码" required />
         </view>
       </view>
-      <view class="mt-20rpx rounded-16rpx bg-#FFF p-24rpx">
+      <view v-for="(people, index) in peopleList" :key="index" class="mt-20rpx rounded-16rpx bg-#FFF p-24rpx">
         <view class="text-28rpx font-bold">
-          游客信息
+          游客信息{{ index + 1 }}
         </view>
         <view>
-          <wd-input placeholder="请输入游客姓名" label="游客姓名1" required />
+          <wd-input v-model="people.linkMan" placeholder="请输入游客姓名" :label="`游客姓名${index + 1}`" required />
         </view>
         <view class="h-2rpx w-full bg-#F0F0F0" />
         <view>
-          <wd-picker v-model="value" required :columns="columns" label="证件类型" @confirm="handleConfirm" />
+          <wd-picker v-model="people.linkCreditType" required :columns="columns" label="证件类型" @confirm="(e: { value: number }) => handlePeopleCreditConfirm(index, e)" />
         </view>
         <view class="h-2rpx w-full bg-#F0F0F0" />
         <view>
-          <wd-input placeholder="请输入证件号码" label="证件号码" required />
+          <wd-input v-model="people.linkCreditNo" placeholder="请输入证件号码" label="证件号码" required />
         </view>
       </view>
       <!-- 多游客end -->
       <view class="mt-20rpx rounded-16rpx bg-#FFF">
-        <wd-input placeholder="如有特殊需要,请留言" label="订单备注" />
+        <wd-input v-model="orderMemo" 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积分
+          {{ info?.data?.availablePointsTotal || 0 }}积分
         </view>
       </view>
+      <view class="h-190rpx" />
     </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"
@@ -126,7 +295,7 @@ function handleConfirm({ value: selectedValue }: { value: string }) {
           <text class="text-26rpx">
           </text>
-          290
+          {{ totalPrice }}
         </view>
       </view>
       <view class="flex items-center gap-20rpx">
@@ -138,7 +307,7 @@ function handleConfirm({ value: selectedValue }: { value: string }) {
         </view>
         <view
           class="h-80rpx w-180rpx rounded-40rpx bg-#9ED605 text-center text-28rpx text-#FFF font-bold line-height-[80rpx]"
-          @click="router.push({ name: 'attractions-order-pay' })"
+          @click="handleSubmit"
         >
           提交
         </view>
@@ -152,19 +321,16 @@ function handleConfirm({ value: selectedValue }: { value: string }) {
         <view class="mt-24rpx h-2rpx w-full bg-#F0F0F0" />
         <view class="mt24rpx text-28rpx">
           <view class="flex items-center gap-56rpx">
-            <view>黄果树瀑布风景区成人票 16:00-19:00</view>
+            <view>{{ productName }}</view>
             <view>
               <text class="text-#FF4A39 font-bold">
-                ¥290
+                ¥{{ price }}
               </text>
-              <text>×1</text>
+              <text>×{{ quantity }}</text>
             </view>
           </view>
           <view class="mt-20rpx">
-            入园周末节假日
-          </view>
-          <view class="mt-20rpx">
-            2026年3月22日(周日)
+            {{ formattedDate }}
           </view>
         </view>
       </view>

+ 24 - 0
src/subPack-attractions/attractionsReservation/reservation-data.ts

@@ -0,0 +1,24 @@
+export const columns = ref([
+  { label: '身份证', value: 0 },
+  { label: '学生证', value: 1 },
+  { label: '军官证', value: 2 },
+  { label: '护照', value: 3 },
+  { label: '户口本(儿童请选择此项)', value: 4 },
+  { label: '港澳通行证', value: 5 },
+  { label: '台湾居民来往大陆通行证', value: 6 },
+  { label: '台湾通行证', value: 7 },
+  { label: '入台证', value: 8 },
+  { label: '香港居民往来内地通行证', value: 9 },
+  { label: '警官证', value: 10 },
+  { label: '驾驶证', value: 11 },
+  { label: '海员证', value: 12 },
+  { label: '外国人在中国永久居留证', value: 13 },
+  { label: '澳门居民往来内地通行证', value: 14 },
+  { label: '港澳居民来往内地通行证', value: 15 },
+  { label: '港澳台居民来往内地通行证', value: 16 },
+  { label: '港澳台居民居住证', value: 17 },
+  { label: '中华人民共和国旅行证', value: 18 },
+  { label: '回乡证', value: 19 },
+  { label: '台胞证', value: 20 },
+  { label: '香港身份证', value: 21 },
+])

+ 1 - 7
src/subPack-attractions/commonTab/components/homeList.vue

@@ -34,9 +34,6 @@ onMounted(() => {
   opcity.value = 0
 })
 
-/**
- * 搜索景区
- */
 function handleSearch() {
   if (productName.value === '') {
     return globalToast.warning('搜索内容不能为空')
@@ -45,9 +42,6 @@ function handleSearch() {
   refresh()
 }
 
-/**
- * 清空搜索
- */
 function handleClear() {
   productName.value = ''
   page.value = 1
@@ -95,7 +89,7 @@ function handleClear() {
       <view>
         <scroll-view scroll-y type="custom">
           <wd-skeleton theme="paragraph" animation="gradient" :loading="loading" :row-col="[{ height: '100px', width: '100%' }, { height: '100px', width: '100%' }, { height: '100px', width: '100%' }]">
-            <view v-for="item in attractionsList" :key="item.productNo" class="mb-20rpx flex items-center gap-24rpx rounded-16rpx bg-#FFF p-24rpx" @click="router.push({ name: 'attractions-detail' })">
+            <view v-for="item in attractionsList" :key="item.productNo" class="mb-20rpx flex items-center gap-24rpx rounded-16rpx bg-#FFF p-24rpx" @click="router.push({ name: 'attractions-detail', params: { productNo: String(item.productNo) } })">
               <image
                 class="h-160rpx w-160rpx rounded-16rpx"
                 :src="item.img"

+ 65 - 7
src/subPack-attractions/commonTab/components/orderList.vue

@@ -4,24 +4,82 @@ import attractionsList from '@/subPack-smqjh/components/attractions-orderList/at
 
 const tab = ref<number>(0)
 const { statusBarHeight, MenuButtonHeight } = storeToRefs(useSysStore())
-function handleClick(e: any) {
-  console.log(e)
+
+// 状态映射:tab索引 -> 接口status
+const statusMap: Record<number, number> = {
+  0: 0, // 全部
+  1: 1, // 待支付
+  2: 2, // 已支付
+  3: 3, // 已取消
+  4: 4, // 已完成
 }
+
+// 使用 usePagination 调用接口
+const { data: orderList, isLastPage, reload, page, loading } = usePagination(
+  (pageNum, pageSize) => Apis.attractions.orderList({
+    data: {
+      pageNum,
+      pageSize,
+      status: statusMap[tab.value],
+    },
+  }),
+  {
+    immediate: true,
+    pageNum: 1,
+    pageSize: 10,
+    initialData: [],
+    data: res => res.data?.list,
+    append: true,
+  },
+)
+
+// tab切换
+function handleClick(e: { index: number }) {
+  tab.value = e.index
+  // 清空数据后重新加载,避免数据溢出
+  orderList.value = []
+  reload()
+}
+
+onReachBottom(() => {
+  if (!isLastPage.value) {
+    page.value++
+  }
+})
+const loadMoreState = computed(() => {
+  if (loading.value)
+    return 'loading'
+  if (isLastPage.value)
+    return 'finished'
+  return 'loading'
+})
 </script>
 
 <template>
-  <view class="attractions-order-page">
+  <view class="attractions-order-page" style="min-height: 100vh; background-color: #f6f6f6;">
     <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` }" />
-    <wd-tabs v-model="tab" animated @click="handleClick">
-      <block v-for="item in tabsList" :key="item">
+    <wd-tabs v-model="tab" animated custom-style="background-color: transparent;" @click="handleClick">
+      <block v-for="(item, index) in tabsList" :key="index">
         <wd-tab :title="item">
-          <view class="box-border bg-#f6f6f6 px24rpx">
+          <view class="box-border px24rpx" style="background-color: #f6f6f6; min-height: 60vh;">
             <view class="h-20rpx" />
-            <attractionsList order />
+            <StatusTip v-if="!orderList.length && !loading" tip="暂无内容" />
+            <attractionsList
+              v-for="order in orderList"
+              :key="order.orderNumber"
+              :order="order"
+            />
+            <wd-loadmore
+              v-if="orderList.length > 0"
+              :state="loadMoreState"
+              :loading-props="{ color: '#9ED605', size: 20 }"
+            />
+            <!-- 底部安全区域占位 -->
+            <view class="h-40rpx" />
           </view>
         </wd-tab>
       </block>

+ 5 - 1
src/subPack-attractions/commonTab/index.vue

@@ -14,7 +14,11 @@ definePage({
     navigationStyle: 'custom',
   },
 })
-
+onLoad((options: any) => {
+  if (options.tabbar) {
+    tabbar.value = Number(options.tabbar)
+  }
+})
 // 页面级滚动监听 - 必须在页面组件中才能生效
 onPageScroll((e) => {
   // 只在首页 tab 时更新透明度

+ 1 - 1
src/subPack-attractions/components/DatePicker.vue

@@ -169,7 +169,7 @@ watch(() => props.modelValue, (newVal) => {
       <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">
+      <view class="rounded-16rpx bg-#E6E6E6 px12rpx px32rpx py12rpx py8rpx text-28rpx">
         {{ currentMonthText }}
       </view>
       <view class="h60rpx w60rpx flex items-center justify-center" @click="nextMonth">

+ 35 - 20
src/subPack-smqjh/components/attractions-orderList/attractions-orderList.vue

@@ -1,43 +1,58 @@
 <script setup lang="ts">
-defineProps<{
-  order: any
+import router from '@/router'
+
+const props = defineProps<{
+  order: Api.ScenicOrderListVo
 }>()
 
-function handleItemClick(item: Api.xsbOrderList) {
-  console.log(item)
+// 订单状态映射(hbOrderStatus)
+const statusMap: Record<number, { text: string, color: string }> = {
+  0: { text: '待支付', color: '#FF9500' },
+  60: { text: '已取消', color: '#999999' },
+  70: { text: '已支付', color: '#52C41A' },
+  80: { text: '已完成', color: '#52C41A' },
+}
+// 订单项点击
+function handleOrderClick(order: Api.ScenicOrderListVo) {
+  router.push({ name: 'attractions-order-detail', params: { orderNo: String(order.orderNumber) } })
 }
+const statusInfo = computed(() => {
+  const status = props.order?.hbOrderStatus as number
+  return statusMap[status] ?? { text: '', color: '#999999' }
+})
 </script>
 
 <template>
-  <view class="mb-20rpx rounded-16rpx bg-#FFF p-24rpx" @click="handleItemClick(order)">
+  <view class="mb-20rpx rounded-16rpx bg-#FFF p-24rpx" @click="handleOrderClick(order)">
     <view class="flex items-center justify-between">
       <view class="text-32rpx font-bold">
-        贵州黄果树瀑布景区
+        {{ order?.viewName }}
       </view>
-      <view class="text-28rpx text-#AAAAAA">
-        已取消
+      <view class="text-28rpx" :style="{ color: statusInfo.color }">
+        {{ statusInfo.text }}
       </view>
     </view>
     <view class="mt-20rpx flex items-center gap-20rpx">
-      <view class="h-160rpx w-160rpx">
-        图片
+      <image v-if="order?.img" :src="order.img" class="h-160rpx w-160rpx rounded-8rpx" mode="aspectFill" />
+      <view v-else class="h-160rpx w-160rpx flex items-center justify-center rounded-8rpx bg-#f5f5f5 text-#999">
+        暂无图片
       </view>
-      <view>
-        <view class="text-32rpx font-bold">
-          日场门票+观光车+飞越黄果树观影票+吉祥物+冰箱贴 经典必打卡
+      <view class="flex-1">
+        <view class="line-clamp-2 text-28rpx font-bold">
+          {{ order?.productName }}
         </view>
-        <view class="mt-20rpx text-28rpx">
-          游玩日期:2026-03-22
+        <view class="mt-20rpx text-24rpx text-#666">
+          游玩日期:{{ order?.travelDate }}
         </view>
       </view>
     </view>
-    <view class="mt-24rpx flex items-center justify-between text-24rpx">
-      <view>订单编号:1867402054587256856</view>
+    <view class="mt-24rpx flex items-center justify-between text-24rpx text-#999">
+      <view>订单编号:{{ order?.orderNumber }}</view>
       <view>共1件商品</view>
     </view>
     <view class="mt-20rpx flex items-center justify-between">
-      <view class="text-24rpx">
-        下单时间:2024-12-13 11:12:30
+      <view class="text-24rpx text-#999">
+        下单时间:{{ order?.createTime }}
       </view>
       <view>
         <text class="text-24rpx">
@@ -47,7 +62,7 @@ function handleItemClick(item: Api.xsbOrderList) {
         </text>
         <text class="text-bold text-32rpx text-#FF4A39">
-          290
+          {{ order?.actualTotal }}
         </text>
       </view>
     </view>

+ 22 - 0
src/utils/index.ts

@@ -217,3 +217,25 @@ export function parseUrlParams(url: string): Record<string, string> {
     return {}
   }
 }
+
+/**
+ *处理富文本图片溢出
+ * @param html - 富文本字符串
+ * @returns
+ */
+export function fixImgStyle(html: string) {
+  if (!html)
+    return html
+  // 处理已有 style 属性的 img 标签:将样式追加到现有 style 中
+  let result = html.replace(/<img([^>]*)style\s*=\s*["']([^"']*)["']([^>]*)>/gi, (match, before, existingStyle, after) => {
+    // 移除可能存在的 width/height 内联样式,然后添加防溢出样式
+    const cleanedStyle = existingStyle.replace(/\bwidth\s*:[^;]*;?/gi, '').replace(/\bheight\s*:[^;]*;?/gi, '')
+    const newStyle = `${cleanedStyle};max-width:100%;height:auto;display:block;`.replace(/^;+|;+$/g, '').replace(/;{2,}/g, ';')
+    return `<img${before}style="${newStyle}"${after}>`
+  })
+
+  // 处理没有 style 属性的 img 标签:添加新的 style
+  result = result.replace(/<img(?![^>]*style\s*=)([^>]*)>/gi, '<img style="max-width:100%;height:auto;display:block;"$1>')
+
+  return result
+}