Quellcode durchsuchen

```
feat(QCode): 新增二维码下载功能并支持多实例

- 添加 qrKey 属性以支持组件多实例
- 实现 downloadQrcode 方法,支持 H5 端下载和小程序/APP 端保存到相册
- 增加授权检查逻辑处理图片保存权限
- 使用 defineExpose 暴露下载方法供父组件调用

feat(film): 电影模块增加登录验证和优化用户交互

- 设置电影首页需要登录验证 (islogin: true)
- 在电影列表页面添加"想看"功能和评分显示优化
- 优化座位选择页面的时间判断逻辑
- 改进订单详情页的二维码展示和下载功能

refactor(components): 统一组件样式类名规范

- 统一使用 Tailwind CSS 的方括号语法处理特殊值
- 修正多个组件中的样式类名格式
- 优化个人中心页面布局和间距样式

feat(film): 完善电影订单管理和导航功能

- 添加电影订单列表组件用于统一管理
- 实现订单详情页的地图导航功能
- 优化取票码复制和下载流程
- 修复订单数据渲染相关问题

fix(seat): 修复座位选择和价格计算逻辑

- 修正快票价格计算逻辑
- 修复区域价格显示格式问题
- 调整座位区域布局样式对齐
```

wenjie vor 1 Woche
Ursprung
Commit
98211b0359

+ 2 - 0
async-import.d.ts

@@ -8,6 +8,8 @@ interface ModuleMap {
   '@/subPack-xsb/utils/order-data': typeof import('@/subPack-xsb/utils/order-data')
   '@/subPack-xsb/utils/confirm-order': typeof import('@/subPack-xsb/utils/confirm-order')
   '@/subPack-xsb/store-xsb/sys': typeof import('@/subPack-xsb/store-xsb/sys')
+  '@/subPack-film/utils/order-data': typeof import('@/subPack-film/utils/order-data')
+  '@/subPack-film/utils/confirm-order': typeof import('@/subPack-film/utils/confirm-order')
   [path: string]: any
 }
 

+ 62 - 4
src/components/QCode.vue

@@ -4,22 +4,80 @@
 
 import UQRCode from 'uqrcodejs'
 
-const props = defineProps<{ text: string, qwidth: number }>()
+const props = defineProps<{ text: string, qwidth: number, qrKey: string }>()
 const _this = getCurrentInstance()
 function createQRCode() {
   const qr = new UQRCode()
   qr.data = props.text
-  qr.size = props.qwidth || 100
+  qr.size = props.qwidth || 100 // 这里是px,和下面的不匹配
   qr.make()
-  const canvasContext = uni.createCanvasContext('qrcode', _this)
+  const canvasContext = uni.createCanvasContext(props.qrKey, _this)
   qr.canvasContext = canvasContext
   qr.drawCanvas()
 }
 createQRCode()
+
+async function downloadQrcode() {
+  try {
+    // 1. 先确保二维码已绘制完成(短暂延迟,兼容绘制异步性)
+    await new Promise(resolve => setTimeout(resolve, 300))
+
+    // 2. 将Canvas转为临时文件路径(复用原有canvas-id)
+    const tempRes: any = await uni.canvasToTempFilePath({
+      canvasId: props.qrKey, // 与原有Canvas的canvas-id保持一致
+      width: props.qwidth,
+      height: props.qwidth,
+      destWidth: props.qwidth * 2, // 生成高清图
+      destHeight: props.qwidth * 2,
+      fileType: 'png',
+    }, _this)
+
+    // 3. 分端处理下载/保存逻辑
+    const systemInfo = uni.getSystemInfoSync()
+    if (systemInfo.platform === 'h5') {
+      // H5端:浏览器下载
+      const link = document.createElement('a')
+      link.href = tempRes.tempFilePath
+      link.download = `二维码_${Date.now()}.png`
+      link.click()
+      uni.showToast({ title: '二维码下载成功' })
+    }
+    else {
+      // 小程序/APP端:保存到相册(处理授权)
+      // 检查授权状态
+      const settingRes = await uni.getSetting({})
+      if (!settingRes.authSetting['scope.writePhotosAlbum']) {
+        // 未授权则请求授权
+        try {
+          await uni.authorize({ scope: 'scope.writePhotosAlbum' })
+        }
+        catch {
+          uni.showToast({ title: '需授权保存相册才能下载', icon: 'none' })
+          return
+        }
+      }
+
+      // 保存到系统相册
+      await uni.saveImageToPhotosAlbum({
+        filePath: tempRes.tempFilePath,
+      })
+      uni.showToast({ title: '二维码已保存到相册' })
+    }
+  }
+  catch (err) {
+    // const errMsg = (err as any).errMsg
+    uni.showToast({ title: `下载失败`, icon: 'none' })
+    console.error('二维码下载失败:', err)
+  }
+}
+
+defineExpose({
+  downloadQrcode,
+})
 </script>
 
 <template>
