index.vue 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401
  1. <script setup lang="ts">
  2. import CouponItem from '../components/coupon/index.vue'
  3. import router from '@/router'
  4. definePage({
  5. name: 'xsb-confirmOrder',
  6. islogin: true,
  7. style: {
  8. navigationBarTitleText: '提交订单',
  9. },
  10. })
  11. const { selectedAddress, userInfo } = storeToRefs(useUserStore())
  12. const { SelectShopInfo } = storeToRefs(useSysXsbStore())
  13. const orderInfo = ref<Api.shoppingCartOrderConfirm>()
  14. const { totalProduct } = storeToRefs(useSmqjhCartStore())
  15. const isPay = ref(false)
  16. const remarks = ref('')
  17. const initialCouponId = ref<string | undefined>(undefined)
  18. const confirmedCouponId = ref<string | undefined>(undefined)
  19. const draftCouponId = ref<string | undefined>(undefined)
  20. const deliveryType = ref(1)
  21. // 优惠券选择
  22. const couponPopup = ref(false)
  23. const couponList = ref<Api.AppMemberCouponVO[]>([])
  24. const couponTabActive = ref(0)
  25. const availableCoupons = computed(() => couponList.value.filter(it => it.isUsed === 2))
  26. const unavailableCoupons = computed(() => couponList.value.filter(it => it.isUsed !== 2))
  27. const displayCoupons = computed(() => couponTabActive.value === 0 ? availableCoupons.value : unavailableCoupons.value)
  28. const confirmedCoupon = computed(() => couponList.value.find(it => it.allowanceId === confirmedCouponId.value))
  29. const draftCoupon = computed(() => couponList.value.find(it => it.allowanceId === draftCouponId.value))
  30. const hasManualCouponSelection = computed(() => confirmedCouponId.value !== initialCouponId.value)
  31. const currentCouponDiscount = computed(() => {
  32. if (hasManualCouponSelection.value) {
  33. return Number(confirmedCoupon.value?.discountMoney || 0)
  34. }
  35. return Number(orderInfo.value?.coupon || 0)
  36. })
  37. const offsetPoints = computed(() => {
  38. const couponCents = Math.round(currentCouponDiscount.value * 100)
  39. const money = Math.round(Number(unref(orderInfo)?.transfee) * 100) + Math.round(Number(unref(orderInfo)?.price) * 100) - couponCents
  40. const cap = Math.max(0, money)
  41. if (Number(unref(orderInfo)?.offsetPoints) > cap) {
  42. return cap
  43. }
  44. return Number(unref(orderInfo)?.offsetPoints)
  45. })
  46. const currentCouponName = computed(() => {
  47. if (hasManualCouponSelection.value) {
  48. return getCouponDisplayName(confirmedCoupon.value)
  49. }
  50. return getCouponDisplayName(orderInfo.value)
  51. })
  52. const displayTotalPrice = computed(() => {
  53. if (!orderInfo.value) {
  54. return 0
  55. }
  56. if (!hasManualCouponSelection.value) {
  57. return Number(orderInfo.value.totalPrice || 0)
  58. }
  59. const goodsPrice = Number(orderInfo.value.price || 0)
  60. const freight = Number(orderInfo.value.transfee || 0)
  61. const pointsDiscount = offsetPoints.value / 100
  62. return Math.max(0, Number((goodsPrice + freight - currentCouponDiscount.value - pointsDiscount).toFixed(2)))
  63. })
  64. const displayDiscountTotal = computed(() => Number((currentCouponDiscount.value + offsetPoints.value / 100).toFixed(2)))
  65. onLoad((options: any) => {
  66. orderInfo.value = JSON.parse(options.data)
  67. couponList.value = orderInfo.value?.orderCouponItemDTOS || []
  68. initialCouponId.value = orderInfo.value?.allowanceId
  69. confirmedCouponId.value = initialCouponId.value
  70. draftCouponId.value = initialCouponId.value
  71. })
  72. onShow(() => {
  73. useUserStore().getuserAddresslist()
  74. if (selectedAddress.value) {
  75. getDevryList()
  76. }
  77. })
  78. function getCouponDisplayName(coupon?: Partial<Api.AppMemberCouponVO> & Record<string, any>) {
  79. return coupon?.activityName || coupon?.couponName || ''
  80. }
  81. async function getDevryList() {
  82. const res = await Apis.xsb.delivery({
  83. data: {
  84. memberId: userInfo.value.id,
  85. shopId: Number(orderInfo.value?.skuList[0].shopId || SelectShopInfo.value?.shopId || 2),
  86. addressId: selectedAddress.value?.id,
  87. },
  88. })
  89. deliveryType.value = res.data.deliveryType
  90. console.log(res)
  91. }
  92. async function openCouponPopup() {
  93. draftCouponId.value = confirmedCouponId.value
  94. couponPopup.value = true
  95. }
  96. function handleSelectCoupon(item: Api.AppMemberCouponVO) {
  97. if (item.isUsed !== 2 || !item.allowanceId) {
  98. return
  99. }
  100. // 再次点击已选中的券则取消选中
  101. if (draftCouponId.value === item.allowanceId) {
  102. draftCouponId.value = undefined
  103. return
  104. }
  105. draftCouponId.value = item.allowanceId
  106. }
  107. function confirmCoupon() {
  108. confirmedCouponId.value = draftCouponId.value
  109. couponPopup.value = false
  110. }
  111. async function handlePay() {
  112. if (!selectedAddress.value) {
  113. useGlobalToast().show({ msg: '请选择收货地址' })
  114. return
  115. }
  116. if (!orderInfo.value) {
  117. useGlobalToast().show({ msg: '网络异常!请联系客服' })
  118. return
  119. }
  120. isPay.value = true
  121. try {
  122. const orderItemList = orderInfo.value?.skuList.map((it) => {
  123. return {
  124. prodCount: it.num,
  125. skuId: it.skuId,
  126. }
  127. })
  128. const orderNumber = await useUserStore().getOrderPayMent(
  129. orderInfo.value.transfee,
  130. 'XSB',
  131. deliveryType.value,
  132. Number(orderInfo.value?.skuList[0].shopId || SelectShopInfo.value?.shopId),
  133. orderItemList,
  134. unref(remarks),
  135. confirmedCouponId.value,
  136. )
  137. const res = await useUserStore().handleCommonPayMent(orderNumber)
  138. await useUserStore().clearCart(orderInfo.value.skuList)
  139. totalProduct.value = null
  140. if (res.payType !== 1) {
  141. try {
  142. await useUserStore().getWxCommonPayment(res)
  143. await useUserStore().paySuccess('xsb-order', 'subPack-xsb/commonTab/index')
  144. }
  145. catch {
  146. await useUserStore().payError('xsb-order', 'subPack-xsb/commonTab/index')
  147. }
  148. }
  149. else {
  150. await useUserStore().paySuccess('xsb-order', 'subPack-xsb/commonTab/index')
  151. }
  152. }
  153. catch {
  154. isPay.value = false
  155. }
  156. }
  157. </script>
  158. <template>
  159. <view class="page px20rpx py20rpx">
  160. <view
  161. class="mb20rpx rounded-16rpx bg-white p24rpx"
  162. @click="router.push({ name: 'common-addressList', params: { type: 'select' } })"
  163. >
  164. <view class="flex items-center justify-between">
  165. <view v-if="!selectedAddress" class="flex items-center">
  166. <wd-icon name="location" size="18px" />
  167. <view class="ml10rpx text-28rpx">
  168. 请添加收货地址
  169. </view>
  170. </view>
  171. <view v-else>
  172. <view class="flex items-center">
  173. <view v-if="selectedAddress.defaulted" class="mr20rpx">
  174. <wd-tag type="primary">
  175. 默认
  176. </wd-tag>
  177. </view>
  178. <view class="flex items-center text-28rpx font-semibold">
  179. <view>{{ selectedAddress?.consigneeName }} </view>
  180. <view class="ml20rpx">
  181. {{ selectedAddress?.consigneeMobile }}
  182. </view>
  183. </view>
  184. </view>
  185. <view class="mt20rpx flex items-center text-24rpx text-#AAAAAA">
  186. {{ selectedAddress?.province }} {{ selectedAddress?.city }} {{ selectedAddress?.detailAddress }}
  187. </view>
  188. </view>
  189. <wd-icon name="arrow-right" size="18px" color="#aaa" />
  190. </view>
  191. </view>
  192. <view class="rounded-16rpx bg-white p24rpx">
  193. <view class="flex items-center text-28rpx font-semibold">
  194. {{ orderInfo?.shopName }}
  195. </view>
  196. <view class="my24rpx h2rpx w-full bg-#F0F0F0" />
  197. <CollapsePanel :line-height="150">
  198. <view v-for="item in orderInfo?.skuList" :key="item.id" class="mb20rpx w-full flex items-center">
  199. <view class="mr20rpx w120rpx flex-shrink-0">
  200. <image :src="item.pic" class="h120rpx w120rpx" />
  201. </view>
  202. <view class="flex-1">
  203. <view class="w-full flex items-center justify-between font-semibold">
  204. <view class="text-28rpx">
  205. {{ item.skuName }}
  206. </view>
  207. <view class="text-32rpx text-#FF4D3A">
  208. ¥{{ item.price }}
  209. </view>
  210. </view>
  211. <view class="text-24rpx text-#AAAAAA">
  212. 规格:{{ item.spec }}
  213. </view>
  214. <view class="text-24rpx text-#AAAAAA">
  215. ×{{ item.num || 1 }}
  216. </view>
  217. </view>
  218. </view>
  219. </CollapsePanel>
  220. </view>
  221. <view class="mt20rpx rounded-16rpx bg-white p24rpx">
  222. <view class="mb28rpx flex items-center justify-between text-28rpx">
  223. <view>商品金额</view>
  224. <view class="text-#FF4D3A font-semibold">
  225. ¥{{ orderInfo?.price }}
  226. </view>
  227. </view>
  228. <view class="mb28rpx flex items-center justify-between text-28rpx">
  229. <view v-if="deliveryType == 3">
  230. 配送费(即时配送)
  231. </view>
  232. <view v-if="deliveryType == 1">
  233. 快递
  234. </view>
  235. <view class="text-#FF4D3A font-semibold">
  236. ¥{{ orderInfo?.transfee }}
  237. </view>
  238. </view>
  239. <view class="mb28rpx flex items-center justify-between text-28rpx" @click="openCouponPopup">
  240. <view>优惠券</view>
  241. <view class="flex items-center">
  242. <view v-if="currentCouponDiscount > 0" class="text-[#FF4D3A] font-semibold">
  243. -¥{{ currentCouponDiscount }}
  244. </view>
  245. <view v-else class="text-[#AAAAAA]">
  246. {{ availableCoupons.length > 0 ? `${availableCoupons.length}张可用` : '暂无可用' }}
  247. </view>
  248. <wd-icon name="arrow-right" size="18px" color="#aaa" class="ml10rpx" />
  249. </view>
  250. </view>
  251. <view class="flex items-center justify-between text-28rpx">
  252. <view>积分({{ offsetPoints }})</view>
  253. <view class="text-#FF4D3A font-semibold">
  254. - ¥{{ offsetPoints / 100 }}
  255. </view>
  256. </view>
  257. <view class="my24rpx h2rpx w-full bg-#F0F0F0" />
  258. <view class="flex items-center justify-between text-28rpx">
  259. <view class="font-semibold">
  260. 总计:
  261. </view>
  262. <view class="text-#FF4D3A font-semibold">
  263. ¥ {{ displayTotalPrice }}
  264. </view>
  265. </view>
  266. </view>
  267. <view class="mt20rpx flex items-center rounded-16rpx bg-white p24rpx">
  268. <view class="w80rpx">
  269. 备注
  270. </view>
  271. <view class="flex-1">
  272. <wd-input v-model="remarks" placeholder="选填,请先和商家协商一致,付款后商家可见" no-border clearable />
  273. </view>
  274. </view>
  275. <view class="h250rpx" />
  276. <!-- 优惠券选择弹窗 -->
  277. <Zpopup v-model="couponPopup" :zindex="100" bg="#fff">
  278. <view class="ios box-border w-full">
  279. <view class="px-32rpx pt-32rpx">
  280. <view class="mb-24rpx text-center text-32rpx font-semibold">
  281. 优惠券
  282. </view>
  283. <view class="mb-24rpx flex items-center">
  284. <view
  285. class="mr-16rpx flex-1 rounded-full py-18rpx text-center text-28rpx"
  286. :class="couponTabActive === 0 ? 'bg-[#9ED605] text-white font-semibold' : 'bg-[#F0F0F0] text-[#666]'"
  287. @click="couponTabActive = 0"
  288. >
  289. 可用券({{ availableCoupons.length }})
  290. </view>
  291. <view
  292. class="flex-1 rounded-full py-18rpx text-center text-28rpx"
  293. :class="couponTabActive === 1 ? 'bg-[#9ED605] text-white font-semibold' : 'bg-[#F0F0F0] text-[#666]'"
  294. @click="couponTabActive = 1"
  295. >
  296. 不可用券({{ unavailableCoupons.length }})
  297. </view>
  298. </view>
  299. <view class="mb-20rpx flex items-center justify-between text-24rpx">
  300. <view class="text-[#333]">
  301. 优惠券共减
  302. </view>
  303. <view v-if="draftCoupon" class="text-right text-[#FF4D3A]">
  304. {{ getCouponDisplayName(draftCoupon) }},可减-¥{{ draftCoupon.discountMoney }}
  305. </view>
  306. <view v-else-if="currentCouponDiscount > 0 && currentCouponName" class="text-right text-[#FF4D3A]">
  307. {{ currentCouponName }},可减¥{{ currentCouponDiscount }}
  308. </view>
  309. <view v-else class="text-[#AAAAAA]">
  310. 暂未选择
  311. </view>
  312. </view>
  313. </view>
  314. <scroll-view scroll-y class="box-border h-560rpx px-32rpx">
  315. <view v-for="item in displayCoupons" :key="item.id" class="relative mb-20rpx">
  316. <view class="relative" @click="handleSelectCoupon(item)">
  317. <CouponItem :itemcoupon="item" :hide-all-btn="true" />
  318. <view
  319. class="absolute right-32rpx top-50% h-44rpx w-44rpx flex items-center justify-center -translate-y-50%"
  320. >
  321. <wd-icon v-if="draftCouponId === item.allowanceId" name="check-circle" size="44rpx" color="#FF4D3A" />
  322. <view v-else class="h-40rpx w-40rpx border-2rpx border-[#ccc] rounded-full border-solid" />
  323. </view>
  324. </view>
  325. <view
  326. v-if="item.unavailableReason"
  327. class="relative z-1 w-full rounded-b-16rpx bg-[#FFF4F3] px20rpx py18rpx text-24rpx -mt16rpx"
  328. >
  329. <view>
  330. 不可用原因
  331. </view>
  332. <view>
  333. {{ item.unavailableReason }}
  334. </view>
  335. </view>
  336. </view>
  337. <view v-if="displayCoupons.length === 0" class="py-80rpx text-center text-28rpx text-[#AAAAAA]">
  338. 暂无优惠券
  339. </view>
  340. </scroll-view>
  341. </view>
  342. <template #footer>
  343. <view class="box-border w-full px24rpx">
  344. <wd-button size="large" block type="primary" @click="confirmCoupon">
  345. 确定
  346. </wd-button>
  347. </view>
  348. </template>
  349. </Zpopup>
  350. <view class="ios footer fixed bottom-0 left-0 box-border w-full rounded-t-16rpx bg-white px24rpx">
  351. <view class="box-border w-full flex items-center justify-between py20rpx">
  352. <view class="flex items-center text-#FF4D3A">
  353. <view class="font-semibold10 flex items-baseline text-36rpx">
  354. <text class="text-24rpx">
  355. </text> {{ displayTotalPrice }}
  356. </view>
  357. <view class="ml20rpx text-22rpx">
  358. 共减¥{{ displayDiscountTotal }}
  359. </view>
  360. </view>
  361. <view class="w180-btn w180rpx">
  362. <wd-button size="large" :loading="isPay" loading-color="#9ED605" @click="handlePay">
  363. 立即支付
  364. </wd-button>
  365. </view>
  366. </view>
  367. </view>
  368. </view>
  369. </template>
  370. <style scoped lang="scss">
  371. .footer {
  372. box-shadow: 0rpx -6rpx 12rpx 2rpx rgba(0, 0, 0, 0.05);
  373. }
  374. .page {
  375. .w180-btn {
  376. :deep() {
  377. .wd-button {
  378. width: 180rpx !important;
  379. }
  380. }
  381. }
  382. }
  383. </style>