index.vue 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588
  1. <script setup lang="ts">
  2. import { getArrayFieldMax, isWithin45Minutes, timeFormat } from '../utils/index'
  3. import router from '@/router'
  4. import { StaticUrl } from '@/config'
  5. definePage({
  6. name: 'film-submit-order',
  7. islogin: false,
  8. style: {
  9. navigationBarTitleText: '确认订单',
  10. backgroundColorBottom: '#fff',
  11. },
  12. })
  13. // const isDifferent = computed(() => {
  14. // return info.choose
  15. // })
  16. const showProtocol = ref(true)
  17. const isAgree = ref(false)
  18. const loading = ref(false)
  19. const { userInfo } = storeToRefs(useUserStore())
  20. const query = ref({
  21. memberId: unref(userInfo).id,
  22. channelId: unref(userInfo).channelId,
  23. shopId: '1',
  24. channelName: unref(userInfo).channelName,
  25. cinemaName: '',
  26. cinemaCode: '',
  27. movieCode: '',
  28. hallName: '',
  29. orderPayMode: '1',
  30. originPrice: 0,
  31. seatNames: '',
  32. sessionBeginTime: '',
  33. switchSeat: false,
  34. movieOrderItems: [] as Array<{ name: string, price: number }>,
  35. session: '',
  36. postImageUrl: '',
  37. movieName: '',
  38. fastTicket: false,
  39. })
  40. const info = ref({
  41. cinemaName: '',
  42. totalPrice: 0,
  43. phone: '',
  44. points: 0,
  45. language: '',
  46. planType: '',
  47. showTime: '',
  48. hallName: '',
  49. chooseSeatList: [] as Array<{ seatName: string, ticketPrice: number, fastPrice: number, areaId: string }>,
  50. })
  51. const isDiffrent = computed(() => {
  52. let flag = false
  53. const areaId = info.value.chooseSeatList[0].areaId
  54. for (const item of info.value.chooseSeatList) {
  55. if (item.areaId !== areaId) {
  56. flag = true
  57. break
  58. }
  59. }
  60. return flag
  61. })
  62. const totalPrice = computed(() => {
  63. let total = 0
  64. const scale = 10000
  65. if (!info.value.showTime)
  66. return 0
  67. const key = isWithin45Minutes(new Date(), info.value.showTime) ? 'fastPrice' : 'ticketPrice'
  68. const price = getArrayFieldMax(info.value.chooseSeatList, key) || 0
  69. total = (price * scale) * info.value.chooseSeatList.length / scale
  70. return total
  71. })
  72. function back() {
  73. showProtocol.value = false
  74. router.back()
  75. }
  76. function next() {
  77. if (!isAgree.value) {
  78. return useGlobalToast().show('请先勾选协议')
  79. }
  80. showProtocol.value = false
  81. }
  82. async function pay() {
  83. loading.value = true
  84. try {
  85. query.value.fastTicket = isWithin45Minutes(new Date(), info.value.showTime)
  86. query.value.movieOrderItems = info.value.chooseSeatList.map((item: any) => {
  87. if (isWithin45Minutes(new Date(), info.value.showTime)) { // 如果在45分钟内,快速出票
  88. return {
  89. name: item.seatName,
  90. price: item.fastPrice,
  91. }
  92. }
  93. else {
  94. return {
  95. name: item.seatName,
  96. price: item.ticketPrice,
  97. }
  98. }
  99. })
  100. const { data: orderNumber } = await Apis.film.addFilmOrder({ data: query.value })
  101. const payMent = await useUserStore().getPayMent(orderNumber)
  102. if (payMent.payType !== 'point') {
  103. try {
  104. // #ifdef MP-WEIXIN
  105. const res = await useUserStore().handleCommonPayMent(orderNumber)
  106. await useUserStore().getWxCommonPayment(res)
  107. await useUserStore().paySuccess('film-order', 'subPack-film/index/index')
  108. // #endif
  109. // #ifdef H5
  110. useUserStore().handleCommonWechatPay(orderNumber)
  111. await handleH5PayResult(orderNumber)
  112. // #endif
  113. }
  114. catch {
  115. await useUserStore().payError('film-order', 'subPack-film/index/index')
  116. }
  117. }
  118. else {
  119. await useUserStore().handleCommonPayMent(orderNumber)
  120. await useUserStore().paySuccess('film-order', 'subPack-film/index/index')
  121. }
  122. }
  123. catch (error) {
  124. console.error('支付失败:', error)
  125. }
  126. finally {
  127. loading.value = false
  128. }
  129. }
  130. async function handleH5PayResult(orderNumber: string) {
  131. const isPaySuccess = await useUserStore().pollOrderPaySuccess(orderNumber)
  132. if (isPaySuccess) {
  133. await useUserStore().paySuccess('film-order', 'subPack-film/index/index')
  134. return
  135. }
  136. useGlobalToast().show({ msg: '暂未查询到支付成功,请稍后在订单列表查看' })
  137. }
  138. function call() {
  139. uni.makePhoneCall({
  140. phoneNumber: info.value.phone,
  141. })
  142. }
  143. async function getPoints() {
  144. const res = await Apis.xsb.findUserPoints({})
  145. if (res.data) {
  146. info.value.points = res.data.availablePointsTotal as number
  147. }
  148. else {
  149. info.value.points = 0
  150. }
  151. console.log(11111111, info.value.points)
  152. }
  153. getPoints()
  154. function usePoints() {
  155. return (totalPrice.value * 100) >= info.value.points ? info.value.points : ((totalPrice.value * 10000) / 100)
  156. }
  157. onLoad((options) => {
  158. query.value = JSON.parse(options?.query)
  159. info.value = uni.getStorageSync('film-info')
  160. console.log(11111, info.value.chooseSeatList)
  161. })
  162. </script>
  163. <template>
  164. <view class="film-submit-order">
  165. <!-- 影片信息 -->
  166. <view class="movie-info-box block">
  167. <view class="movie-info">
  168. <image class="img" :src="query.postImageUrl" />
  169. <view class="title-box">
  170. <view class="title">
  171. {{ query.movieName }}
  172. </view>
  173. <view class="time-box">
  174. {{ timeFormat(info.showTime) }} {{ info.language }} {{ info.planType }}
  175. </view>
  176. </view>
  177. </view>
  178. <view class="phone-box">
  179. <view class="notice-box">
  180. <image class="icon" :src="`${StaticUrl}/film-error.png`" mode="scaleToFill" />
  181. <view class="text">
  182. 不支持线上改签、退款,具体请咨询商家。
  183. </view>
  184. </view>
  185. <image class="phone" :src="`${StaticUrl}/film-phone.png`" mode="scaleToFill" @click="call" />
  186. </view>
  187. </view>
  188. <!-- 座位信息 -->
  189. <view class="seat-info block">
  190. <view class="sub-title">
  191. {{ info.cinemaName }}
  192. </view>
  193. <view class="room-num">
  194. {{ query.hallName }}
  195. </view>
  196. <!-- <view class="area-price">
  197. 普通区¥29.9
  198. </view> -->
  199. <view class="seat-list">
  200. <view v-for="(item, index) in info.chooseSeatList" :key="index" class="item">
  201. <view class="label">
  202. {{ item.seatName }}
  203. </view>
  204. <view class="value">
  205. ¥{{ isWithin45Minutes(new Date(), info.showTime) ? item.fastPrice : item.ticketPrice }}
  206. </view>
  207. </view>
  208. </view>
  209. <!--
  210. <view class="area-price">
  211. 贵宾区¥39.9
  212. </view>
  213. <view class="seat-list">
  214. <view class="item">
  215. <view class="label">
  216. 2排11座
  217. </view>
  218. <view class="value">
  219. ¥29.9
  220. </view>
  221. </view>
  222. <view class="item">
  223. <view class="label">
  224. 2排11座
  225. </view>
  226. <view class="value">
  227. ¥29.9
  228. </view>
  229. </view>
  230. </view> -->
  231. </view>
  232. <!-- 价格信息 -->
  233. <view class="goods-price-box block">
  234. <view class="item">
  235. <view class="label">
  236. 商品金额
  237. </view>
  238. <view class="value">
  239. ¥{{ totalPrice || info.totalPrice }}
  240. </view>
  241. </view>
  242. <view v-if="isDiffrent" class="notice">
  243. 座位跨区,按最高价格计算。订单总价=最高座位价格×座位数
  244. </view>
  245. <view class="item">
  246. <view class="label">
  247. 积分({{ usePoints() }})
  248. </view>
  249. <view class="value price">
  250. -¥{{ usePoints() / 100 }}
  251. </view>
  252. </view>
  253. <!-- <view class="item">
  254. <view class="label">
  255. 平台券
  256. </view>
  257. <view class="value price">
  258. -¥14
  259. </view>
  260. </view> -->
  261. <view class="line" />
  262. <view class="total-box">
  263. <view class="text">
  264. 总计
  265. </view>
  266. <view class="total">
  267. ¥{{ (totalPrice * 100 - usePoints()) / 100 }}
  268. </view>
  269. </view>
  270. </view>
  271. <!-- 购票须知 -->
  272. <view class="notice-box block">
  273. <view class="sub-title">
  274. 购票须知
  275. </view>
  276. <view class="content-text">
  277. 1.请提前30分钟左右到达影院现场,通过影院自助取票机完成
  278. 取票。
  279. 2.若取票过程中遇到无法取票等其它问题,请联系影院工作人
  280. 员进行处理。
  281. 3.请及时关注电影开场时间,凭票有序检票入场。
  282. 4.如需开具电影票发票,可联系影院工作人员凭当日票根进行
  283. 开具,若遇到特殊情况请及时联系猫眼客服人员。
  284. 5.退票、改签服务请参考影院具体政策要求,特殊场次及部分
  285. 使用卡、券场次订单可能不支持此服务。
  286. 6.由于设备故障等不可抗力因素,存在少量影院调整/取消场次
  287. 的情况,系统将会自动退票退款。
  288. 7.由于影院系统不稳定等因素,存在少量出票失败的情况系统
  289. 将会自动退款请注意查收。
  290. 8.如有其他问题可联系在线客服,工作时间:9:00-22:00。
  291. </view>
  292. </view>
  293. <!-- 底部 -->
  294. <view class="footer-box">
  295. <view class="price-box">
  296. <view class="total">
  297. ¥{{ (totalPrice * 100 - usePoints()) / 100 }}
  298. </view>
  299. <view class="reduce">
  300. 共减¥{{ usePoints() / 100 }}
  301. </view>
  302. </view>
  303. <wd-button
  304. custom-style="width: 180rpx;height: 80rpx;background: #9ED605;border-radius: 40rpx 40rpx 40rpx 40rpx;" :loading="loading" @click="pay"
  305. >
  306. 立即支付
  307. </wd-button>
  308. </view>
  309. <!-- 协议弹窗 -->
  310. <wd-popup v-model="showProtocol" :close-on-click-modal="false" custom-style="border-radius:16rpx;">
  311. <view class="popup-box">
  312. <view class="title">
  313. 退改签协议
  314. </view>
  315. <view class="content-text">
  316. 用户点击同意本协议之前,请务必认真阅读完全理解本协议中
  317. 全部条款,特别是其中与用户权益有或可能具有重大关系的条
  318. 款(包括但不限于第1.2条、第1.3条、第2条、第3.2条)。当用
  319. 户按照页面提示阅读、点击确认同意本协议及完成支付购票时,
  320. 即表示用户已经充分阅读、理解并接受本协议的全部内容。如
  321. 用户不同意接受本协议的任何条款,或无法理解本协议相关条
  322. 款含义的,请不要进行后续操用户点击同意本协议之前,请务
  323. 必认真阅读完全理解木协议中全部条款 特别是其中与用户权
  324. 益有或可能,用户点击同意本协议之前,请务必认真阅读
  325. </view>
  326. <view class="radio-box">
  327. <wd-checkbox v-model="isAgree">
  328. 我已阅读并同意以上协议
  329. </wd-checkbox>
  330. </view>
  331. <view class="btn-box">
  332. <wd-button custom-class="btn" @click="next">
  333. 继续购票
  334. </wd-button>
  335. <wd-button custom-class="btn" type="text" @click="back">
  336. 暂不购票
  337. </wd-button>
  338. </view>
  339. </view>
  340. </wd-popup>
  341. </view>
  342. </template>
  343. <style lang="scss" scoped>
  344. .film-submit-order{
  345. padding: 20rpx 24rpx 300rpx;
  346. background: #F9F9F9;
  347. .block {
  348. background: #FFFFFF;
  349. border-radius: 16rpx 16rpx 16rpx 16rpx;
  350. padding: 24rpx;
  351. margin-bottom: 20rpx;
  352. }
  353. .sub-title {
  354. font-weight: bold;
  355. font-size: 32rpx;
  356. color: #222222;
  357. }
  358. .item {
  359. display: flex;
  360. justify-content: space-between;
  361. align-items: center;
  362. margin-top: 20rpx;
  363. .label {
  364. font-size: 28rpx;
  365. color: #222222;
  366. }
  367. .label.gray {
  368. color: #AAAAAA;
  369. }
  370. .value {
  371. font-size: 28rpx;
  372. color: #222222;
  373. }
  374. .value.price {
  375. color: #FF4A39;
  376. }
  377. }
  378. .item:first-child {
  379. margin-top: 0 !important;
  380. }
  381. .movie-info {
  382. display: flex;
  383. align-items: center;
  384. .img {
  385. width: 152rpx;
  386. height: 208rpx;
  387. margin-right: 24rpx;
  388. border-radius: 16rpx;
  389. vertical-align: bottom;
  390. }
  391. .title-box {
  392. display: flex;
  393. flex-direction: column;
  394. justify-content: center;
  395. .title {
  396. font-weight: bold;
  397. font-size: 32rpx;
  398. color: #222222;
  399. margin-bottom: 20rpx;
  400. }
  401. .time-box {
  402. font-size: 24rpx;
  403. color: #AAAAAA;
  404. }
  405. }
  406. }
  407. .phone-box{
  408. display: flex;
  409. justify-content: space-between;
  410. align-items: center;
  411. margin-top: 24rpx;
  412. .notice-box{
  413. display: flex;
  414. align-items: center;
  415. .icon{
  416. width: 32rpx;
  417. height: 32rpx;
  418. margin-right: 16rpx;
  419. }
  420. .text{
  421. font-size: 24rpx;
  422. color: #222222;
  423. }
  424. }
  425. .phone{
  426. width: 40rpx;
  427. height: 40rpx;
  428. }
  429. }
  430. .seat-info {
  431. .room-num {
  432. font-size: 28rpx;
  433. color: #222222;
  434. margin: 20rpx 0;
  435. }
  436. .area-price {
  437. font-size: 24rpx;
  438. color: #AAAAAA;
  439. margin: 20rpx 0;
  440. }
  441. .seat-list {}
  442. }
  443. .goods-price-box {
  444. .notice {
  445. font-size: 24rpx;
  446. color: #AAAAAA;
  447. margin-top: 16rpx;
  448. }
  449. .line {
  450. height: 2rpx;
  451. background: #F0F0F0;
  452. margin-top: 20rpx;
  453. }
  454. .total-box {
  455. display: flex;
  456. justify-content: space-between;
  457. align-items: center;
  458. margin-top: 30rpx;
  459. .text {
  460. font-weight: bold;
  461. font-size: 32rpx;
  462. color: #222222;
  463. }
  464. .total {
  465. font-weight: bold;
  466. font-size: 32rpx;
  467. color: #FF4A39;
  468. }
  469. }
  470. }
  471. .notice-box{
  472. .content-text{
  473. font-size: 24rpx;
  474. color: #222222;
  475. line-height: 42rpx;
  476. margin-top: 24rpx;
  477. }
  478. }
  479. .footer-box{
  480. position: fixed;
  481. bottom: 0;
  482. left: 0;
  483. box-sizing: border-box;
  484. padding: 12rpx 24rpx 76rpx;
  485. background: #fff;
  486. width: 100%;
  487. display: flex;
  488. justify-content: space-between;
  489. align-items: center;
  490. border-top: 1rpx solid #EEEEEE;
  491. .price-box{
  492. display: flex;
  493. align-items: center;
  494. .total{
  495. font-weight: bold;
  496. font-size: 40rpx;
  497. color: #FF4A39;
  498. }
  499. .reduce{
  500. font-size: 24rpx;
  501. color: #FF4A39;
  502. margin-left: 20rpx;
  503. }
  504. }
  505. }
  506. }
  507. .btn-box::v-deep .btn {
  508. width: 654rpx;
  509. height: 80rpx;
  510. margin-top: 24rpx;
  511. }
  512. .popup-box{
  513. width: 702rpx;
  514. height: 826rpx;
  515. background: #FFFFFF;
  516. border-radius: 16rpx 16rpx 16rpx 16rpx;
  517. padding: 28rpx 24rpx;
  518. box-sizing: border-box;
  519. .title{
  520. font-weight: bold;
  521. font-size: 28rpx;
  522. color: #222222;
  523. text-align: center;
  524. }
  525. .content-text{
  526. height: 500rpx;
  527. overflow-y: auto;
  528. font-size: 24rpx;
  529. color: #222222;
  530. line-height: 42rpx;
  531. margin-top: 24rpx;
  532. }
  533. .radio-box{
  534. margin-top: 24rpx;
  535. display: flex;
  536. align-items: center;
  537. justify-content: center;
  538. }
  539. .btn-box{
  540. display: flex;
  541. flex-direction: column;
  542. }
  543. }
  544. </style>