select_spec.vue 8.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377
  1. <template>
  2. <popup
  3. @close="close"
  4. ref="popupRef"
  5. isClose
  6. iconSize="16px"
  7. zIndex="998"
  8. iconColor="var(--text)"
  9. >
  10. <template #title>
  11. <view class="product_info">
  12. <image
  13. :src="cur_img || pic_url"
  14. class="cur_img"
  15. @click="previewImage(1, cur_img || pic_url)"
  16. ></image>
  17. <view class="product_price">
  18. <rich-text
  19. class="texts"
  20. :nodes="MoneyAbouthtml((skuItem && skuItem.price) || price, true)"
  21. ></rich-text>
  22. </view>
  23. </view>
  24. </template>
  25. <template #content>
  26. <scroll-view class="sku_info" scroll-y>
  27. <view class="prop_wrapper" v-for="(val, num) in listArr" :key="val">
  28. <view class="spec_title">{{ val.name }}</view>
  29. <view class="spec_items">
  30. <view
  31. class="spec_item"
  32. :class="(listActive[num] || 0) == index ? 'active' : ''"
  33. v-for="(item, index) in val.spec"
  34. :key="index"
  35. @click="listClick(num, item, index)"
  36. >
  37. <image
  38. :src="item.specUrl"
  39. v-if="item.specUrl"
  40. class="img"
  41. ></image>
  42. <view class="text" v-else>{{ item.specName }}</view>
  43. </view>
  44. </view>
  45. </view>
  46. <view class="prop_wrapper">
  47. <view class="spec_title">
  48. <trans _t="数量" />
  49. </view>
  50. <view class="spec_items decrease">
  51. <up-number-box
  52. v-model="total"
  53. :min="1"
  54. :max="skuItem.quantity"
  55. @change="totalChange"
  56. ></up-number-box>
  57. <view class="inventory">
  58. <trans _t="库存" />:{{ skuItem.quantity }}
  59. </view>
  60. </view>
  61. <view class="least_purchase">
  62. <trans _t="最小购买数量" />
  63. </view>
  64. </view>
  65. </scroll-view>
  66. </template>
  67. <template #footer>
  68. <view
  69. class="footer_btn"
  70. @click="btnClick"
  71. :style="{
  72. opacity: canBuyNow ? 1 : 0.5,
  73. 'pointer-events': canBuyNow ? 'auto' : 'none',
  74. }"
  75. >
  76. <trans :_t="btnText == 'add' ? '加入购物车' : '立即购买'" />
  77. </view>
  78. </template>
  79. </popup>
  80. </template>
  81. <script setup>
  82. import popup from "@/components/popup";
  83. import { ref, computed } from "vue";
  84. import { useSystemStore } from "@/store";
  85. import { Moneyhtml, Toast, MoneyAbouthtml } from "@/utils";
  86. import { SHOP_ADD_CART, SHOP_CART_CONFIRM } from "@/api";
  87. import { useShopStore } from "@/store";
  88. const useShop = useShopStore();
  89. const props = defineProps({
  90. pic_url: String,
  91. props_spec: {
  92. type: Object,
  93. default: () => ({}),
  94. },
  95. price: {
  96. type: [String, Number],
  97. default: 0,
  98. },
  99. sku: {
  100. type: Object,
  101. default: () => [],
  102. },
  103. channel: {
  104. type: [String, Number],
  105. default: 0,
  106. },
  107. goods_id: {
  108. type: [String, Number],
  109. default: 0,
  110. },
  111. isFree: {
  112. type: [String, Number],
  113. default: 0,
  114. },
  115. });
  116. const emit = defineEmits(["open", "close"]);
  117. const total = ref(1);
  118. const cur_img = ref(props.pic_url);
  119. const listActive = ref([]);
  120. const btnText = ref("add");
  121. const listArr = computed(() => {
  122. const arr = Object.values(props.props_spec);
  123. listActive.value = new Array(arr.length).fill(0);
  124. return arr;
  125. });
  126. const shopIds = computed(() => {
  127. let arr = [];
  128. listActive.value.forEach((ele, num) => {
  129. let item = listArr.value[num].spec[ele];
  130. let str = `${item.pid}:${item.specId}`;
  131. arr.push(str);
  132. });
  133. arr.join(";");
  134. return arr.join(";");
  135. });
  136. const skuItem = computed(() => {
  137. let findItem = props.sku.find((item) => item.properties == shopIds.value);
  138. return findItem;
  139. });
  140. const useSystem = useSystemStore();
  141. const popupRef = ref(null);
  142. const symbol = computed(() => useSystem.getSymbol);
  143. // 是否可以立即购买(isFree==1 时需满199)
  144. const canBuyNow = computed(() => {
  145. if (btnText.value !== "buy") return true;
  146. if (Number(props.isFree) !== 1) return true;
  147. const unitPrice = parseFloat(
  148. (skuItem.value && skuItem.value.price) || props.price || 0
  149. );
  150. const totalPrice = unitPrice * (total.value || 1);
  151. return totalPrice >= 199;
  152. });
  153. const totalChange = (e) => {};
  154. const listClick = (num, item, index) => {
  155. let new_listActive = listActive.value;
  156. new_listActive[num] = index;
  157. listActive.value = new_listActive;
  158. if (item.specUrl) {
  159. cur_img.value = item.specUrl;
  160. }
  161. };
  162. const btnClick = () => {
  163. if (!canBuyNow.value) {
  164. Toast("包邮通道需满199才可立即购买");
  165. return;
  166. }
  167. addCart();
  168. };
  169. const close = () => {
  170. popupRef.value && popupRef.value.close();
  171. emit("close");
  172. };
  173. const open = (value = "add") => {
  174. btnText.value = value;
  175. popupRef.value && popupRef.value.open();
  176. };
  177. const addCart = async () => {
  178. try {
  179. const res = await SHOP_ADD_CART({
  180. channel: props.channel,
  181. goods_id: props.goods_id,
  182. sku_id: skuItem.value.sku_id,
  183. total: total.value,
  184. });
  185. useShop.setCartNum(res.data.cartCount);
  186. if (btnText.value == "buy") {
  187. uni.navigateTo({
  188. url: `/pages/shop/shopConfirm?comfirmId=${res.data.cartId}&isFree=${props.isFree}`,
  189. });
  190. } else {
  191. close();
  192. }
  193. } catch (error) {
  194. Toast(error.msg);
  195. }
  196. };
  197. function previewImage(index, urls) {
  198. uni.previewImage({
  199. current: index,
  200. urls: [urls],
  201. success() {},
  202. });
  203. }
  204. defineExpose({
  205. open,
  206. close,
  207. });
  208. </script>
  209. <style lang="less" scoped>
  210. @import url("@/style.less");
  211. /deep/ .pop {
  212. .top {
  213. align-items: unset;
  214. padding-bottom: 24rpx;
  215. }
  216. .conts {
  217. padding-top: 0;
  218. }
  219. }
  220. .uni-preview-container {
  221. z-index: 99999 !important;
  222. /* 确保高于其他元素 */
  223. }
  224. .product_info {
  225. .flex();
  226. .cur_img {
  227. width: 144rpx;
  228. height: 144rpx;
  229. border-radius: 8rpx;
  230. margin-right: 24rpx;
  231. }
  232. .product_price {
  233. color: var(--red);
  234. .size(40rpx);
  235. font-weight: 700;
  236. .flex();
  237. .texts {
  238. margin-left: 8rpx;
  239. /deep/ div {
  240. .ver(baseline);
  241. .decimal {
  242. .size(24rpx);
  243. }
  244. }
  245. }
  246. }
  247. }
  248. .sku_info {
  249. .prop_wrapper {
  250. margin-top: 8rpx;
  251. .spec_title {
  252. .ver();
  253. color: var(--text);
  254. .size(28rpx);
  255. font-weight: 700;
  256. line-height: 40rpx;
  257. min-height: 60rpx;
  258. }
  259. .spec_items {
  260. .flex();
  261. flex-wrap: wrap;
  262. gap: 16rpx;
  263. margin-top: 24rpx;
  264. .spec_item {
  265. border: var(--bor);
  266. color: var(--text);
  267. border-radius: 8rpx;
  268. overflow: hidden;
  269. .img {
  270. width: 88rpx;
  271. height: 88rpx;
  272. display: block;
  273. }
  274. .text {
  275. .size(28rpx);
  276. line-height: 60rpx;
  277. padding: 0 24rpx;
  278. }
  279. }
  280. .active {
  281. border-color: var(--black);
  282. color: var(--light);
  283. background-color: var(--black);
  284. }
  285. }
  286. .decrease {
  287. .ver();
  288. /deep/ .u-number-box {
  289. border: 1px solid #dcdfe6;
  290. border-radius: 16rpx;
  291. .u-number-box__minus,
  292. .u-number-box__plus {
  293. width: 76rpx !important;
  294. height: 76rpx !important;
  295. background-color: transparent !important;
  296. .u-icon__icon {
  297. .size(26rpx) !important;
  298. color: var(--text) !important;
  299. }
  300. &--hover {
  301. .u-icon__icon {
  302. color: var(--primary) !important;
  303. }
  304. }
  305. }
  306. .u-number-box__input {
  307. margin: 0;
  308. border-left: 1px solid #dcdfe6;
  309. border-right: 1px solid #dcdfe6;
  310. height: 76rpx !important;
  311. width: 160rpx !important;
  312. background-color: transparent !important;
  313. }
  314. }
  315. .inventory {
  316. color: var(--text);
  317. margin-left: 8rpx;
  318. .size(28rpx);
  319. line-height: 60rpx;
  320. }
  321. }
  322. .least_purchase {
  323. color: var(--text-01);
  324. margin-top: 8rpx;
  325. .size(28rpx);
  326. line-height: 60rpx;
  327. }
  328. }
  329. }
  330. .footer_btn {
  331. background-color: var(--black);
  332. color: var(--light);
  333. border-radius: 20rpx;
  334. padding: 16rpx 30rpx;
  335. .flex_center();
  336. .size();
  337. font-weight: bold;
  338. height: 100rpx;
  339. }
  340. </style>