RightCartDrawer.vue 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351
  1. <template>
  2. <view class="drawer-wrapper" v-if="visible">
  3. <view class="drawer-mask" @click="close"></view>
  4. <view class="drawer-panel" :style="{ width: panelWidth }">
  5. <view class="drawer-header">
  6. <view class="title">
  7. <trans _t="购物车预览" />
  8. <text v-if="cartNum"> ({{ cartNum }})</text>
  9. </view>
  10. <view class="close" @click="close">
  11. <up-icon name="close" size="18" color="var(--text)"></up-icon>
  12. </view>
  13. </view>
  14. <view class="tab">
  15. <Tab
  16. :active="actvieNum"
  17. size="24rpx"
  18. height="80rpx"
  19. :tabList="tabList"
  20. @confirm="tabClick"
  21. />
  22. </view>
  23. <view class="drawer-content">
  24. <shop-model
  25. :list="cartList"
  26. ref="shopRef"
  27. @getIds="getIds"
  28. :isFree="actvieNum"
  29. :key="actvieNum"
  30. />
  31. </view>
  32. <view class="drawer-footer">
  33. <view class="footer_left">
  34. <up-checkbox
  35. :label="t('全部')"
  36. :disabled="!cartList.length"
  37. activeColor="var(--black)"
  38. shape="circle"
  39. labelSize="14"
  40. labelColor="#676969"
  41. iconSize="16"
  42. name="agree"
  43. usedAlone
  44. v-model:checked="selectAllChecked"
  45. @change="selectAllChange"
  46. >
  47. </up-checkbox>
  48. </view>
  49. <view class="footer_right">
  50. <view class="total_info">
  51. <view class="total_price">
  52. <text
  53. >{{ symbol.symbol }} {{ Moneyhtml(money.total_price) }}</text
  54. >
  55. </view>
  56. <view class="total_desc">
  57. <trans _t="199包邮" v-if="actvieNum == 1" />
  58. <trans _t="不包括运费" v-else />
  59. </view>
  60. </view>
  61. <view
  62. class="total_btn"
  63. @click.stop="submit"
  64. :style="{
  65. opacity: canCheckout ? 1 : 0.5,
  66. 'pointer-events': canCheckout ? 'auto' : 'none',
  67. }"
  68. >
  69. <trans _t="去结算" />
  70. </view>
  71. </view>
  72. </view>
  73. </view>
  74. </view>
  75. </template>
  76. <script setup>
  77. import { ref, computed, watch, nextTick } from "vue";
  78. import { useShopStore, useSystemStore, useUserStore } from "@/store";
  79. import { Moneyhtml, systemInfo } from "@/utils";
  80. import shopModel from "./shopModel.vue";
  81. import Tab from "@/components/tabs";
  82. import { t } from "@/locale";
  83. import { onShow } from "@dcloudio/uni-app";
  84. const props = defineProps({
  85. panelWidth: {
  86. type: String,
  87. default: "90vw",
  88. },
  89. });
  90. const emit = defineEmits(["close", "new-action"]);
  91. const visible = ref(false);
  92. const shopRef = ref(null);
  93. const ids = ref([]);
  94. const idsObj = ref({});
  95. const actvieNum = ref(0);
  96. const tabList = ["代购", "包邮"];
  97. const useUser = useUserStore();
  98. const useShop = useShopStore();
  99. const useSystem = useSystemStore();
  100. const symbol = computed(() => useSystem.getSymbol);
  101. const cartList = computed(() => useShop.getCartList);
  102. const cartNum = computed(() => useShop.getCartNum);
  103. const token = computed(() => useUser.getToken);
  104. const selectAllChecked = ref(true);
  105. const top = systemInfo().statusBarHeight || 0;
  106. const statusBarHeightRef = ref(top);
  107. const headerStyle = computed(() => {
  108. return {
  109. paddingTop: `${statusBarHeightRef.value + 10}px`,
  110. };
  111. });
  112. const canCheckout = computed(() => {
  113. if (!ids.value.length) return false;
  114. const total = parseFloat(money.value.total_price || 0);
  115. if (actvieNum.value === 1) {
  116. return total >= 199;
  117. }
  118. return true;
  119. });
  120. const money = computed(() => {
  121. let result = cartList.value.reduce(
  122. (acc, seller) => {
  123. const sellerId = seller.seller_id;
  124. if (idsObj.value[sellerId]) {
  125. const totalPrice = seller.goods
  126. .filter((good) => idsObj.value[sellerId].includes(good.id))
  127. .reduce((sum, good) => sum + parseFloat(good.price) * good.total, 0);
  128. acc.total_price = (acc.total_price - 0 + (totalPrice - 0)).toFixed(2);
  129. const totalNum = seller.goods
  130. .filter((good) => idsObj.value[sellerId].includes(good.id))
  131. .reduce((sum, good) => sum + good.total, 0);
  132. acc.total_num = acc.total_num - 0 + (totalNum - 0);
  133. }
  134. return acc;
  135. },
  136. { total_price: 0, total_num: 0, orginal_price: 0 }
  137. );
  138. return result;
  139. });
  140. watch(
  141. () => cartList.value,
  142. (list) => {
  143. const hasData = Array.isArray(list) && list.length > 0;
  144. if (!hasData) {
  145. selectAllChecked.value = false;
  146. return;
  147. }
  148. nextTick(() => {
  149. selectAllChecked.value = true;
  150. selectAllChange(true);
  151. });
  152. },
  153. { immediate: true, deep: true }
  154. );
  155. const selectAllChange = (e) => {
  156. shopRef.value && shopRef.value.allStatus(e);
  157. };
  158. const open = () => {
  159. visible.value = true;
  160. // 默认全选
  161. nextTick(() => {
  162. selectAllChange(true);
  163. });
  164. };
  165. const close = () => {
  166. visible.value = false;
  167. emit("close");
  168. };
  169. const getIds = (arr, flag) => {
  170. ids.value = Object.values(arr).flat(1);
  171. idsObj.value = arr;
  172. };
  173. const onNewAction = () => {
  174. // 默认行为:关闭抽屉;也向外发出事件供页面自定义
  175. emit("new-action");
  176. close();
  177. };
  178. const tabClick = (item, index) => {
  179. if (actvieNum.value == index) return;
  180. actvieNum.value = index;
  181. useShop.setCartList(index);
  182. };
  183. const submit = () => {
  184. if (!canCheckout.value) return;
  185. close();
  186. uni.navigateTo({
  187. url: `/pages/shop/shopConfirm?comfirmId=${ids.value.join(",")}&isFree=${
  188. actvieNum.value
  189. }`,
  190. });
  191. };
  192. onShow(() => {
  193. token.value && useShop.setCartList(actvieNum.value);
  194. });
  195. defineExpose({ open, close });
  196. </script>
  197. <style lang="less" scoped>
  198. @import url("@/style.less");
  199. .drawer-wrapper {
  200. position: fixed;
  201. inset: 0;
  202. z-index: 9999;
  203. }
  204. .drawer-mask {
  205. position: absolute;
  206. inset: 0;
  207. background: rgba(0, 0, 0, 0.4);
  208. }
  209. .drawer-panel {
  210. position: absolute;
  211. top: 0;
  212. right: 0;
  213. bottom: 0;
  214. width: 90vw;
  215. max-width: 720rpx;
  216. background: var(--bg);
  217. display: flex;
  218. flex-direction: column;
  219. box-shadow: -8rpx 0 24rpx rgba(0, 0, 0, 0.14);
  220. animation: slideIn 0.2s ease;
  221. /* #ifdef APP-PLUS */
  222. padding-top: 88rpx;
  223. /* #endif */
  224. }
  225. @keyframes slideIn {
  226. from {
  227. transform: translateX(100%);
  228. }
  229. to {
  230. transform: translateX(0);
  231. }
  232. }
  233. .drawer-header {
  234. display: flex;
  235. align-items: center;
  236. justify-content: space-between;
  237. padding: 20rpx 24rpx;
  238. background: var(--light);
  239. border-bottom: 1px solid #eee;
  240. .title {
  241. .size(28rpx);
  242. font-weight: 700;
  243. color: var(--text);
  244. }
  245. .close {
  246. padding: 8rpx;
  247. }
  248. }
  249. .drawer-content {
  250. flex: 1;
  251. overflow: hidden auto;
  252. padding: 16rpx;
  253. }
  254. .drawer-footer {
  255. background: var(--light);
  256. border-top: 1px solid #eee;
  257. padding: 16rpx;
  258. display: flex;
  259. align-items: center;
  260. justify-content: space-between;
  261. gap: 16rpx;
  262. padding-bottom: constant(safe-area-inset-bottom);
  263. padding-bottom: env(safe-area-inset-bottom);
  264. .footer_right {
  265. .ver();
  266. .total_info {
  267. .total_price {
  268. text-align: right;
  269. color: var(--red);
  270. .size();
  271. font-weight: 700;
  272. line-height: 48rpx;
  273. }
  274. .total_desc {
  275. color: var(--text-01);
  276. .size(24rpx);
  277. line-height: 40rpx;
  278. text-align: right;
  279. .icon-font {
  280. .size();
  281. }
  282. }
  283. }
  284. .total_btn {
  285. .size(28rpx);
  286. font-weight: 700;
  287. height: 38px;
  288. margin-left: 16rpx;
  289. min-width: 180rpx;
  290. background-color: var(--black);
  291. color: var(--light);
  292. border-radius: 16rpx;
  293. padding: 16rpx 30rpx;
  294. text-align: center;
  295. }
  296. }
  297. .actions {
  298. display: flex;
  299. align-items: center;
  300. gap: 12rpx;
  301. .btn {
  302. .size(26rpx);
  303. font-weight: 700;
  304. height: 72rpx;
  305. padding: 0 28rpx;
  306. border-radius: 16rpx;
  307. display: inline-flex;
  308. align-items: center;
  309. justify-content: center;
  310. white-space: nowrap;
  311. }
  312. .new {
  313. background: #f2f3f5;
  314. color: var(--text);
  315. }
  316. .checkout {
  317. background: var(--black);
  318. color: var(--light);
  319. }
  320. }
  321. }
  322. </style>