index.vue 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495
  1. <template>
  2. <view class="container p-bottom" :style="appThemeStyle">
  3. <view v-if="order.goodsList.length && order.goodsList.length">
  4. <!-- 实物订单:选择配送方式 -->
  5. <block v-if="order.orderType == OrderTypeEnum.PHYSICAL.value">
  6. <!-- 配送方式选项卡 -->
  7. <view v-if="isShowTab" class="swiper-tab dis-flex flex-y-center flex-x-around">
  8. <view class="swiper-tab-item" :class="{ on: curDelivery == DeliveryTypeEnum.EXPRESS.value }"
  9. @click="handleSwichDelivery(DeliveryTypeEnum.EXPRESS.value)">
  10. <text>快递配送</text>
  11. </view>
  12. </view>
  13. <!-- 快递配送:配送地址 -->
  14. <view v-if="curDelivery == DeliveryTypeEnum.EXPRESS.value" @click="onSelectAddress" class="flow-delivery">
  15. <view class="flow-delivery__detail dis-flex flex-y-center">
  16. <view class="detail-location dis-flex">
  17. <text class="iconfont icon-dingwei"></text>
  18. </view>
  19. <view class="detail-content flex-box">
  20. <block v-if="order.address">
  21. <view class="detail-content__title dis-flex">
  22. <text class="f-30">{{ order.address.name }}</text>
  23. <text class="detail-content__title-phone f-28">{{ order.address.phone }}</text>
  24. </view>
  25. <view class="address detail-content__describe">
  26. <text class="region" v-for="(region, idx) in order.address.region" :key="idx">{{ region }}</text>
  27. <text class="detail">{{ order.address.detail }}</text>
  28. </view>
  29. </block>
  30. <block v-else>
  31. <view class="detail-content__describe dis-flex">
  32. <text class="col-6">请选择配送地址</text>
  33. </view>
  34. </block>
  35. </view>
  36. <view class="detail-arrow dis-flex">
  37. <text class="iconfont icon-arrow-right"></text>
  38. </view>
  39. </view>
  40. </view>
  41. </block>
  42. <!-- 商品列表 -->
  43. <view class="checkout_list" v-for="(item, index) in order.goodsList" :key="index">
  44. <view class="flow-shopList dis-flex" data-index="index" @click="onTargetGoods(item.goods_id)">
  45. <!-- 商品图片 -->
  46. <view class="flow-list-left">
  47. <image mode="scaleToFill" :src="item.goods_image"></image>
  48. </view>
  49. <view class="flow-list-right flex-box">
  50. <!-- 商品名称 -->
  51. <text class="goods-name twoline-hide">{{ item.goods_name }}</text>
  52. <!-- 商品规格 -->
  53. <view class="goods-props clearfix">
  54. <view class="goods-props-item" v-for="(props, idx) in item.skuInfo.goods_props" :key="idx">
  55. <text class="group-name">{{ props.group.name }}: </text>
  56. <text>{{ props.value.name }};</text>
  57. </view>
  58. </view>
  59. <!-- 商品数量和单价 -->
  60. <view class="flow-list-cont dis-flex flex-x-between flex-y-center">
  61. <text class="small">×{{ item.total_num }}</text>
  62. <text class="flow-cont" :class="[item.is_user_grade ? 'price-delete' : '']">¥{{ item.goods_price }}</text>
  63. </view>
  64. <!-- 会员折扣价 -->
  65. <view v-if="item.is_user_grade" class="grade-price">
  66. <text>会员折扣价:¥{{ item.grade_goods_price }}</text>
  67. </view>
  68. </view>
  69. </view>
  70. </view>
  71. <view class="flow-num-box b-f">
  72. <!-- <text>共{{ order.orderTotalNum }}件商品,合计:</text>
  73. <text class="flow-money col-m">¥{{ order.orderTotalPrice }}</text> -->
  74. <text>共{{ order.orderTotalNum }}件商品</text>
  75. </view>
  76. <!-- 商品金额 -->
  77. <view class="flow-all-money b-f m-top20">
  78. <view class="flow-all-list dis-flex">
  79. <text class="flex-five">订单总金额:</text>
  80. <view class="flex-five t-r">
  81. <text class="col-m">¥{{ order.orderTotalPrice }}</text>
  82. </view>
  83. </view>
  84. <!-- 优惠券 -->
  85. <view v-if="$checkModule('market-coupon')" class="flow-all-list dis-flex">
  86. <text class="flex-five">优惠券:</text>
  87. <view class="flex-five t-r">
  88. <view v-if="order.couponList.length > 0" @click="handleShowPopup()">
  89. <text class="col-m" v-if="order.couponId > 0">-¥{{ order.couponMoney }}</text>
  90. <text class="col-m" v-else>有{{ order.couponList.length }}张优惠券</text>
  91. <text class="right-arrow iconfont icon-arrow-right"></text>
  92. </view>
  93. <text v-else class="">无优惠券可用</text>
  94. </view>
  95. </view>
  96. <!-- 积分抵扣 -->
  97. <view v-if="$checkModule('market-points') && order.isAllowPoints" class="points flow-all-list dis-flex flex-y-center">
  98. <view class="block-left flex-five" @click="handleShowPoints()">
  99. <text class="title">可用{{ setting.points_name }}抵扣:</text>
  100. <text class="iconfont icon-help"></text>
  101. </view>
  102. <view class="flex-five dis-flex flex-x-end flex-y-center">
  103. <text class="points-money col-m">-¥{{ order.pointsMoney }}</text>
  104. <u-switch v-model="isUsePoints" size="42" :active-color="appTheme.mainBg" @change="getOrderData()"></u-switch>
  105. </view>
  106. </view>
  107. <!-- 配送费用 -->
  108. <view v-if="curDelivery == DeliveryTypeEnum.EXPRESS.value" class="dis-flex flow-all-list">
  109. <text class="flex-five">配送费用:</text>
  110. <view class="flex-five t-r">
  111. <view v-if="order.address">
  112. <text class="col-m" v-if="order.isIntraRegion">+¥{{ order.expressPrice }}</text>
  113. <text v-else>不在配送范围</text>
  114. </view>
  115. <view v-else>
  116. <text class="col-7">请先选择配送地址</text>
  117. </view>
  118. </view>
  119. </view>
  120. </view>
  121. <!-- 买家留言 -->
  122. <view class="flow-all-money b-f m-top20">
  123. <view class="ipt-wrapper flow-all-list">
  124. <input class="input" v-model="remark" placeholder="选填:买家留言(50字以内)" />
  125. </view>
  126. </view>
  127. <!-- 提交订单 -->
  128. <view class="flow-fixed-footer b-f m-top10">
  129. <view class="dis-flex chackout-box">
  130. <view class="chackout-left pl-12">实付款:
  131. <text class="col-m">¥{{ order.orderPayPrice }}</text>
  132. </view>
  133. <view class="chackout-right" @click="onSubmitOrder()">
  134. <view class="flow-btn f-32" :class="{ disabled }">提交订单</view>
  135. </view>
  136. </view>
  137. </view>
  138. <!-- 积分说明弹窗 -->
  139. <u-modal v-model="showPoints" :title="`${setting.points_name}说明`">
  140. <scroll-view class="points-content" :scroll-y="true">
  141. <text>{{ setting.points_describe }}</text>
  142. </scroll-view>
  143. </u-modal>
  144. <!-- 优惠券弹出框 -->
  145. <u-popup v-model="showPopup" mode="bottom">
  146. <view class="popup__coupon">
  147. <view class="coupon__title f-30">选择优惠券</view>
  148. <!-- 优惠券列表 -->
  149. <scroll-view :scroll-y="true" style="height: 650rpx;">
  150. <view class="coupon-list">
  151. <view class="coupon-item" v-for="(item, index) in order.couponList" :key="index">
  152. <view class="item-wrapper" :class="[ !item.state.value ? 'disable' : '' ]" @click="handleSelectCoupon(index)">
  153. <!-- 优惠券类型 (标签) -->
  154. <view class="coupon-tag">
  155. <text>{{ CouponTypeEnum[item.coupon_type].name }}</text>
  156. </view>
  157. <view class="coupon-left">
  158. <!-- 优惠额度/折扣 -->
  159. <view class="coupon-reduce">
  160. <block v-if="item.coupon_type == CouponTypeEnum.FULL_DISCOUNT.value">
  161. <view class="coupon-reduce-unit"><text>¥</text></view>
  162. <view class="coupon-reduce-amount">
  163. <text class="value">{{ item.reduce_price }}</text>
  164. </view>
  165. </block>
  166. <block v-if="item.coupon_type == CouponTypeEnum.DISCOUNT.value">
  167. <view class="coupon-reduce-amount">
  168. <text class="value">{{ item.discount }}折</text>
  169. </view>
  170. </block>
  171. </view>
  172. <!-- 最低消费金额 -->
  173. <text class="coupon-hint">满{{ item.min_price }}元可用</text>
  174. </view>
  175. <view class="coupon-content">
  176. <view class="coupon-name">{{ item.name }}</view>
  177. <view class="coupon-middle">
  178. <view class="coupon-expire">
  179. <text v-if="item.expire_type == CouponTypeEnum.FULL_DISCOUNT.value">领取后{{ item.expire_day }}天内有效</text>
  180. <text v-if="item.expire_type == CouponTypeEnum.DISCOUNT.value">
  181. <block v-if="item.start_time === item.end_time">{{ item.start_time }} 当天有效</block>
  182. <block v-else>{{ item.start_time }}~{{ item.end_time }}</block>
  183. </text>
  184. </view>
  185. </view>
  186. <view v-if="item.describe" class="coupon-expand" @click.stop="handleDescribe(index)">
  187. <text>使用说明</text>
  188. <text class="coupon-expand-arrow iconfont icon-arrow-down" :class="[item.expand ? 'expand' : '']" />
  189. </view>
  190. </view>
  191. <view class="coupon-right">
  192. <view class="my-radio" :class="{ checked: selectCouponId == item.user_coupon_id }">
  193. <u-icon name="checkbox-mark" :size="20" />
  194. </view>
  195. </view>
  196. </view>
  197. <!-- 优惠券描述 -->
  198. <view :class="[item.expand ? 'expand' : '']" class="coupon-expand-rules">
  199. <view class="coupon-expand-rules-content">
  200. <view class="pre">{{ item.describe }}</view>
  201. </view>
  202. </view>
  203. </view>
  204. </view>
  205. </scroll-view>
  206. <!-- 不使用优惠券 -->
  207. <view class="coupon__do_not">
  208. <view class="control" @click="handleNotUseCoupon()">
  209. <text class="f-26">不使用优惠券</text>
  210. </view>
  211. </view>
  212. </view>
  213. </u-popup>
  214. </view>
  215. <u-toast ref="uToast" />
  216. </view>
  217. </template>
  218. <script>
  219. import * as Verify from '@/utils/verify'
  220. import * as CheckoutApi from '@/api/checkout'
  221. import { CouponTypeEnum } from '@/common/enum/coupon'
  222. import { OrderTypeEnum, DeliveryTypeEnum } from '@/common/enum/order'
  223. const CouponColors = ['red', 'blue', 'violet', 'yellow']
  224. // 根据指定mode获取对应的api类
  225. const getCheckoutApi = (mode) => {
  226. const apiEnum = {
  227. buyNow: CheckoutApi,
  228. cart: CheckoutApi
  229. }
  230. return apiEnum[mode]
  231. }
  232. // 根据指定mode获取param
  233. const getModeParam = (mode, options) => {
  234. const param = {}
  235. // 结算模式: 立即购买
  236. if (mode === 'buyNow') {
  237. param.goodsId = options.goodsId
  238. param.goodsNum = options.goodsNum
  239. param.goodsSkuId = options.goodsSkuId
  240. }
  241. // 结算模式: 购物车
  242. if (mode === 'cart') {
  243. param.cartIds = options.cartIds
  244. }
  245. return param
  246. }
  247. export default {
  248. data() {
  249. return {
  250. // 枚举类
  251. OrderTypeEnum,
  252. DeliveryTypeEnum,
  253. CouponTypeEnum,
  254. // 当前页面参数
  255. options: {},
  256. // 配送方式
  257. isShowTab: false,
  258. DeliveryTypeEnum,
  259. curDelivery: null,
  260. // 优惠券颜色组
  261. CouponColors,
  262. // 选择的优惠券
  263. selectCouponId: 0,
  264. // 是否使用积分抵扣
  265. isUsePoints: false,
  266. // 买家留言
  267. remark: '',
  268. // 禁用submit按钮
  269. disabled: false,
  270. // 是否显示积分说明
  271. showPoints: false,
  272. // 是否显示优惠券弹窗
  273. showPopup: false,
  274. // 订单信息 (从后端api中获取)
  275. order: {},
  276. // 个人信息
  277. personal: {},
  278. // 商城设置
  279. setting: {}
  280. }
  281. },
  282. /**
  283. * 生命周期函数--监听页面加载
  284. */
  285. onLoad(options) {
  286. this.options = options
  287. },
  288. /**
  289. * 生命周期函数--监听页面的卸载
  290. */
  291. onUnload() {
  292. },
  293. /**
  294. * 生命周期函数--监听页面显示
  295. */
  296. onShow() {
  297. // 获取当前订单信息
  298. this.getOrderData()
  299. },
  300. methods: {
  301. // 获取订单数据
  302. getOrderData() {
  303. const app = this
  304. const { options: { mode } } = app
  305. // 请求的参数
  306. const params = app.getRequestParam()
  307. // 请求api
  308. getCheckoutApi(mode)
  309. .order(mode, params)
  310. .then(result => app.initData(result.data))
  311. .catch(err => err)
  312. },
  313. // 初始化数据
  314. initData({ order, setting, personal }) {
  315. const app = this
  316. app.order = order
  317. app.personal = personal
  318. app.setting = setting
  319. // 显示错误信息
  320. if (order.hasError) {
  321. app.showToast(order.errorMsg, 3000)
  322. }
  323. // 当前选择的配送方式
  324. app.curDelivery = order.delivery
  325. // 如果只有一种配送方式则不显示选项卡
  326. app.isShowTab = setting.deliveryType.length > 1
  327. // 是否使用积分抵扣
  328. app.isUsePoints = order.isUsePoints
  329. app.selectCouponId = order.couponId
  330. },
  331. // 获取api请求的参数
  332. getRequestParam() {
  333. const app = this
  334. const { options } = app
  335. // 结算模式的固定参数
  336. const modeParam = getModeParam(options.mode, options)
  337. // 订单结算参数(用户选择)
  338. const orderParam = {
  339. delivery: app.curDelivery || 0,
  340. couponId: app.selectCouponId || 0,
  341. isUsePoints: app.isUsePoints ? 1 : 0,
  342. }
  343. return { ...orderParam, ...modeParam }
  344. },
  345. // 切换配送方式
  346. handleSwichDelivery(key) {
  347. this.curDelivery = key
  348. this.getOrderData()
  349. },
  350. // 显示积分说明
  351. handleShowPoints() {
  352. this.showPoints = true
  353. },
  354. // 显示优惠券弹窗
  355. handleShowPopup() {
  356. this.showPopup = true
  357. },
  358. // 展开优惠券描述
  359. handleDescribe(index) {
  360. const { couponList } = this.order
  361. couponList[index].expand = !couponList[index].expand
  362. },
  363. // 选择优惠券
  364. handleSelectCoupon(index) {
  365. const app = this
  366. const { couponList } = app.order
  367. // 当前选择的优惠券
  368. const couponItem = couponList[index]
  369. // 判断是否在适用范围
  370. if (!couponItem.is_apply) {
  371. app.showToast(couponItem.not_apply_info)
  372. return
  373. }
  374. // 记录选中的优惠券ID
  375. app.selectCouponId = app.selectCouponId == couponItem.user_coupon_id ? 0 : couponItem.user_coupon_id
  376. // 重新获取订单信息
  377. app.getOrderData()
  378. // 隐藏优惠券弹层
  379. app.showPopup = false
  380. },
  381. // 不使用优惠券
  382. handleNotUseCoupon() {
  383. this.selectCouponId = 0
  384. // 重新获取订单信息
  385. this.getOrderData()
  386. // 隐藏优惠券弹层
  387. this.showPopup = false
  388. },
  389. // 快递配送:选择收货地址
  390. onSelectAddress() {
  391. this.$navTo('pages/address/index', { from: 'checkout' })
  392. },
  393. // 跳转到商品详情页
  394. onTargetGoods(goodsId) {
  395. this.$navTo('pages/goods/detail', { goodsId })
  396. },
  397. // 订单提交
  398. onSubmitOrder() {
  399. const app = this
  400. if (app.disabled) {
  401. return false
  402. }
  403. // 表单验证
  404. if (!app.onVerifyFrom()) {
  405. return false
  406. }
  407. // 按钮禁用
  408. app.disabled = true
  409. // 请求api
  410. getCheckoutApi(app.options.mode)
  411. .submit(app.options.mode, app.getFormData())
  412. .then(result => {
  413. // 订单创建成功
  414. const orderId = result.data.orderId
  415. // 判断订单是否已支付: 已支付跳转订单列表 未支付跳转支付页
  416. if (result.data.isPaySuccess) {
  417. app.showToast(result.message, 1500)
  418. setTimeout(() => app.$navTo('pages/order/index', {}, 'redirectTo'), 1500)
  419. } else {
  420. // 订单未支付: 跳转到订单支付页
  421. setTimeout(() => app.$navTo('pages/checkout/cashier/index', { orderId }, 'redirectTo'), 100)
  422. }
  423. })
  424. .catch(res => app.showToast(res.errMsg, 3000))
  425. .finally(() => setTimeout(() => app.disabled = false, 800))
  426. },
  427. // 表单提交的数据
  428. getFormData() {
  429. const app = this
  430. const { options } = app
  431. // 表单数据
  432. const form = {
  433. delivery: app.curDelivery,
  434. couponId: app.selectCouponId || 0,
  435. isUsePoints: app.isUsePoints ? 1 : 0,
  436. remark: app.remark || '',
  437. }
  438. // 获取不同模式的参数
  439. const modeParam = getModeParam(options.mode, options)
  440. return { ...form, ...modeParam }
  441. },
  442. // 表单验证
  443. onVerifyFrom() {
  444. const app = this
  445. if (app.hasError) {
  446. app.showToast(app.errorMsg, 3000)
  447. return false
  448. }
  449. return true
  450. },
  451. // 显示toast信息
  452. showToast(title, duration = 2000) {
  453. this.$refs.uToast.show({ title, duration })
  454. }
  455. }
  456. }
  457. </script>
  458. <style lang="scss" scoped>
  459. @import "./style.scss";
  460. </style>