-  <canvas id="qrcode" canvas-id="qrcode" :style="{ width: `${props.qwidth}px`, height: `${props.qwidth}px` }" />
+  <canvas :id="qrKey" :canvas-id="qrKey" :style="{ width: `${props.qwidth}px`, height: `${props.qwidth}px` }" />
 </template>
 
 <style scoped>

+ 1 - 1
src/pages.json

@@ -313,7 +313,7 @@
         {
           "path": "index/index",
           "name": "film-index",
-          "islogin": false,
+          "islogin": true,
           "style": {
             "navigationBarTitleText": "电影演出",
             "backgroundColorBottom": "#fff"

+ 20 - 21
src/pages/my/index.vue

@@ -40,15 +40,15 @@ function handleGo(item: { name: string }) {
 </script>
 
 <template>
-  <view class="page-class bg-#F9F9F9 dark:bg-[var(--wot-dark-background)]">
+  <view class="page-class bg-[#F9F9F9] dark:bg-[var(--wot-dark-background)]">
     <wd-navbar
       title="个人中心" custom-style="background-color: transparent !important;" :bordered="false"
       safe-area-inset-top fixed :z-index="99"
     />
-    <view class="header relative h-408rpx rounded-18px">
-      <view class="absolute bottom-100rpx left-0 box-border w-full flex items-center justify-between pl48rpx pr58rpx">
+    <view class="header relative h-408rpx rounded-[18px]">
+      <view class="absolute bottom-100rpx left-0 box-border w-full flex items-center justify-between pl-48rpx pr-58rpx">
         <template v-if="!token">
-          <image :src="`${StaticUrl}/9.png`" alt="" class="h100rpx w100rpx" />
+          <image :src="`${StaticUrl}/9.png`" alt="" class="h-100rpx w-100rpx" />
           <view class="text-32rpx font-semibold">
             请登录后使用完整功能
           </view>
@@ -58,12 +58,12 @@ function handleGo(item: { name: string }) {
         </template>
         <template v-else>
           <view class="flex items-center">
-            <image :src="getUserAvatar" alt="" class="h100rpx w100rpx flex-shrink-0 rounded-full" />
-            <view class="ml20rpx flex-1">
+            <image :src="getUserAvatar" alt="" class="h-100rpx w-100rpx flex-shrink-0 rounded-full" />
+            <view class="ml-20rpx flex-1">
               <view class="text-32rpx font-semibold">
                 {{ userInfo.nickName }}
               </view>
-              <view class="mt12rpx rounded-8rpx bg-white px12rpx py4rpx text-24rpx text-[var(--them-color)] opacity-70">
+              <view class="mt-12rpx rounded-8rpx bg-white px-12rpx py-4rpx text-24rpx text-[var(--them-color)] opacity-70">
                 {{ userInfo.channelName }}
               </view>
             </view>
@@ -71,9 +71,9 @@ function handleGo(item: { name: string }) {
           <view class="flex flex-col items-center" @click="router.push({ name: 'common-user-center' })">
             <image
               :src="`${StaticUrl}/user-setting.png`"
-              class="h48rpx w48rpx"
+              class="h-48rpx w-48rpx"
             />
-            <view class="mt12rpx text-24rpx text-[var(--them-color)]">
+            <view class="mt-12rpx text-24rpx text-[var(--them-color)]">
               账户设置
             </view>
           </view>
@@ -97,29 +97,29 @@ function handleGo(item: { name: string }) {
         </template>
         <view class="grid grid-cols-4 gap-4">
           <view v-for="item in tabList" :key="item.title" class="flex flex-col items-center justify-center" @click="handleGo(item)">
-            <image :src="item.icon" class="h56rpx w56rpx" />
-            <view class="mt20rpx text-24rpx">
+            <image :src="item.icon" class="h-56rpx w-56rpx" />
+            <view class="mt-20rpx text-24rpx">
               {{ item.title }}
             </view>
           </view>
         </view>
-        <view class="h20rpx" />
+        <view class="h-20rpx" />
       </wd-card>
     </view>
-    <view class="item-cell mt20rpx">
+    <view class="item-cell mt-20rpx">
       <wd-card custom-class="card">
         <wd-cell-group custom-class="cell-group">
           <wd-cell title="收货地址" custom-title-class="cell-title" clickable is-link @click="router.push({ name: 'common-addressList' })">
             <template #icon>
-              <image :src="`${StaticUrl}/4.png`" class="h50rpx w50rpx" />
+              <image :src="`${StaticUrl}/4.png`" class="h-50rpx w-50rpx" />
             </template>
           </wd-cell>
           <wd-cell custom-title-class="cell-title" clickable is-link>
             <template #icon>
-              <image :src="`${StaticUrl}/5.png`" class="h50rpx w50rpx" />
+              <image :src="`${StaticUrl}/5.png`" class="h-50rpx w-50rpx" />
             </template>
             <template #title>
-              <view class="ml20rpx w90%">
+              <view class="ml-20rpx w-[90%]">
                 <Zcontact>联系平台客服</Zcontact>
               </view>
             </template>
@@ -127,28 +127,27 @@ function handleGo(item: { name: string }) {
         </wd-cell-group>
       </wd-card>
     </view>
-    <view class="item-cell mt20rpx">
+    <view class="item-cell mt-20rpx">
       <wd-card custom-class="card">
         <wd-cell-group custom-class="cell-group">
           <wd-cell title="积分" custom-title-class="cell-title" clickable is-link @click="router.push({ name: 'common-integral' })">
             <template #icon>
-              <image :src="`${StaticUrl}/7.png`" class="h50rpx w50rpx" />
+              <image :src="`${StaticUrl}/7.png`" class="h-50rpx w-50rpx" />
             </template>
           </wd-cell>
           <wd-cell title="评价" custom-title-class="cell-title" clickable is-link>
             <template #icon>
-              <image :src="`${StaticUrl}/8.png`" class="h50rpx w50rpx" />
+              <image :src="`${StaticUrl}/8.png`" class="h-50rpx w-50rpx" />
             </template>
           </wd-cell>
         </wd-cell-group>
       </wd-card>
     </view>
-    <view v-if="token" class="mt80rpx flex items-center justify-center">
+    <view v-if="token" class="mt-80rpx flex items-center justify-center">
       <wd-button @click="handleLoginOut">
         退出登录
       </wd-button>
     </view>
-    <QCode text="123" :qwidth="100" />
   </view>
 </template>
 

+ 2 - 2
src/subPack-film/choose-film/index.vue

@@ -255,7 +255,7 @@ onLoad((options) => {
     <wd-action-sheet v-if="show" v-model="show" @close="close">
       <view class="content">
         <!-- 影院时间 -->
-        <selectTime v-model="query.showDates" />
+        <selectTime v-model="query.showDates" :data="dayList" @change="changeTime" />
         <view class="choose-list">
           <view class="choose-item" :class="[active == 0 ? 'active' : '']" @click="handleChoose(0)">
             <view class="choose-item-title">
@@ -320,7 +320,7 @@ onLoad((options) => {
         </view>
         <view v-if="active == 1" class="choose-box">
           <view class="choose-content">
-            <view class="choose-item" @click="choose(query.cityId)">
+            <view class="choose-item" @click="choose('')">
               <view class="name">
                 全部
               </view>

+ 3 - 3
src/subPack-film/choose-seat/index.vue

@@ -118,6 +118,7 @@ const query = ref({
   switchSeat: false,
   movieName: '',
   postImageUrl: '',
+  planType: '',
 
 })
 const active = ref('')
@@ -302,7 +303,7 @@ async function buySeat() {
   }
 
   query.value.seatNames = chooseSeatList.value.map(item => item.seatName).join(',')
-
+  query.value.planType = seatInfo.value?.language as string + seatInfo.value?.planType
   uni.showToast({
     title: '开始锁座',
     icon: 'none',
@@ -400,7 +401,7 @@ function judgeNearDaysSimple(time: any) {
       <view v-for="(item, index) in areaList" :key="index" class="area-item">
         <view class="area" :style="`border:1px solid ${item.strokeStyle}`" />
         <view class="area-price">
-          {{ item.areaName + (isWithin45Minutes(new Date(), seatInfo.showTime) ? item.fastPrice : item.price) }}
+          {{ `${item.areaName} ${isWithin45Minutes(new Date(), seatInfo.showTime) ? item.fastPrice : item.price}` }}
         </view>
       </view>
     </view>
@@ -488,7 +489,6 @@ function judgeNearDaysSimple(time: any) {
   display: flex;
   align-items: center;
   /* 上下居中 */
-  justify-content: center;
   /* 左右居中(可选) */
   height: 40px;
   white-space: nowrap;

+ 0 - 34
src/subPack-film/components/code.vue

@@ -1,34 +0,0 @@
-<script setup lang="ts">
-// eslint-disable-next-line ts/ban-ts-comment
-// @ts-expect-error
-import UQRCode from 'uqrcodejs'
-
-const props = defineProps<{ canvasWidth: number, text: string }>()
-const instance = getCurrentInstance()
-function getQrcode() {
-  const code = new UQRCode()
-  code.data = props.text
-  code.size = props.canvasWidth * 0.29
-  code.areaColor = 'rgba(25,147,227,0.5)'
-  code.make()
-  const canvasText = uni.createCanvasContext('qrcodeCanvas', instance)
-  code.canvasContext = canvasText
-  code.drawCanvas()
-  console.log(code, 'code')
-}
-onReady(() => {
-  getQrcode()
-})
-defineExpose({ getQrcode })
-</script>
-
-<template>
-  <canvas
-    id="qrcodeCanvas" :style="{ width: `${canvasWidth * 0.29}px`, height: `${canvasWidth * 0.29}px` }"
-    canvas-id="qrcodeCanvas" type="2d"
-  />
-</template>
-
-<style scoped>
-
-</style>

+ 1 - 1
src/subPack-film/components/tabbar.vue

@@ -16,7 +16,7 @@ const tabList = reactive([
 ])
 
 function handleItem(name: string) {
-  router.push({ name })
+  router.replace({ name })
 }
 </script>
 

+ 1 - 1
src/subPack-film/index/index.vue

@@ -4,7 +4,7 @@ import router from '@/router'
 
 definePage({
   name: 'film-index',
-  islogin: false,
+  islogin: true,
   style: {
     navigationBarTitleText: '电影演出',
     backgroundColorBottom: '#fff',

+ 13 - 3
src/subPack-film/movie/index.vue

@@ -139,6 +139,10 @@ function handleBuy(item: Api.filmMovieList) {
   console.log('goumai')
   router.push({ name: 'film-movie-detail', params: { id: item.id as string, movieId: item.movieId as string } })
 }
+
+function handleWant() {
+  useGlobalToast().show('敬请期待')
+}
 </script>
 
 <template>
@@ -186,7 +190,7 @@ function handleBuy(item: Api.filmMovieList) {
               </view>
             </view>
             <view class="num">
-              {{ item.wish }}人评分
+              {{ item.wish }}人想看
             </view>
           </view>
           <view class="type-box">
@@ -269,7 +273,7 @@ function handleBuy(item: Api.filmMovieList) {
               </view>
             </view>
             <view class="num">
-              {{ item.wish }}人评分
+              {{ item.wish }}人想看
             </view>
           </view>
           <view class="type-box">
@@ -289,7 +293,7 @@ function handleBuy(item: Api.filmMovieList) {
         </view>
         <view class="btn-box">
           <image class="img" :src="`${StaticUrl}/film-movie-icon.png`" mode="scaleToFill" />
-          <view class="btn">
+          <view class="btn" @click="handleWant()">
             想看
           </view>
         </view>
@@ -368,8 +372,12 @@ function handleBuy(item: Api.filmMovieList) {
         }
         .num-box{
           display: flex;
+          align-items: flex-end;
           margin-top: 8rpx;
+          line-height: 28rpx;
           .score-box{
+            display: flex;
+            align-items: flex-end;
             .label{
               font-size: 24rpx;
               color: #AAAAAA;
@@ -378,11 +386,13 @@ function handleBuy(item: Api.filmMovieList) {
               font-weight: bold;
               font-size: 28rpx;
               color: #FF4D3A;
+              margin-left: 6rpx;
             }
           }
           .num{
             font-size: 24rpx;
             color: #AAAAAA;
+            margin-left: 8rpx;
           }
         }
         .type-box{

+ 51 - 27
src/subPack-film/order-detail/index.vue

@@ -13,7 +13,8 @@ definePage({
 })
 
 const orderInfo = ref()
-
+const current = ref(0)
+const qrRefs = ref<Record<string, any>>({})
 function handleCopy(data: string) {
   uni.setClipboardData({
     data,
@@ -26,19 +27,12 @@ function handleCopy(data: string) {
   })
 }
 
-function downloadImg() {
-  uni.saveImageToPhotosAlbum({
-    filePath: `${StaticUrl}/film-qrcode.png`,
-    success() {
-      uni.showToast({
-        title: '保存成功',
-        icon: 'none',
-      })
-    },
-    fail(err) {
-      console.log('保存失败:', err)
-    },
-  })
+async function downloadImg() {
+  await nextTick()
+  const qrInstance = qrRefs.value[current.value]
+  console.log(qrInstance, current.value)
+
+  qrInstance.downloadQrcode()
 }
 
 async function getData(orderNo: string) {
@@ -46,7 +40,12 @@ async function getData(orderNo: string) {
   // const res = await Apis.film.filmOrderDetail({ data: { id } })
 
   orderInfo.value = res.data
-  orderInfo.value.codeList = orderInfo.value.orderMovieItems[0].ticketCode?.split(',')
+  if (orderInfo.value.orderMovieItems[0].ticketCode) {
+    orderInfo.value.codeList = orderInfo.value.orderMovieItems[0].ticketCode?.split(',')
+  }
+  else {
+    orderInfo.value.codeList = []
+  }
 }
 function call() {
   uni.makePhoneCall({
@@ -54,6 +53,19 @@ function call() {
   })
 }
 
+function handleNav() {
+  uni.openLocation({
+    latitude: Number(orderInfo.value?.latitude),
+    longitude: Number(orderInfo.value?.longitude),
+    name: orderInfo.value.cinemaName,
+    address: orderInfo.value.cinemaName,
+  })
+}
+function changeSwiper(e: any) {
+  console.log(e.detail.current)
+  current.value = e.detail.current
+}
+
 onLoad((options) => {
   getData(options?.orderNo as string)
 })
@@ -100,17 +112,26 @@ onLoad((options) => {
         <view class="notice">
           (前为取票码,后为验票码)
         </view> -->
-        <view class="qrcode-box">
-          <view class="qrcode">
-            <!-- <uqrcode value="test" canvas-id="qrcode" />
-              -->
-          </view>
-          <Qrcode content="https://example.com/测试中文内容" :size="260" color="#333333" error-level="H" />
-
-          <view class="download-btn" @click="downloadImg">
-            下载到手机
+        <template v-if="orderInfo.codeList.length > 0">
+          <swiper class="swiper w-[100%]" :indicator-dots="true" @change="changeSwiper">
+            <swiper-item
+              v-for="(item, index) in orderInfo.codeList" :key="index"
+              class="flex items-center justify-center"
+            >
+              <QCode
+                :ref="(el) => { if (el) qrRefs[index] = el; }" class="my-20rpx" :text="item" :qwidth="90"
+                :qr-key="`qr${index}`"
+              />
+            </swiper-item>
+          </swiper>
+          <view class="qrcode-box">
+            <!-- <view class="qrcode">
+            </view> -->
+            <view class="download-btn" @click="downloadImg">
+              下载到手机
+            </view>
           </view>
-        </view>
+        </template>
       </view>
       <!-- <view class="open-box">
         <view class="text">
@@ -200,7 +221,7 @@ onLoad((options) => {
         {{ orderInfo.cinemaName }}
       </view>
       <view class="icon-box">
-        <view class="icon-item">
+        <view class="icon-item" @click="handleNav">
           <image class="icon" :src="`${StaticUrl}/film-nav-icon.png`" />
           <view class="text">
             导航
@@ -250,7 +271,7 @@ onLoad((options) => {
       </view>
       <view class="item">
         <view class="label">
-          积分({{ orderInfo.offsetPoints }})
+          积分({{ orderInfo.offsetPoints || 0 }})
         </view>
         <view class="value price">
           -¥{{ orderInfo.offsetPoints / 100 }}
@@ -512,6 +533,9 @@ onLoad((options) => {
       font-weight: bold;
       font-size: 32rpx;
       color: #222222;
+      white-space: nowrap;
+      overflow: hidden;
+      text-overflow: ellipsis;
     }
     .icon-box{
       display: flex;

+ 7 - 3
src/subPack-film/order/index.vue

@@ -62,8 +62,11 @@ async function handleCancel(order: Api.xsbOrderList) {
   await handleCommonCancelOrder(order)
   reload()
 }
-function handleCopy() {
-
+function handleCopy(data: string) {
+  uni.setClipboardData({
+    data,
+    showToast: true,
+  })
 }
 function handleOrder(orderNo: string) {
   router.push({
@@ -162,8 +165,9 @@ onLoad(() => {
             </view>
           </template>
           <wd-button
+            v-if="item.orderMovieItems[0].ticketCode"
             custom-style="width: 188rpx;height: 44rpx;font-size: 28rpx;box-sizing: border-box"
-            @click="handleCopy"
+            @click="handleCopy(item.orderMovieItems[0].ticketCode as string)"
           >
             复制取票码
           </wd-button>

+ 3 - 2
src/subPack-film/submit-order/index.vue

@@ -39,6 +39,7 @@ const query = ref({
   session: '',
   postImageUrl: '',
   movieName: '',
+  fastTicket: false,
 })
 
 const info = ref({
@@ -90,7 +91,7 @@ function next() {
 async function pay() {
   loading.value = true
   console.log(info.value.chooseSeatList)
-
+  query.value.fastTicket = isWithin45Minutes(new Date(), info.value.showTime)
   query.value.movieOrderItems = info.value.chooseSeatList.map((item: any) => {
     if (isWithin45Minutes(new Date(), info.value.showTime)) { // 如果在45分钟内,快速出票
       return {
@@ -129,7 +130,7 @@ async function getPoints() {
 getPoints()
 
 function usePoints() {
-  return (totalPrice.value * 100) >= info.value.points ? info.value.points : totalPrice.value * 100
+  return (totalPrice.value * 100) >= info.value.points ? info.value.points : ((totalPrice.value * 10000) / 100)
 }
 
 onLoad((options) => {

+ 245 - 0
src/subPack-smqjh/components/film-orderList/film-orderList.vue

@@ -0,0 +1,245 @@
+<script setup lang="ts">
+// import { StaticUrl } from '@/config'
+import router from '@/router'
+
+const props = defineProps<{
+  orderList: Api.xsbOrderList[]
+  subPackOrder?: typeof import('@/subPack-film/utils/order-data')
+  subPackConfirm?: typeof import('@/subPack-film/utils/confirm-order')
+}>()
+
+const _emit = defineEmits<{
+  'after-sale': [item: Api.xsbOrderList]
+  'refresh': []
+}>()
+
+function resolveModule<T = any>(maybeRef: any): T | undefined {
+  if (!maybeRef)
+    return undefined
+  if (maybeRef.value !== undefined)
+    return maybeRef.value as T
+  return maybeRef as T
+}
+async function handlePay(orderNumber: string) {
+  const spc = resolveModule(props.subPackConfirm)
+  if (!spc)
+    return
+  const res = await spc.handleCommonPayMent(orderNumber)
+  if (res.payType !== 1) {
+    await getWxCommonPayment(res)
+  }
+  else {
+    _emit('refresh')
+  }
+}
+async function handleCancel(order: Api.xsbOrderList) {
+  const sp = resolveModule(props.subPackOrder)
+  if (!sp)
+    return
+  await handleCommonCancelOrder(order)
+  _emit('refresh')
+}
+function handleCopy(data: string) {
+  uni.setClipboardData({
+    data,
+    showToast: true,
+  })
+}
+
+function handleOrder(orderNo: string) {
+  router.push({
+    name: 'film-order-detail',
+    params: {
+      orderNo,
+    },
+  })
+}
+</script>
+
+<template>
+  <view
+    v-for="(item, index) in orderList" :key="index" class="order-item block"
+    @click.self="handleOrder(item.orderNumber as string)"
+  >
+    <view class="top-box">
+      <view class="title">
+        {{ item.movieName }}
+      </view>
+      <view class="status">
+        <template v-if="item.hbOrderStatus === props.subPackOrder?.OrderStatus.PaddingPay">
+          <view class="flex items-center">
+            待支付( 还剩 <wd-count-down
+              :time="props.subPackOrder?.handleCommonOrderStatusText(item)"
+              @finish="() => _emit('refresh')"
+            /> )
+          </view>
+        </template>
+        <text v-else>
+          {{ props.subPackOrder?.handleCommonOrderStatusText(item) }}
+        </text>
+      </view>
+    </view>
+    <!-- <view class="status-box">
+          <view class="reason-box">
+            <image class="icon" :src="`${StaticUrl}/film-error.png`" mode="scaleToFill" />
+            <view class="reason">
+              订单取消原因
+            </view>
+          </view>
+          <view class="info">
+            退款金额 ¥240 预计1-3个工作日到账
+          </view>
+        </view> -->
+
+    <view class="info-box">
+      <image class="img" :src="item.orderImage" mode="scaleToFill" />
+      <view class="info">
+        <view class="info-item">
+          影院:{{ item.cinemaName }}
+        </view>
+        <view class="info-item">
+          场次:2025-12-23 21:00
+        </view>
+        <view class="info-item">
+          数量:{{ item.orderMovieItems?.length }}张
+        </view>
+        <view class="info-item">
+          总价:¥{{ item.orderMoney }}
+        </view>
+      </view>
+    </view>
+    <view class="btn-box">
+      <template v-if="item.hbOrderStatus === props.subPackOrder?.OrderStatus.PaddingPay">
+        <!-- 待支付 -->
+        <view
+          class="btn cancel"
+          custom-style="width: 128rpx;height: 38rpx;font-size: 24rpx;color: #AAAAAA;box-sizing: border-box;"
+          @click="handleCancel(item)"
+        >
+          取消订单
+        </view>
+        <view
+          class="btn"
+          custom-style="width: 128rpx;height: 38rpx;font-size: 24rpx;color: #222222;box-sizing: border-box"
+          @click="handlePay(item.orderNumber as string)"
+        >
+          付款
+        </view>
+      </template>
+      <wd-button
+        v-if="item.orderMovieItems[0].ticketCode"
+        custom-style="width: 188rpx;height: 44rpx;font-size: 28rpx;box-sizing: border-box"
+        @click="handleCopy(item.orderMovieItems[0].ticketCode as string)"
+      >
+        复制取票码
+      </wd-button>
+    </view>
+  </view>
+</template>
+
+<style lang="scss" scoped>
+.block {
+  background: #FFFFFF;
+  border-radius: 16rpx 16rpx 16rpx 16rpx;
+  padding: 24rpx;
+  margin-bottom: 20rpx;
+}
+
+  .order-item {
+    .top-box {
+      display: flex;
+      justify-content: space-between;
+      align-items: center;
+
+      .title {
+        font-size: 32rpx;
+        color: #222222;
+        font-weight: bold;
+      }
+
+      .status {
+        font-size: 28rpx;
+        color: #222222;
+      }
+    }
+
+    .status-box {
+      margin-top: 24rpx;
+      background: #F9F9F9;
+      border-radius: 16rpx 16rpx 16rpx 16rpx;
+      padding: 24rpx;
+
+      .reason-box {
+        display: flex;
+        align-items: center;
+
+        .icon {
+          width: 30rpx;
+          height: 30rpx;
+        }
+
+        .reason {
+          font-size: 28rpx;
+          color: #999999;
+          margin-left: 10rpx;
+        }
+      }
+
+      .info {
+        font-size: 24rpx;
+        color: #AAAAAA;
+        margin-top: 20rpx;
+        line-height: 44rpx;
+      }
+    }
+
+    .info-box {
+      margin-top: 20rpx;
+      display: flex;
+
+      .img {
+        width: 160rpx;
+        height: 160rpx;
+        border-radius: 16rpx 16rpx 16rpx 16rpx;
+        background: #999999;
+        margin-right: 20rpx;
+      }
+
+      .info {
+        display: flex;
+        flex-direction: column;
+        justify-content: space-between;
+
+        .info-item {
+          font-size: 24rpx;
+          color: #222222;
+        }
+      }
+    }
+
+    .btn-box {
+      display: flex;
+      align-items: center;
+      justify-content: flex-end;
+      margin-top: 20rpx;
+
+      .btn {
+        width: 128rpx;
+        height: 48rpx;
+        line-height: 48rpx;
+        text-align: center;
+        background: #FFFFFF;
+        border-radius: 24rpx 24rpx 24rpx 24rpx;
+        border: 1rpx solid #222222;
+        font-size: 24rpx;
+        color: #222222;
+      }
+
+      .btn.cancel {
+        color: #AAAAAA;
+        border: 1rpx solid #AAAAAA;
+        margin-right: 20rpx;
+      }
+    }
+  }
+</style>

+ 28 - 26
src/subPack-smqjh/order/components/OrderRenderer.vue

@@ -1,6 +1,7 @@
 <script setup lang="ts">
 import chargeList from '../../components/charge-orderList/charge-orderList.vue'
 import xsbList from '../../components/xsb-orderList/xsb-orderList.vue'
+import filmList from '../../components/film-orderList/film-orderList.vue'
 
 interface Props {
   orderList: Api.xsbOrderList[]
@@ -10,6 +11,7 @@ interface Props {
 }
 
 defineProps<Props>()
+
 defineEmits<{
   'cancel': [order: Api.xsbOrderList]
   'pay': [orderNumber: string]
@@ -18,45 +20,45 @@ defineEmits<{
   'after-sale': [item: Api.xsbOrderList]
   'refresh': []
 }>()
+
+const subPackFilmOrder = ref<typeof import('@/subPack-film/utils/order-data')>()
+const subPackFilmComfirm = ref<typeof import('@/subPack-film/utils/confirm-order')>()
+
+onMounted(async () => {
+  subPackFilmOrder.value = await AsyncImport('@/subPack-film/utils/order-data')
+  subPackFilmComfirm.value = await AsyncImport('@/subPack-film/utils/confirm-order')
+})
 </script>
 
 <template>
   <template v-if="navActiveTab === 'all'">
     <template v-for="order in orderList" :key="order.orderNumber">
       <xsbList
-        v-if="order.businessType === 'XSB'"
-        :order-list="[order]"
-        :sub-pack-order="subPackOrder"
-        :sub-pack-confirm="subPackConfirm"
-        @cancel="$emit('cancel', $event)"
-        @pay="$emit('pay', $event)"
-        @submit="$emit('submit', $event)"
-        @del="$emit('del', $event)"
-        @after-sale="$emit('after-sale', $event)"
+        v-if="order.businessType === 'XSB'" :order-list="[order]" :sub-pack-order="subPackOrder"
+        :sub-pack-confirm="subPackConfirm" @cancel="$emit('cancel', $event)" @pay="$emit('pay', $event)"
+        @submit="$emit('submit', $event)" @del="$emit('del', $event)" @after-sale="$emit('after-sale', $event)"
         @refresh="$emit('refresh')"
       />
-      <chargeList
-        v-else-if="order.businessType === 'CD'"
-        :order-list="[order]"
-        :sub-pack-order="subPackOrder"
+      <chargeList v-else-if="order.businessType === 'CD'" :order-list="[order]" :sub-pack-order="subPackOrder" />
+      <filmList
+        v-else-if="order.businessType === 'DYY'" :order-list="[order]" :sub-pack-order="subPackFilmOrder"
+        :sub-pack-confirm="subPackFilmComfirm" @cancel="$emit('cancel', $event)" @pay="$emit('pay', $event)"
+        @submit="$emit('submit', $event)" @del="$emit('del', $event)" @after-sale="$emit('after-sale', $event)"
+        @refresh="$emit('refresh')"
       />
     </template>
   </template>
   <xsbList
-    v-else-if="navActiveTab === 'XSB'"
-    :order-list="orderList"
-    :sub-pack-order="subPackOrder"
-    :sub-pack-confirm="subPackConfirm"
-    @cancel="$emit('cancel', $event)"
-    @pay="$emit('pay', $event)"
-    @submit="$emit('submit', $event)"
-    @del="$emit('del', $event)"
-    @after-sale="$emit('after-sale', $event)"
+    v-else-if="navActiveTab === 'XSB'" :order-list="orderList" :sub-pack-order="subPackOrder"
+    :sub-pack-confirm="subPackConfirm" @cancel="$emit('cancel', $event)" @pay="$emit('pay', $event)"
+    @submit="$emit('submit', $event)" @del="$emit('del', $event)" @after-sale="$emit('after-sale', $event)"
     @refresh="$emit('refresh')"
   />
-  <chargeList
-    v-else-if="navActiveTab === 'CD'"
-    :order-list="orderList"
-    :sub-pack-order="subPackOrder"
+  <chargeList v-else-if="navActiveTab === 'CD'" :order-list="orderList" :sub-pack-order="subPackOrder" />
+  <filmList
+    v-else-if="navActiveTab === 'DYY'" :order-list="orderList" :sub-pack-order="subPackFilmOrder"
+    :sub-pack-confirm="subPackFilmComfirm" @cancel="$emit('cancel', $event)" @pay="$emit('pay', $event)"
+    @submit="$emit('submit', $event)" @del="$emit('del', $event)" @after-sale="$emit('after-sale', $event)"
+    @refresh="$emit('refresh')"
   />
 </template>

+ 9 - 8
src/subPack-smqjh/order/index.vue

@@ -61,6 +61,7 @@ const currentOrderList = computed(() => {
   }
   return []
 })
+
 onMounted(async () => {
   subPackOrder.value = await AsyncImport('@/subPack-xsb/utils/order-data')
   subPackComfirm.value = await AsyncImport('@/subPack-xsb/utils/confirm-order')
@@ -98,7 +99,7 @@ async function handleAfterSale(item: Api.xsbOrderList) {
 
 <template>
   <view class="page-smqjh">
-    <view class="nav sticky top-0 z-10 bg-white px24rpx py18rpx">
+    <view class="nav sticky top-0 z-10 bg-white px-24rpx py-18rpx">
       <scroll-view
         scroll-x class="whitespace-nowrap" :scroll-into-view="`id-${scrollViewId}`"
         :scroll-into-view-offset="-150" scroll-with-animation enable-passive
@@ -106,22 +107,22 @@ async function handleAfterSale(item: Api.xsbOrderList) {
         <view class="flex items-center">
           <view
             v-for="item in navTabTypeList" :id="`id-${item.value}`" :key="item.value"
-            class="mr64rpx flex flex-col items-center whitespace-nowrap text-32rpx"
-            :class="[navActiveTab == item.value ? 'font-semibold ' : 'text-#AAAAAA']"
+            class="mr-64rpx flex flex-col items-center whitespace-nowrap text-32rpx"
+            :class="[navActiveTab == item.value ? 'font-semibold ' : 'text-[#AAAAAA]']"
             @click="handleChangeTypeNav(item.value)"
           >
             {{ item.name }}
             <view
-              class="mt10rpx bg-[var(--them-color)] transition-all transition-duration-400 ease-in"
-              :class="[navActiveTab == item.value ? 'w40rpx h8rpx rounded-4rpx' : '']"
+              class="mt-10rpx bg-[var(--them-color)] transition-all transition-duration-400 ease-in"
+              :class="[navActiveTab == item.value ? 'w-40rpx h-8rpx rounded-4rpx' : '']"
             />
           </view>
         </view>
       </scroll-view>
-      <view class="mt20rpx flex items-center">
+      <view class="mt-20rpx flex items-center">
         <view
           v-for="item in orderStatusList" :key="item.value"
-          class="mr16rpx rounded-24rpx bg-#F6F6F6 px16rpx py6rpx text-24rpx"
+          class="mr-16rpx rounded-24rpx bg-[#F6F6F6] px-16rpx py-6rpx text-24rpx"
           :class="[orderStatusActive == item.value ? 'bg-[var(--them-color)] text-white' : '']"
           @click="handleChangeStatus(item.value)"
         >
@@ -129,7 +130,7 @@ async function handleAfterSale(item: Api.xsbOrderList) {
         </view>
       </view>
     </view>
-    <view class="px24rpx">
+    <view class="px-24rpx">
       <OrderRenderer
         :order-list="currentOrderList"
         :nav-active-tab="navActiveTab"