orderDetail.vue 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504
  1. <route lang="json5" type="page">
  2. {
  3. layout: 'default',
  4. style: {
  5. navigationBarTitleText: '%orderDetail.title%',
  6. navigationBarBackgroundColor: '#fff',
  7. },
  8. }
  9. </route>
  10. <script lang="ts" setup>
  11. import { getConfigByCode } from '@/api/common'
  12. import { orderCancel, orderDetail, orderPink, orderStatusEnum, payOrder } from '@/api/order'
  13. import { getWalletAccountInfo } from '@/api/wallet'
  14. import DialogBox from '@/components/DialogBox/DialogBox.vue'
  15. import { DialogUtils } from '@/components/DialogBox/utils'
  16. import { t } from '@/locale'
  17. import { formatNumber } from '@/utils'
  18. import { getPageParams, toPage } from '@/utils/page'
  19. import { toast } from '@/utils/toast'
  20. defineOptions({
  21. name: 'OrderDetail', // 订单详情
  22. })
  23. // z-paging
  24. const paging = ref(null)
  25. const id = ref<any>()
  26. const isPayOrder = ref<boolean>(false)
  27. const detail = ref<any>({})
  28. const orderStatusEnumData = ref<any>([])
  29. const openRedEnvelopeRate = ref<any>()
  30. const joinRedEnvelopeRate = ref<any>()
  31. const countdown = ref('00:00')
  32. const timer = ref()
  33. // DialogBox 函数式调用配置
  34. const dialogConfig = ref<any>({})
  35. const dialogType = ref<'cancel' | 'pay' | 'recharge' | ''>('')
  36. async function getConfig(code: string) {
  37. try {
  38. const res = await getConfigByCode({ code })
  39. if (res.code === '200') {
  40. switch (code) {
  41. case 'open_red_envelope_rate':
  42. openRedEnvelopeRate.value = res.data.valueInfo
  43. break
  44. case 'join_red_envelope_rate':
  45. joinRedEnvelopeRate.value = res.data.valueInfo
  46. break
  47. default:
  48. break
  49. }
  50. }
  51. }
  52. catch {
  53. }
  54. }
  55. async function getOrderStatus() {
  56. try {
  57. const res = await orderStatusEnum({ id: 1 })
  58. if (res.code === '200') {
  59. orderStatusEnumData.value = res.data
  60. }
  61. }
  62. catch {
  63. }
  64. }
  65. function startCountdown() {
  66. // 清除之前的定时器
  67. if (timer.value)
  68. clearInterval(timer.value)
  69. if (!detail.value?.createTime || detail.value?.status !== 1) {
  70. countdown.value = '00:00'
  71. return
  72. }
  73. // 计算过期时间(创建时间 + 20分钟)
  74. const createTime = new Date(detail.value.createTime).getTime()
  75. const expireTime = createTime + 20 * 60 * 1000
  76. timer.value = setInterval(() => {
  77. const now = Date.now()
  78. const diff = expireTime - now
  79. if (diff <= 0) {
  80. clearInterval(timer.value)
  81. countdown.value = '00:00'
  82. getDetail() // 刷新订单状态
  83. return
  84. }
  85. const minutes = Math.floor(diff / 1000 / 60)
  86. const seconds = Math.floor((diff / 1000) % 60)
  87. countdown.value = `${String(minutes).padStart(2, '0')}:${String(seconds).padStart(2, '0')}`
  88. }, 1000)
  89. }
  90. async function getDetail() {
  91. uni.showLoading({
  92. title: t('orderDetail.loading'),
  93. })
  94. try {
  95. const res = await orderDetail({ id: id.value })
  96. if (res.code === '200') {
  97. detail.value = res.data
  98. if (detail.value?.status === 1) {
  99. startCountdown() // 开始倒计时
  100. // if (isPayOrder.value) {
  101. // // 自动调用支付接口
  102. // await goPay()
  103. // }
  104. }
  105. await getPink()
  106. paging.value.complete()
  107. }
  108. }
  109. finally {
  110. uni.hideLoading()
  111. }
  112. }
  113. const pinkList = ref<any>([])
  114. async function getPink() {
  115. try {
  116. const res = await orderPink({ id: detail.value?.storePink?.id })
  117. if (res.code === '200') {
  118. pinkList.value = res.data
  119. }
  120. }
  121. catch {
  122. }
  123. }
  124. // 跳转到地址簿选择地址
  125. function selectAddress() {
  126. toPage('/pages/mine/addressBook', {
  127. selectMode: true,
  128. orderId: detail.value?.orderId,
  129. })
  130. }
  131. // 显示取消订单确认对话框
  132. function showCancelOrderDialog() {
  133. dialogType.value = 'cancel'
  134. Object.assign(dialogConfig.value, DialogUtils.info(
  135. t('orderDetail.dialog.cancel.title'),
  136. {
  137. showCancel: true,
  138. confirmText: t('orderDetail.dialog.cancel.confirm'),
  139. cancelText: t('orderDetail.dialog.cancel.keep'),
  140. confirmPlain: true,
  141. },
  142. ))
  143. }
  144. // 取消订单
  145. async function cancelOrder() {
  146. try {
  147. uni.showLoading({
  148. title: t('orderDetail.cancel.loading'),
  149. mask: true,
  150. })
  151. const res = await orderCancel({ id: id.value })
  152. if (res.code === '200') {
  153. // 显示成功提示
  154. toast.success(t('orderDetail.cancel.success'))
  155. // 刷新订单详情
  156. getDetail()
  157. }
  158. else {
  159. // 显示错误提示
  160. toast.error(res.message || t('orderDetail.cancel.error'))
  161. }
  162. }
  163. catch (error: any) {
  164. console.error('Cancel order error:', error)
  165. // 显示错误提示
  166. toast.error(t('orderDetail.cancel.network'))
  167. }
  168. finally {
  169. uni.hideLoading()
  170. }
  171. }
  172. // 处理对话框确认事件
  173. function handleDialogConfirm() {
  174. if (dialogType.value === 'cancel') {
  175. cancelOrder()
  176. }
  177. else if (dialogType.value === 'pay') {
  178. goPay()
  179. }
  180. else if (dialogType.value === 'recharge') {
  181. toPage('/pages/wallet/recharge', { price: detail.value?.payPrice }, true)
  182. }
  183. // 关闭对话框
  184. handleDialogClose()
  185. }
  186. // 处理对话框关闭事件
  187. function handleDialogClose() {
  188. dialogConfig.value.show = false
  189. }
  190. // 支付
  191. // 显示支付余额去充值提示
  192. function showRechargeDialog() {
  193. dialogType.value = 'recharge'
  194. Object.assign(dialogConfig.value, DialogUtils.info(
  195. `Your wallet balance is insufficient. \n Please Recharge!`,
  196. {
  197. showCancel: false,
  198. confirmText: 'Recharge Now',
  199. cancelText: '',
  200. confirmPlain: true,
  201. },
  202. ))
  203. }
  204. // 显示支付确认对话框
  205. function showPayOrderDialog() {
  206. dialogType.value = 'pay'
  207. Object.assign(dialogConfig.value, DialogUtils.info(
  208. 'BandhuBuy Wallet Pay',
  209. {
  210. showCancel: false,
  211. cancelText: '',
  212. confirmText: 'DONE',
  213. confirmPlain: false,
  214. },
  215. ))
  216. }
  217. async function goPay() {
  218. const payRes = await payOrder({
  219. orderId: detail.value?.orderId,
  220. type: detail.value?.storePink?.kId ? 'join' : 'open',
  221. })
  222. console.log(payRes)
  223. if (payRes.code === '200') {
  224. toast.success(t('orderDetail.payment.success'))
  225. getDetail()
  226. }
  227. }
  228. function handleClick() {
  229. if (detail.value?.status === 1) {
  230. // 去支付
  231. showPayOrderDialog()
  232. }
  233. else {
  234. // 去分享
  235. }
  236. }
  237. const timeMap = {
  238. create_order: 'placed on',
  239. pay_success: 'paid on',
  240. delivery: 'shipped on',
  241. receive: 'completed on',
  242. }
  243. onLoad(async (options) => {
  244. getConfig('open_red_envelope_rate')
  245. getConfig('join_red_envelope_rate')
  246. const params = getPageParams(options)
  247. id.value = params.id
  248. isPayOrder.value = params.isPayOrder
  249. await getOrderStatus()
  250. })
  251. const balance = ref<number>(0)
  252. onShow(async () => {
  253. await getDetail()
  254. if (isPayOrder.value && detail.value?.status === 1) {
  255. const res = await getWalletAccountInfo()
  256. console.log(res)
  257. balance.value = res?.data?.balance
  258. if (balance.value < detail.value?.payPrice) {
  259. // 余额不足,提示去充值
  260. showRechargeDialog()
  261. }
  262. else {
  263. // 余额充足,显示支付对话框
  264. showPayOrderDialog()
  265. }
  266. }
  267. })
  268. onUnmounted(() => {
  269. if (timer.value)
  270. clearInterval(timer.value)
  271. })
  272. </script>
  273. <template>
  274. <z-paging ref="paging" refresher-only @refresh="getDetail">
  275. <view class="pt-20rpx">
  276. <!-- 状态显示 -->
  277. <template v-if="detail.status !== 4 && detail?.status !== 2">
  278. <!-- 已中奖 -->
  279. <view v-if="detail?.storePink?.status === 2 && detail?.storePink?.lId === 1" class="mb-20rpx bg-#17AA68/80 py-20rpx text-center text-28rpx text-white">
  280. {{ t('orderDetail.congrats') }}
  281. <br>
  282. {{ t('orderDetail.receiveReward') }}
  283. <text class="text-[var(--wot-color-theme)]">
  284. ৳{{ 10 }}
  285. </text>
  286. </view>
  287. <!-- 未中奖 -->
  288. <view v-else-if="detail?.storePink?.status === 2 && detail?.storePink?.lId === 0" class="mb-20rpx bg-#E61B28/80 py-20rpx text-center text-28rpx text-white">
  289. {{ t('orderDetail.sorry') }}
  290. <br>
  291. {{ t('orderDetail.receiveReward') }}
  292. <text class="text-#66C59B">
  293. ৳8
  294. </text>
  295. </view>
  296. <!-- 未开奖||未支付 -->
  297. <view v-else-if="detail?.storePink?.status === 1 || detail?.status === 1" class="mb-20rpx bg-#fff py-20rpx text-center text-28rpx text-white">
  298. <text v-if="detail?.storePink?.status === 1 && detail?.status !== 1" class="text-[var(--wot-color-theme)]">
  299. {{ t('orderDetail.waiting') }}
  300. </text>
  301. <text v-else class="text-[var(--wot-color-theme)]">
  302. {{ t('orderDetail.paymentCountdown') }} {{ countdown }}
  303. </text>
  304. </view>
  305. <!-- 拼团头像 -->
  306. <view v-if="pinkList && pinkList.length" class="mb-20rpx bg-white px-20rpx py-20rpx text-center">
  307. <image v-for="i in pinkList" :key="i" class="mx-4rpx mb-8rpx h-80rpx w-80rpx rounded-full" :src="i.avatar" />
  308. </view>
  309. </template>
  310. <!-- 地址 -->
  311. <view v-if="detail?.storePink?.status === 2 && detail?.storePink?.lId === 1" class="mb-20rpx bg-white px-24rpx py-20rpx">
  312. <!-- 无地址 -->
  313. <template v-if="!detail.orderAddressVO">
  314. <view class="flex items-center justify-between" @click="selectAddress">
  315. <view class="text-28rpx text-[var(--wot-color-theme)]">
  316. {{ t('orderDetail.address.add') }}
  317. </view>
  318. <wd-icon name="arrow-right" color="#7D7D7D" size="28rpx" />
  319. </view>
  320. </template>
  321. <!-- 有地址 -->
  322. <template v-else>
  323. <view v-if="detail.deliveryCode" class="mb-18rpx flex justify-between border-b-1 border-b-#E1E1E1 border-b-solid pb-18rpx text-24rpx">
  324. <!-- 物流信息 -->
  325. <view>{{ detail.deliveryName }}</view>
  326. <view> DHL:{{ detail.deliveryCode || '-' }}</view>
  327. </view>
  328. <view class="mb-20rpx text-24rpx">
  329. <text class="mr-20rpx">
  330. {{ detail?.orderAddressVO?.realName }}
  331. </text>
  332. <text>{{ detail?.orderAddressVO?.phone }}</text>
  333. </view>
  334. <view class="text-22rpx text-#3A444C">
  335. {{ detail?.orderAddressVO?.province }} {{ detail?.orderAddressVO?.city }} {{ detail?.orderAddressVO?.district }} {{ detail?.orderAddressVO?.detail }} {{ detail?.orderAddressVO?.postCode }}
  336. </view>
  337. </template>
  338. </view>
  339. <!-- 商品信息 -->
  340. <wd-card type="rectangle" custom-class="px-24rpx! py-6rpx!" custom-content-class="py-18rpx!" custom-title-class="py-18rpx!" @click="toPage('/pages/productDetail/productDetail', { productId: detail?.orderInfoVO?.[0].productId })">
  341. <template #title>
  342. <view class="flex items-center justify-between">
  343. <view class="text-28rpx text-#000">
  344. {{ t('orderDetail.address.orderNo') }}:{{ detail?.orderInfoVO?.[0].orderNo }}
  345. </view>
  346. <wd-text size="26rpx" type="primary" :text="orderStatusEnumData.find((i:any) => i.code === detail?.status)?.name" />
  347. </view>
  348. </template>
  349. <view class="flex items-center gap-24rpx">
  350. <view class="h-140rpx w-140rpx shrink-0">
  351. <image
  352. :src="detail?.orderInfoVO?.[0]?.image"
  353. class="h-full w-full"
  354. mode="aspectFit"
  355. />
  356. </view>
  357. <view class="flex-1">
  358. <view class="line-clamp-2 text-28rpx text-#000">
  359. {{ detail?.orderInfoVO?.[0].productName }}
  360. </view>
  361. <view class="py-4rpx text-24rpx text-#3A444C">
  362. {{ t('orderDetail.address.color') }}:{{ detail?.orderInfoVO?.[0].sku }}
  363. </view>
  364. <view class="flex items-center justify-between text-24rpx">
  365. <view class="text-[var(--wot-color-theme)]">
  366. ৳ {{ formatNumber(detail?.orderInfoVO?.[0].price) }}
  367. </view>
  368. <view class="text-#3A444C">
  369. {{ t('orderDetail.address.quantity') }}:{{ detail?.orderInfoVO?.[0].payNum }}
  370. </view>
  371. </view>
  372. </view>
  373. </view>
  374. </wd-card>
  375. <!-- 订单信息 -->
  376. <view class="bg-white px-24rpx">
  377. <view class="border-b-1 border-b-#E1E1E1 border-b-solid py-24rpx">
  378. <view class="mb-12rpx text-28rpx">
  379. {{ t('orderDetail.summary.title') }}
  380. </view>
  381. <view class="flex flex-col gap-16rpx text-#3A444C">
  382. <view class="flex items-center justify-between text-24rpx">
  383. <text>SubTotal</text>
  384. <text>৳{{ formatNumber(detail.totalPrice) }}</text>
  385. </view>
  386. </view>
  387. </view>
  388. <view class="border-b-1 border-b-#E1E1E1 border-b-solid py-24rpx">
  389. <view class="mb-12rpx text-28rpx">
  390. {{ t('orderDetail.payment.title') }}
  391. </view>
  392. <view class="flex flex-col gap-16rpx text-#3A444C">
  393. <view class="flex items-center justify-between text-24rpx">
  394. <text>SubTotal</text>
  395. <text>৳{{ formatNumber(detail.payPrice) }}</text>
  396. </view>
  397. </view>
  398. </view>
  399. <view v-if="detail?.orderStatusVO?.length" class="py-24rpx">
  400. <template v-for="i in detail?.orderStatusVO" :key="i.id">
  401. <view v-if="timeMap[i.changeType]" class="mb-16rpx flex flex-col text-#3A444C">
  402. <view class="flex items-center justify-between text-24rpx">
  403. <text>{{ timeMap[i.changeType] }}</text>
  404. <text>{{ i.createTime }}</text>
  405. </view>
  406. </view>
  407. </template>
  408. </view>
  409. </view>
  410. </view>
  411. <template #bottom>
  412. <view v-if="detail?.status === 1" class="flex items-center justify-end bg-white/60 px-28rpx py-30rpx backdrop-blur-20">
  413. <!-- 取消订单按钮 -->
  414. <wd-button
  415. v-if="detail?.status === 1"
  416. custom-class="mr-16rpx!"
  417. plain
  418. @click="showCancelOrderDialog"
  419. >
  420. {{ t('orderDetail.button.cancel') }}
  421. </wd-button>
  422. <wd-button @click="handleClick">
  423. {{ t('orderDetail.button.pay') }}
  424. </wd-button>
  425. </view>
  426. </template>
  427. <!-- DialogBox 函数式调用 -->
  428. <DialogBox
  429. v-bind="dialogConfig"
  430. @confirm="handleDialogConfirm"
  431. @cancel="handleDialogClose"
  432. @close="handleDialogClose"
  433. >
  434. <view v-if="dialogType === 'pay'">
  435. <view class="font-blod relative text-32rpx">
  436. <text>BandhuBuy Wallet Pay</text>
  437. <wd-icon name="close-normal" custom-class="absolute right-0 top-1/2 -translate-y-1/2" size="40rpx" @click="handleDialogClose" />
  438. </view>
  439. <view class="py-60rpx">
  440. <text class="text-40rpx">
  441. </text>
  442. <text class="text-60rpx">
  443. {{ formatNumber(detail.payPrice) }}
  444. </text>
  445. </view>
  446. <view class="pb-28rpx text-left">
  447. <view class="text-24rpx font-bold">
  448. Payment Method
  449. </view>
  450. <view class="my-14rpx border-b-1px border-b-#EBEBEB border-b-solid" />
  451. <view class="flex items-center justify-between text-24rpx">
  452. <view class="flex items-center">
  453. <view class="text-24rpx">
  454. <text>BandhuBuy Wallet (</text>
  455. <text class="text-[var(--wot-color-theme)]">
  456. Balance: ৳{{ formatNumber(balance) }}
  457. </text>
  458. <text>)</text>
  459. </view>
  460. </view>
  461. <view>
  462. <image
  463. src="/static/icons/circle-check.png"
  464. class="h-36rpx w-36rpx"
  465. />
  466. </view>
  467. </view>
  468. </view>
  469. </view>
  470. </DialogBox>
  471. </z-paging>
  472. </template>
  473. <style lang="scss" scoped>
  474. //
  475. </style>