order-detail.tsx 19 KB

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