order-detail.tsx 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538
  1. import OHeader from '@/components/o-header'
  2. import { computed, defineComponent, onMounted, reactive } from 'vue'
  3. import styles from './order-detail.module.less'
  4. // import Addres from './component/addres'
  5. import OSticky from '@/components/o-sticky'
  6. import { Button, Cell, CellGroup, Image, Popup, showToast, Tag } from 'vant'
  7. import Payment from '@/views/adapay/payment'
  8. import { useRoute, useRouter } from 'vue-router'
  9. // import OQrcode from '@/components/o-qrcode'
  10. import request from '@/helpers/request'
  11. import { state as baseState } from '@/state'
  12. import { browser, moneyFormat } from '@/helpers/utils'
  13. import OProtocol from '@/components/o-protocol'
  14. import OPopup from '@/components/o-popup'
  15. import UserAuth from './component/user-auth'
  16. import qs from 'query-string'
  17. import MemberBao from '../member-bao'
  18. import GoodsDetail from '../goods-detail'
  19. import ODialog from '@/components/o-dialog'
  20. import { orderStatus } from '@/constant'
  21. import QrcodePayment from './qrcode-payment'
  22. import { beforeSubmit } from './order-state'
  23. /**
  24. * 接入jsdk
  25. * 乐团报名-原生js支付
  26. * 会员购买-汇付
  27. */
  28. export default defineComponent({
  29. name: 'order-detail',
  30. setup() {
  31. const route = useRoute()
  32. const router = useRouter()
  33. const state = reactive({
  34. paymentType: 'adapay' as 'wxpay' | 'adapay', // 支付方式
  35. orderTimer: null as any,
  36. paymentStatus: false,
  37. showQrcode: false,
  38. qrCodeUrl: '',
  39. pay_channel: '',
  40. orderNo: route.query.orderNo,
  41. orderInfo: {} as any, // 订单信息
  42. goodsInfos: [] as any, // 订单信息列表
  43. config: route.query.config ? JSON.parse(route.query.config as any) : {},
  44. hasFreight: route.query.hf ? false : true, // 是否显示
  45. freight: '', // 运费
  46. agreeStatus: true, //是否勾选协议
  47. showHeader: false,
  48. authShow: false, // 是否进行实名认证
  49. memberBaoStatus: false, // 团练宝详情状态
  50. goodsStatus: false, //
  51. selectGoodsId: null as any,
  52. currentPrice: 0,
  53. dialogStatus: false,
  54. dialogMessage: '',
  55. dialogType: '',
  56. submitStatus: false
  57. })
  58. const orderType = computed(() => {
  59. return state.orderInfo.orderType
  60. })
  61. // const addressDetails = ref<any>({})
  62. const getOrderDetails = async () => {
  63. try {
  64. const { data } = await request.get('/api-student/userPaymentOrder/detail/' + state.orderNo)
  65. const goodsInfos = data.goodsInfos || []
  66. state.orderInfo = data
  67. let hasInstrument = false // 是否有乐器
  68. let hasTextbook = false // 是否购买教材
  69. goodsInfos.forEach((item: any) => {
  70. const img = item.goodsUrl ? item.goodsUrl.split(',')[0] : ''
  71. item.goodsUrl = img
  72. if (item.goodsType === 'INSTRUMENTS') {
  73. hasInstrument = true
  74. } else if (item.goodsType === 'TEXTBOOK') {
  75. hasTextbook = true
  76. }
  77. })
  78. state.goodsInfos = goodsInfos
  79. // if (!addressDetails.value.id) {
  80. // addressDetails.value = data.addresses || {}
  81. // }
  82. // 判断运费状态
  83. // 如果没有购买商品,有购买教材则『到付』 其它则免运费
  84. // console.log(hasInstrument, hasTextbook)
  85. if (!hasInstrument && hasTextbook) {
  86. state.freight = '到付'
  87. } else {
  88. state.freight = '免运费'
  89. }
  90. // 判断订单状态,如果不是待支付则返回
  91. // WAIT_PAY: '待支付',
  92. // PAYING: '支付中',
  93. // PAID: '已付款',
  94. // TIMEOUT: '订单超时',
  95. // FAIL: '支付失败',
  96. // CLOSED: '订单关闭',
  97. // REFUNDING: '退款中',
  98. // REFUNDED: '已退款'
  99. if (data.status !== 'WAIT_PAY' && data.status !== 'PAYING') {
  100. // status
  101. state.dialogStatus = true
  102. state.dialogMessage = '订单' + orderStatus[data.status]
  103. state.dialogType = data.status
  104. }
  105. } catch {
  106. //
  107. }
  108. }
  109. const onConfirm = (val: any) => {
  110. const config: any = state.config
  111. state.pay_channel = val.pay_channel
  112. const params = qs.stringify({
  113. pay_channel: val.pay_channel,
  114. wxAppId: config.wxAppId,
  115. alipayAppId: config.alipayAppId,
  116. body: config.body,
  117. price: config.price,
  118. paymentType: config.paymentType,
  119. orderNo: config.merOrderNo,
  120. userId: config.userId
  121. })
  122. if (val.payCode === 'payResult') {
  123. window.location.href = window.location.origin + '/orchestra-student/#/payResult?' + params
  124. } else {
  125. state.qrCodeUrl = window.location.origin + '/orchestra-student/#/payDefine?' + params
  126. state.showQrcode = true
  127. state.paymentStatus = false
  128. setTimeout(() => {
  129. getPaymentOrderStatus()
  130. }, 300)
  131. }
  132. }
  133. // 轮询查询订单状态
  134. const getPaymentOrderStatus = async () => {
  135. // 循环查询订单
  136. // const orderNo = state.orderNo
  137. const orderTimer = setInterval(async () => {
  138. // 判断是否在当前路由,如果不是则清除定时器
  139. if (route.name != 'orderDetail') {
  140. clearInterval(orderTimer)
  141. return
  142. }
  143. state.orderTimer = orderTimer
  144. try {
  145. const { data } = await request.post(
  146. '/api-student/open/userOrder/paymentStatus/' + state.orderNo,
  147. {
  148. hideLoading: true
  149. }
  150. )
  151. // console.log(data)
  152. if (data.status !== 'WAIT_PAY' && data.status !== 'PAYING') {
  153. // 默认关闭支付二维码弹窗
  154. state.showQrcode = false
  155. clearInterval(state.orderTimer)
  156. // window.location.replace(
  157. // window.location.origin + '/#/payment-result?orderNo=' + state.orderNo
  158. // )
  159. setTimeout(() => {
  160. checkOrderTypeJump()
  161. }, 100)
  162. }
  163. } catch {
  164. //
  165. clearInterval(state.orderTimer)
  166. }
  167. }, 5000)
  168. }
  169. // 确定支付
  170. const onSubmit = async () => {
  171. clearInterval(state.orderTimer)
  172. if (orderType.value === 'VIP') {
  173. buyVip(onCallback)
  174. } else {
  175. buyOrchestra(onCallback)
  176. }
  177. }
  178. /**
  179. * @description 回调,判断是否有支付渠道,如果有则直接去支付
  180. * @returns void
  181. */
  182. const onCallback = () => {
  183. const pt = state.pay_channel
  184. // 判断是否有支付方式
  185. if (pt) {
  186. const payCode: string = beforeSubmit(state.pay_channel)
  187. onConfirm({
  188. payCode,
  189. pay_channel: pt
  190. })
  191. } else {
  192. if (orderType.value === 'VIP') {
  193. state.paymentStatus = true
  194. } else {
  195. if (browser().isApp) {
  196. state.paymentStatus = true
  197. } else {
  198. // 直接去拉取微信支付
  199. onConfirm({
  200. payCode: 'payResult',
  201. pay_channel: 'wx_pub'
  202. })
  203. }
  204. }
  205. }
  206. }
  207. /**
  208. * @description 会员购买
  209. * @param callback 回调方式
  210. */
  211. const buyVip = async (callback?: any) => {
  212. try {
  213. state.submitStatus = true
  214. const { data } = await request.get(
  215. '/api-student/userPaymentOrder/detail/' + state.orderNo,
  216. {
  217. hideLoading: false
  218. }
  219. )
  220. state.pay_channel = data.paymentChannel
  221. state.submitStatus = false
  222. if (data.status !== 'WAIT_PAY' && data.status !== 'PAYING') {
  223. router.replace({
  224. path: '/payment-result',
  225. query: {
  226. orderNo: state.orderNo
  227. }
  228. })
  229. } else {
  230. callback && callback()
  231. }
  232. } catch {
  233. //
  234. state.submitStatus = false
  235. }
  236. }
  237. /**
  238. * @description 乐团报名 - 汇付
  239. * @param callback 回调方式
  240. */
  241. const buyOrchestra = async (callback: any) => {
  242. // 请选择收货地址
  243. // if (!addressDetails.value.id) {
  244. // showToast('请选择收货地址')
  245. // return
  246. // }
  247. if (!state.agreeStatus) {
  248. showToast('请先阅读并同意《管乐团平台服务协议》')
  249. return
  250. }
  251. const users = baseState.user.data
  252. // console.group(users)
  253. // 判断是否需要实名认证, 姓名,卡号
  254. if (!users?.account.realName || !users?.account.idCardNo) {
  255. state.authShow = true
  256. return
  257. }
  258. state.submitStatus = true
  259. try {
  260. const { data } = await request.post('/api-student/userPaymentOrder/updateReceiveAddress', {
  261. hideLoading: false,
  262. data: {
  263. orderNo: state.orderNo,
  264. orderType: 'ORCHESTRA',
  265. receiveAddress: null
  266. }
  267. })
  268. if (data.status == 'CLOSED') {
  269. state.dialogStatus = true
  270. state.dialogMessage = '订单已关闭'
  271. state.dialogType = 'CLOSED'
  272. } else if (data.status !== 'WAIT_PAY' && data.status !== 'PAYING') {
  273. checkOrderTypeJump()
  274. } else {
  275. callback && callback()
  276. }
  277. } catch {
  278. //
  279. } finally {
  280. state.submitStatus = false
  281. }
  282. }
  283. // 有支付结果后回调
  284. const checkOrderTypeJump = () => {
  285. // 判断是否是乐团报名 并且在不在app里面
  286. if (orderType.value === 'ORCHESTRA' && !browser().isApp) {
  287. if (state.dialogType === 'CLOSED') {
  288. router.back()
  289. } else {
  290. window.location.replace(
  291. 'https://mp.weixin.qq.com/s?__biz=MzkxMDMwOTI5Nw==&mid=2247485362&idx=3&sn=9b265d36b5dabe7f9393fc679c367540&chksm=c12c256cf65bac7ae2a865435b950f6e1285afd226356db0ffde815b1ee345f29cfcdb798cc9#rd'
  292. )
  293. }
  294. } else {
  295. router.replace({
  296. path: '/payment-result',
  297. query: {
  298. orderNo: state.orderNo
  299. }
  300. })
  301. }
  302. }
  303. // 放弃支付时,则取消订单
  304. const onBackOut = async () => {
  305. try {
  306. await request.post('/api-student/userPaymentOrder/cancelPayment/' + state.orderNo)
  307. router.back()
  308. } catch {
  309. //
  310. }
  311. }
  312. // 实名认证成功
  313. const onAuthSuccess = () => {
  314. //
  315. state.authShow = false
  316. onSubmit() // 实名成功后自动支付
  317. }
  318. onMounted(() => {
  319. if (browser().isApp) {
  320. state.showHeader = true
  321. } else {
  322. state.showHeader = false
  323. }
  324. // 获取收货地址
  325. // let details = sessionStorage.getItem('addressDetails')
  326. // details = details ? JSON.parse(details) : {}
  327. // addressDetails.value = details
  328. // sessionStorage.removeItem('addressDetails')
  329. getOrderDetails()
  330. })
  331. return () => (
  332. <>
  333. {browser().isApp && <OHeader border={false} />}
  334. <div class={styles.cartConfirm}>
  335. {/* 只有乐团购买的时候显示收货地址 */}
  336. {/* {orderType.value === 'ORCHESTRA' && (
  337. <div class={styles.cartConfirmBox}>
  338. <Addres item={addressDetails.value} />
  339. </div>
  340. )} */}
  341. <CellGroup style={{ margin: 0 }} border={false}>
  342. {state.goodsInfos &&
  343. state.goodsInfos.map((goods: any) => (
  344. <Cell
  345. class={styles.cellItem}
  346. // center
  347. onClick={() => {
  348. if (goods.goodsType === 'INSTRUMENTS' || goods.goodsType === 'TEXTBOOK') {
  349. // console.log(goods)
  350. state.selectGoodsId = goods.goodsId
  351. state.currentPrice = goods.currentPrice
  352. state.goodsStatus = true
  353. } else if (goods.goodsType === 'VIP') {
  354. state.memberBaoStatus = true
  355. }
  356. }}
  357. >
  358. {{
  359. icon: () => <Image class={styles.img} src={goods.goodsUrl} />,
  360. title: () => (
  361. <div class={styles.goodsContent}>
  362. <h2>
  363. <span>{goods.goodsName}</span>
  364. {/* <span class={styles.goodsNum}>
  365. {goods.goodsType === 'VIP' ? '6个月' : 'x 1'}
  366. </span> */}
  367. <span
  368. class={[
  369. styles.goodsNums,
  370. goods.paymentCashAmount > 0 ? styles.numFont : styles.free
  371. ]}
  372. >
  373. {goods.paymentCashAmount > 0 ? (
  374. <>
  375. <span class={styles.numPrefix}>¥ </span>
  376. {moneyFormat(goods.paymentCashAmount)}
  377. </>
  378. ) : state.orderInfo.orchestraRegisterType === 'GROUP_BUY' &&
  379. goods.goodsType === 'INSTRUMENTS' ? (
  380. '免费提供'
  381. ) : (
  382. '免费'
  383. )}
  384. </span>
  385. </h2>
  386. <div class={styles.goodsPrice}>
  387. {/* {goods.goodsType === 'INSTRUMENT_INSPECT' ? (
  388. <span>1-2次/学期</span>
  389. ) : (
  390. <Tag
  391. color="linear-gradient(135deg, #FF8C4A 0%, #FF531C 100%)"
  392. textColor="#fff"
  393. class={styles.brandName}
  394. >
  395. {goods.brandName}
  396. </Tag>
  397. )} */}
  398. {goods.goodsType === 'INSTRUMENT_INSPECT' ? (
  399. <span class={[styles.model, 'van-multi-ellipsis--l2']}>1-2次/学期</span>
  400. ) : (
  401. <span class={[styles.model, 'van-multi-ellipsis--l2']}>
  402. {goods.description}
  403. </span>
  404. )}
  405. <span class={styles.goodsNum}>
  406. {goods.goodsType === 'VIP' ? '6个月' : 'x 1'}
  407. </span>
  408. </div>
  409. {/* <p class={styles.model}>{goods.description}</p> */}
  410. </div>
  411. )
  412. }}
  413. </Cell>
  414. ))}
  415. </CellGroup>
  416. {/* {orderType.value === 'ORCHESTRA' && (
  417. <Cell class={styles.freight} title="运费" value={state.freight}></Cell>
  418. )} */}
  419. </div>
  420. <OSticky position="bottom" background="white-only">
  421. <div class={styles.protocol}>
  422. <OProtocol
  423. v-model:modelValue={state.agreeStatus}
  424. showHeader={state.showHeader}
  425. style={{ paddingTop: 0, paddingBottom: 0 }}
  426. />
  427. </div>
  428. <div class={styles.paymentContainer}>
  429. <div class={styles.payemntPrice}>
  430. <p class={styles.needPrice}>
  431. 支付金额:<span>¥ {moneyFormat(state.orderInfo.currentPrice)}</span>
  432. </p>
  433. </div>
  434. <div class={styles.paymentBtn}>
  435. <Button
  436. color="linear-gradient(135deg, #FF8C4A 0%, #FF531C 100%)"
  437. round
  438. onClick={onSubmit}
  439. loading={state.submitStatus}
  440. disabled={state.submitStatus}
  441. >
  442. 立即购买
  443. </Button>
  444. </div>
  445. </div>
  446. </OSticky>
  447. <Popup
  448. show={state.paymentStatus}
  449. closeOnClickOverlay={false}
  450. position="bottom"
  451. round
  452. closeOnPopstate
  453. safeAreaInsetBottom
  454. style={{ minHeight: '30%' }}
  455. >
  456. <Payment
  457. paymentConfig={state.orderInfo}
  458. onClose={() => (state.paymentStatus = false)}
  459. onBackOut={onBackOut}
  460. onConfirm={(val: any) => onConfirm(val)}
  461. />
  462. </Popup>
  463. <OPopup
  464. v-model:modelValue={state.showQrcode}
  465. onClose={() => {
  466. // 二维码关闭时清除定时器
  467. clearInterval(state.orderTimer)
  468. }}
  469. >
  470. <QrcodePayment
  471. url={state.qrCodeUrl}
  472. pay_channel={state.pay_channel}
  473. orderType={orderType.value}
  474. />
  475. </OPopup>
  476. <OPopup v-model:modelValue={state.authShow}>
  477. <UserAuth onSuccess={onAuthSuccess} hideHeader={!browser().isApp} />
  478. </OPopup>
  479. <OPopup v-model:modelValue={state.memberBaoStatus} position="right">
  480. <MemberBao />
  481. </OPopup>
  482. <OPopup v-model:modelValue={state.goodsStatus} position="right" destroy>
  483. {state.goodsStatus && (
  484. <GoodsDetail id={state.selectGoodsId} groupPrice={state.currentPrice} />
  485. )}
  486. </OPopup>
  487. <ODialog
  488. title="提示"
  489. v-model:show={state.dialogStatus}
  490. message={state.dialogMessage}
  491. confirmButtonText="确定"
  492. onConfirm={() => {
  493. checkOrderTypeJump()
  494. // if (state.dialogType === 'CLOSED') {
  495. // } else {
  496. // checkOrderTypeJump()
  497. // }
  498. }}
  499. />
  500. </>
  501. )
  502. }
  503. })