confirm-order.vue 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451
  1. <template>
  2. <view class="confirm-order">
  3. <!-- 收货地址部分 -->
  4. <view class="section-card">
  5. <view class="flex-a flex-j-b mb-20" @click="handleAddress" v-if="defaultAddr.id">
  6. <image src="/pages-mine/static/adderss.png" style="width: 40rpx; height: 40rpx"></image>
  7. <view class="flex-d flex-1 ml-24" style="margin-right: 90rpx">
  8. <view class="flex-a flex-j-b mb-10">
  9. <view :style="titleStyle">收货人:{{ defaultAddr.name }}</view>
  10. <view :style="titleStyle">{{ defaultAddr.mobile }}</view>
  11. </view>
  12. <view :style="titleStyle">地址:{{ defaultAddr.fullAddress }}</view>
  13. </view>
  14. <u-icon name="arrow-right" :size="28" color="#666" top="4"></u-icon>
  15. </view>
  16. <view class="flex-a flex-j-b" @click="handleAddress" v-else>
  17. <view class="flex-a">
  18. <u-icon name="plus-circle-fill" :size="48" color="#38C148" top="2"></u-icon>
  19. <view :style="titleStyle" class="ml-10 font-30">添加收货地址</view>
  20. <text class="u-required">*</text>
  21. </view>
  22. <view class="flex-a">
  23. <view :style="titleStyle" class="ml-10">请添加</view>
  24. <u-icon name="arrow-right" :size="28" color="#666" top="4"></u-icon>
  25. </view>
  26. </view>
  27. </view>
  28. <!-- 商品列表 -->
  29. <view class="book-list">
  30. <buy-book-item v-for="(book, index) in books" :key="index" :book="book"></buy-book-item>
  31. </view>
  32. <!-- 订单信息 -->
  33. <view class="section-card mt-20" style="padding: 0; overflow: hidden;">
  34. <u-cell-group :border="false">
  35. <u-cell-item title="配送服务" :value="!preOrder.expressMoney ? '快递 免邮' : '快递费 ' + preOrder.expressMoney + '元'" :arrow="false"
  36. :title-style="{ color: '#333' }" :value-style="{ color: '#333' }"></u-cell-item>
  37. <u-cell-item title="红包" :value="redPacketText" @click="showRedPacket = true"
  38. :title-style="{ color: '#333' }"></u-cell-item>
  39. <u-cell-item title="订单备注" :arrow="false" :title-style="{ color: '#333' }">
  40. <u-input slot="right-icon" v-model="submitData.remark" type="text" placeholder="无备注"
  41. input-align="right" :custom-style="{ color: '#999', minHeight: '28px' }" />
  42. </u-cell-item>
  43. <u-cell-item title="如遇缺货" :value="selectedStockOption.text || '其他商品继续发货'"
  44. @click="showStockShortage = true" :border-bottom="false"
  45. :title-style="{ color: '#ff4500', fontWeight: 'bold' }"></u-cell-item>
  46. </u-cell-group>
  47. </view>
  48. <!-- 底部栏 -->
  49. <view class="bottom-bar">
  50. <view class="order-summary">
  51. <view class="total">
  52. 共<text class="num">{{ preOrder.totalQuantity }}</text>本
  53. <text class="reduce-text" v-if="totalDiscountAmount > 0" @click="openDiscountDetail">已降¥{{ totalDiscountAmount }}</text>
  54. 合计: <text class="price">¥{{ finalTotalMoney }}</text>
  55. </view>
  56. <u-button type="primary" shape="circle" @click="submitOrder">提交订单</u-button>
  57. </view>
  58. </view>
  59. <submit-confirm ref="submitConfirmDialog" @confirm="handleConfirmSubmit"></submit-confirm>
  60. <red-packet-popup v-model="showRedPacket" :list="preOrder.couponList"
  61. @confirm="onRedPacketConfirm"></red-packet-popup>
  62. <stock-shortage-popup v-model="showStockShortage" @confirm="onStockShortageConfirm"></stock-shortage-popup>
  63. <!-- 优惠明细弹窗 -->
  64. <u-popup v-model="showDiscountDetail" mode="bottom" border-radius="24">
  65. <view class="discount-popup">
  66. <view class="popup-header">
  67. <text>优惠明细</text>
  68. <u-icon name="close" size="28" color="#999" class="close-icon" @click="showDiscountDetail = false"></u-icon>
  69. </view>
  70. <view class="discount-list">
  71. <view class="discount-item" v-for="(item, index) in discountDetails" :key="index">
  72. <text class="label">{{ item.label }}</text>
  73. <text class="amount">-¥{{ item.amount }}</text>
  74. </view>
  75. </view>
  76. <view class="popup-footer">
  77. <u-button type="primary" shape="circle" @click="showDiscountDetail = false">确认</u-button>
  78. </view>
  79. </view>
  80. </u-popup>
  81. </view>
  82. </template>
  83. <script>
  84. import SubmitConfirm from "@/pages-car/components/submit-confirm.vue";
  85. import BuyBookItem from "@/pages-car/components/buy-book-item.vue";
  86. import RedPacketPopup from "@/pages-car/components/red-packet-popup.vue";
  87. import StockShortagePopup from "@/pages-car/components/stock-shortage-popup.vue";
  88. export default {
  89. components: {
  90. SubmitConfirm,
  91. BuyBookItem,
  92. RedPacketPopup,
  93. StockShortagePopup
  94. },
  95. data() {
  96. return {
  97. titleStyle: {
  98. "font-family": "PingFang SC",
  99. "font-weight": 400,
  100. color: "#333333",
  101. },
  102. books: [],
  103. defaultAddr: {},
  104. submitData: {
  105. cartIdList: [],
  106. addressId: "",
  107. remark: "",
  108. outOfStock: "2", // 默认为2
  109. userCouponIds: []
  110. },
  111. totalNum: 0,
  112. totalPrice: 0,
  113. totalReduced: 0,
  114. btnStyle: {
  115. width: '240rpx',
  116. height: '80rpx',
  117. lineHeight: '80rpx',
  118. background: 'linear-gradient(90deg, #ff8c00, #ff4500)',
  119. color: '#fff',
  120. fontSize: '30rpx',
  121. margin: '0'
  122. },
  123. showRedPacket: false,
  124. showStockShortage: false,
  125. showDiscountDetail: false,
  126. selectedPacket: null,
  127. selectedStockOption: { text: '其他商品继续发货(缺货商品退款)', value: 2 },
  128. preOrder: {},// 预订单信息
  129. };
  130. },
  131. computed: {
  132. redPacketText() {
  133. if (!this.preOrder.couponList || this.preOrder.couponList.length === 0) {
  134. return '暂无红包';
  135. }
  136. return this.selectedPacket ? this.selectedPacket.couponName : '请选择';
  137. },
  138. finalTotalMoney() {
  139. let total = parseFloat(this.preOrder.totalMoney || 0);
  140. if (this.selectedPacket && this.selectedPacket.faceMoney) {
  141. total -= parseFloat(this.selectedPacket.faceMoney);
  142. }
  143. // 减去分享降价金额(如果不在totalMoney中扣除的话,通常totalMoney是已经扣除了活动优惠的,但红包和分享降价可能需要客户端处理,
  144. // 不过根据截图,totalReduceMoney是单独字段,这里假设preOrder.totalMoney只扣除了discountMoney,
  145. // 如果后端已经处理了totalReduceMoney,则不需要减。但通常前端展示需要一致。
  146. // 假设totalMoney是应付金额(不含红包),是否含分享降价需确认。
  147. // 通常preOrder.totalMoney是经过服务器计算的"折后价"(扣除了discountMoney)。
  148. // 这里暂时保持原逻辑:只减红包。如果分享降价也需要客户端减,请根据实际情况调整。
  149. // 假设 totalMoney 已经包含了 discountMoney 的扣减,但不包含红包。
  150. // 关于分享降价,通常也是作为一种支付前扣减。如果后端没扣,这里可能需要减。
  151. // 鉴于截图preOrder中有totalReduceMoney,且totalMoney为29.92。
  152. // 保险起见,先维持原逻辑(只减红包),除非用户明确说totalMoney没扣分享降价。
  153. // 但用户说"选中的红包减去的金额也要加上...还要再加上分享降价的金额",这是指展示的"已降"金额。
  154. return total < 0 ? '0.00' : total.toFixed(2);
  155. },
  156. totalDiscountAmount() {
  157. let total = 0;
  158. // 1. 活动优惠
  159. if (this.preOrder.discountMoney) {
  160. total += parseFloat(this.preOrder.discountMoney);
  161. }
  162. // 2. 红包
  163. if (this.selectedPacket && this.selectedPacket.faceMoney) {
  164. total += parseFloat(this.selectedPacket.faceMoney);
  165. }
  166. // 3. 分享降价
  167. if (this.preOrder.totalReduceMoney) {
  168. total += parseFloat(this.preOrder.totalReduceMoney);
  169. }
  170. return total.toFixed(2);
  171. },
  172. discountDetails() {
  173. const list = [];
  174. // 1. 惊喜红包 (红包)
  175. if (this.selectedPacket && this.selectedPacket.faceMoney > 0) {
  176. list.push({
  177. label: '惊喜红包',
  178. amount: parseFloat(this.selectedPacket.faceMoney).toFixed(2)
  179. });
  180. }
  181. // 2. 分享降价
  182. if (this.preOrder.totalReduceMoney > 0) {
  183. list.push({
  184. label: '分享降价',
  185. amount: parseFloat(this.preOrder.totalReduceMoney).toFixed(2)
  186. });
  187. }
  188. // 3. 活动优惠 (每满减等)
  189. if (this.preOrder.discountMoney > 0) {
  190. list.push({
  191. label: this.preOrder.discountDesc || '活动优惠',
  192. amount: parseFloat(this.preOrder.discountMoney).toFixed(2)
  193. });
  194. }
  195. return list;
  196. }
  197. },
  198. onLoad(options) {
  199. // 从本地存储获取提交订单数据
  200. const preSubmitOrderData = uni.getStorageSync('preSubmitOrderData');
  201. if (preSubmitOrderData) {
  202. this.preOrder = preSubmitOrderData;
  203. this.books = preSubmitOrderData.orderDetailList || [];
  204. this.defaultAddr = preSubmitOrderData.defaultAddress || {};
  205. //拼接提交数据
  206. this.submitData.cartIdList = preSubmitOrderData.cartIdList || [];
  207. this.submitData.addressId = this.defaultAddr.id || "";
  208. } else {
  209. this.$u.toast('参数错误');
  210. setTimeout(() => uni.navigateBack(), 1500);
  211. }
  212. },
  213. methods: {
  214. //添加或者选择地址
  215. handleAddress() {
  216. uni.navigateTo({
  217. url: `/pages-mine/pages/address/list?id=${this.defaultAddr.id}&isSelect=1`,
  218. });
  219. },
  220. onRedPacketConfirm(packet) {
  221. this.selectedPacket = packet;
  222. if (packet && packet.userCouponId) {
  223. this.submitData.userCouponIds = [packet.userCouponId];
  224. } else {
  225. this.submitData.userCouponIds = [];
  226. }
  227. },
  228. onStockShortageConfirm(option) {
  229. this.selectedStockOption = option;
  230. this.submitData.outOfStock = option.value;
  231. },
  232. submitOrder() {
  233. if (!this.submitData.addressId) {
  234. this.$u.toast("请选择收货地址");
  235. return;
  236. }
  237. // 显示确认弹窗
  238. this.$refs.submitConfirmDialog.openPopup();
  239. },
  240. openDiscountDetail() {
  241. this.showDiscountDetail = true;
  242. },
  243. // 确认提交订单
  244. handleConfirmSubmit() {
  245. uni.showLoading({ title: '提交中' });
  246. this.$u.api.submitShopOrderAjax(this.submitData).then((res) => {
  247. uni.hideLoading();
  248. if (res.code == 200) {
  249. uni.showToast({
  250. title: '下单成功',
  251. icon: "success",
  252. });
  253. uni.removeStorageSync("selectAddr");
  254. //存储订单信息
  255. uni.setStorageSync("orderBuyInfo", res.data);
  256. //跳转到 收银台页面
  257. uni.navigateTo({
  258. url: `/pages-car/pages/cashier-desk?id=${res.data.orderId}`
  259. })
  260. } else {
  261. this.$u.toast(res.msg || '下单失败');
  262. }
  263. }).catch(() => {
  264. uni.hideLoading();
  265. });
  266. },
  267. },
  268. onShow() {
  269. let selectAddr = uni.getStorageSync("selectAddr");
  270. if (selectAddr) {
  271. this.defaultAddr = selectAddr;
  272. this.submitData.addressId = selectAddr.id;
  273. uni.removeStorageSync("selectAddr"); // Clear after use
  274. }
  275. },
  276. };
  277. </script>
  278. <style lang="scss">
  279. .confirm-order {
  280. min-height: 100vh;
  281. background: #f5f5f5;
  282. padding: 20rpx 30rpx;
  283. padding-bottom: calc(env(safe-area-inset-bottom) + 120rpx);
  284. }
  285. .section-card {
  286. background: #fff;
  287. margin-bottom: 20rpx;
  288. padding: 30rpx;
  289. border-radius: 16rpx;
  290. box-sizing: border-box;
  291. }
  292. .u-required {
  293. color: #ff0000;
  294. margin-left: 8rpx;
  295. }
  296. .book-list {
  297. background: #fff;
  298. border-radius: 16rpx;
  299. margin-bottom: 20rpx;
  300. }
  301. .info-row {
  302. display: flex;
  303. justify-content: space-between;
  304. align-items: center;
  305. padding: 20rpx 0;
  306. font-size: 28rpx;
  307. color: #333;
  308. .label {
  309. &.red-label {
  310. color: #ff4500;
  311. font-weight: bold;
  312. }
  313. }
  314. .value {
  315. &.text-gray {
  316. color: #999;
  317. }
  318. }
  319. .mr-10 {
  320. margin-right: 10rpx;
  321. }
  322. }
  323. .bottom-bar {
  324. position: fixed;
  325. bottom: 0;
  326. left: 0;
  327. right: 0;
  328. background: #fff;
  329. padding: 20rpx 30rpx;
  330. padding-bottom: env(safe-area-inset-bottom);
  331. box-shadow: 0 -2rpx 10rpx rgba(0, 0, 0, 0.05);
  332. z-index: 9;
  333. .order-summary {
  334. display: flex;
  335. justify-content: space-between;
  336. align-items: center;
  337. .total {
  338. font-size: 28rpx;
  339. color: #666;
  340. .num {
  341. color: #333;
  342. font-weight: bold;
  343. margin: 0 4rpx;
  344. }
  345. .reduce-text {
  346. color: #38C148;
  347. margin-left: 10rpx;
  348. margin-right: 10rpx;
  349. background-color: #e6f9e9;
  350. padding: 4rpx 10rpx;
  351. border-radius: 4rpx;
  352. font-size: 24rpx;
  353. }
  354. .price {
  355. font-size: 36rpx;
  356. color: #ff4500;
  357. font-weight: bold;
  358. margin-left: 10rpx;
  359. }
  360. }
  361. }
  362. }
  363. .mt-20 {
  364. margin-top: 20rpx;
  365. }
  366. .ml-10 {
  367. margin-left: 10rpx;
  368. }
  369. .discount-popup {
  370. padding: 30rpx;
  371. background-color: #fff;
  372. .popup-header {
  373. display: flex;
  374. justify-content: center;
  375. align-items: center;
  376. position: relative;
  377. padding-bottom: 30rpx;
  378. border-bottom: 1rpx solid #eee;
  379. font-size: 32rpx;
  380. font-weight: bold;
  381. color: #333;
  382. .close-icon {
  383. position: absolute;
  384. right: 0;
  385. top: 0;
  386. }
  387. }
  388. .discount-list {
  389. padding: 30rpx 0;
  390. .discount-item {
  391. display: flex;
  392. justify-content: space-between;
  393. align-items: center;
  394. padding: 20rpx 0;
  395. font-size: 28rpx;
  396. .label {
  397. color: #333;
  398. }
  399. .amount {
  400. color: #ff4500;
  401. font-weight: bold;
  402. }
  403. }
  404. }
  405. .popup-footer {
  406. margin-top: 20rpx;
  407. padding-bottom: env(safe-area-inset-bottom);
  408. }
  409. }
  410. </style>