spec-popup.vue 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417
  1. <template>
  2. <u-popup v-model="showPop" mode="bottom" border-radius="14" :closeable="true" @close="onClose"
  3. :safe-area-inset-bottom="true">
  4. <view class="bg-white spec-contain">
  5. <view class="header flex">
  6. <u-image width="160rpx" height="160rpx" class="m-r-20" border-radius="10rpx"
  7. @click="previewImage(checkedGoods.image)" :src="checkedGoods.image"></u-image>
  8. <view class="goods-info">
  9. <view class="primary flex">
  10. <price-format :first-size="46" :second-size="32" :subscript-size="32"
  11. :price="group ? checkedGoods.team_price : checkedGoods.price" :weight="500" />
  12. <view class="vip-price flex" v-if="!group && !isSeckill && checkedGoods.member_price">
  13. <view class="price-name xxs">会员价</view>
  14. <view style="padding: 0 11rpx">
  15. <price-format :price="checkedGoods.member_price" :first-size="22" :second-size="22"
  16. :subscript-size="22" :weight="500" color="#7B3200" />
  17. </view>
  18. </view>
  19. </view>
  20. <view class="sm" v-if="showStock">
  21. 库存:{{checkedGoods.stock}}件
  22. </view>
  23. <view class="sm m-t-10">
  24. <text>{{specValueText}}</text>
  25. </view>
  26. </view>
  27. </view>
  28. <view class="spec-main" style="height: 550rpx;">
  29. <scroll-view style="max-height: 500rpx;" scroll-y="true">
  30. <view class="spec-list">
  31. <view v-for="(item, index) in specList" :key="index" class="spec">
  32. <view class="name m-b-20">
  33. {{ item.name }}
  34. <text
  35. class="primary xs m-l-20">{{checkedGoods.spec_value_ids_arr[index] == '' ? '请选择'+item.name:''}}</text>
  36. </view>
  37. <view class="flex flex-wrap">
  38. <view v-for="(specitem, index2) in item.spec_value" :key="index2" :class="'spec-item sm ' +
  39. ( checkedGoods.spec_value_ids_arr[index] == specitem.id ? 'checked ' : '' ) +
  40. ( checkedGoods.spec_value_ids_arr[index] != specitem.id && isDisable(specitem.id) ? 'unspec-disabled ':'') +
  41. ( isDisable(specitem.id) ? 'spec-disabled':'')" @click="choseSpecItem(index, index2)">
  42. {{ specitem.value }}
  43. </view>
  44. </view>
  45. </view>
  46. </view>
  47. </scroll-view>
  48. <view class="good-num flex row-between m-l-20 m-r-20">
  49. <view class="label">数量</view>
  50. <u-number-box v-if="show" v-model="goodsNum" :min="1" :max="checkedGoods.stock"></u-number-box>
  51. </view>
  52. </view>
  53. <view v-if="shop.is_pay" class="btns flex row-between bg-white" :class="specValueText.indexOf('请选择') != -1? 'disabled':''">
  54. <template v-if="checkedGoods.stock != 0">
  55. <button v-if="showAdd && type==0" class="add-cart br60 white btn" size="lg"
  56. @click="onClick('addcart')">{{yellowBtnText}}</button>
  57. <button v-if="showBuy" class="bg-primary br60 white btn" size="lg"
  58. @click="onClick('buynow')">{{redBtnText}}</button>
  59. <button v-if="showConfirm" class="bg-primary br60 white btn" size="lg"
  60. @click="onClick('confirm')">确定</button>
  61. </template>
  62. <button v-else class="bg-gray br60 white btn" size="lg">缺货</button>
  63. </view>
  64. </view>
  65. </u-popup>
  66. </template>
  67. <script>
  68. export default {
  69. data() {
  70. return {
  71. shop: {},
  72. checkedGoods: {
  73. stock: 0
  74. }, //选中的
  75. outOfStock: [], //缺货的
  76. specList: [], //规格
  77. disable: [], //不可选择的
  78. goodsNum: 1,
  79. showPop: false
  80. };
  81. },
  82. props: {
  83. show: {
  84. type: Boolean
  85. },
  86. goods: {
  87. type: Object
  88. },
  89. showAdd: {
  90. type: Boolean,
  91. default: true
  92. },
  93. showBuy: {
  94. type: Boolean,
  95. default: true
  96. },
  97. showConfirm: {
  98. type: Boolean,
  99. default: false
  100. },
  101. redBtnText: {
  102. type: String,
  103. default: "立即购买"
  104. },
  105. yellowBtnText: {
  106. type: String,
  107. default: "加入购物车"
  108. },
  109. showStock: {
  110. type: Boolean,
  111. default: true
  112. },
  113. group: {
  114. type: Boolean,
  115. default: false
  116. },
  117. isSeckill: {
  118. type: Boolean,
  119. default: false
  120. },
  121. // type 是用来判断是什么类型的商品0=实物商品,1=虚拟商品
  122. type: {
  123. }
  124. },
  125. computed: {
  126. // 选择的规格参数等
  127. specValueText() {
  128. let arr = this.checkedGoods.spec_value_ids?.split(',');
  129. let spec_str = ''
  130. if (arr)
  131. arr.forEach((item, index) => {
  132. if (item == '') spec_str += this.specList[index].name + ','
  133. })
  134. if (this.checkedGoods?.stock != 0 && spec_str == '')
  135. return `已选择 ${this.checkedGoods.spec_value_str} ${this.goodsNum} 件`
  136. else if(this.checkedGoods?.stock != 0 && spec_str.length)
  137. return `请选择 ${spec_str.slice(0, spec_str.length - 1)}`
  138. else return `库存不足`
  139. }
  140. },
  141. watch: {
  142. // 初始化
  143. goods(value) {
  144. this.specList = value.goods_spec || [];
  145. let goodsItem = value.goods_item || [];
  146. this.shop = value.shop || {}
  147. this.outOfStock = goodsItem.filter(item => item.stock == 0)
  148. // 找出库存不为0的
  149. const resultArr = goodsItem.filter(item => item.stock != 0)
  150. if (resultArr.length != 0) {
  151. resultArr[0].spec_value_ids_arr = resultArr[0].spec_value_ids.split(',');
  152. this.checkedGoods = resultArr[0]
  153. } else {
  154. // 无法选择
  155. goodsItem[0].spec_value_ids_arr = []
  156. this.disable = goodsItem.map(item => item.spec_value_ids.split(','));
  157. this.checkedGoods = goodsItem[0]
  158. }
  159. },
  160. // 规格列表变更时
  161. specList(value) {
  162. const res = this.goods.goods_item.filter(item => this.checkedGoods.spec_value_ids === item.spec_value_ids)
  163. if (res.length != 0) {
  164. const result = JSON.parse(JSON.stringify(res[0]))
  165. result.spec_value_ids_arr = result.spec_value_ids.split(',')
  166. this.checkedGoods = result;
  167. // 同步到父组件
  168. this.$emit('change', {
  169. detail: this.checkedGoods
  170. });
  171. }
  172. // 当前选择的规格ID数组
  173. const idsArr = this.checkedGoods.spec_value_ids_arr;
  174. // 缺货规格列表
  175. const outOfStock = this.outOfStock;
  176. // 计算当前规格与缺货规格匹配
  177. const getArrGather = this.getArrResult(idsArr, outOfStock);
  178. // 计算出缺货的规格项
  179. this.disable = this.getOutOfStockArr(getArrGather, idsArr)
  180. },
  181. show(val) {
  182. this.showPop = val
  183. }
  184. },
  185. created() {
  186. console.log('spec')
  187. },
  188. methods: {
  189. isDisable(spec_id) {
  190. // 如果有禁用的就直接返回
  191. if(this.disable.includes(spec_id)) return true
  192. // 是否全选的缺货
  193. const checked = this.checkedGoods;
  194. const isLess = this.goods.goods_item.filter(item => {
  195. return checked.stock == 0 && checked.spec_value_ids === item.spec_value_ids
  196. })
  197. if(isLess.length) {
  198. return checked.spec_value_ids_arr.includes(spec_id+"")
  199. }
  200. },
  201. onClose() {
  202. this.$emit('close')
  203. },
  204. onClick(type) {
  205. let {
  206. checkedGoods,
  207. goodsNum
  208. } = this;
  209. if(this.specValueText.indexOf('请选择') != -1) return this.$toast({
  210. title: this.specValueText
  211. })
  212. if(checkedGoods.stock == 0) return this.$toast({
  213. title: '当前选择库存不足'
  214. })
  215. checkedGoods.goodsNum = goodsNum;
  216. this.$emit(type, {
  217. detail: checkedGoods
  218. });
  219. },
  220. // 选择规格
  221. choseSpecItem(index, index2) {
  222. const id = this.specList[index].spec_value[index2].id;
  223. let idsArr = this.checkedGoods.spec_value_ids_arr;
  224. if (id == idsArr[index]) idsArr[index] = ''
  225. else idsArr[index] = id;
  226. //保存已选规格
  227. this.checkedGoods.spec_value_ids_arr = idsArr;
  228. this.checkedGoods.spec_value_ids = idsArr.join(',');
  229. // 重新渲染页面
  230. this.specList = [...this.specList]
  231. },
  232. // 过滤出需要进行禁用的规格
  233. getOutOfStockArr(arr, arr1, result = []) {
  234. arr.forEach(el => {
  235. if (el.num >= (arr1.length - 1)) result.push(...el.different)
  236. })
  237. return result
  238. },
  239. // 匹配出缺货库存和已选中对比结果
  240. getArrIdentical(arr1, arr2, arr = [], num = 0) {
  241. arr1.forEach(el => {
  242. arr2.forEach(el2 => {
  243. if (el == el2) {
  244. num += 1;
  245. arr.push(el)
  246. }
  247. })
  248. });
  249. return {
  250. num, //n个相同的
  251. different: this.getArrDifference([...new Set(arr)].map(Number), arr2.map(Number)),
  252. identical: [...new Set(arr)]
  253. }
  254. },
  255. // 匹配出已选择和缺库存的
  256. getArrResult(arr, outOfStock, result = []) {
  257. outOfStock.forEach(item => {
  258. const res = this.getArrIdentical(arr, item.spec_value_ids.split(','))
  259. if(res != undefined && res.length != 0) result.push(res);
  260. })
  261. return result
  262. },
  263. // 找出两个数组中参数不同的
  264. getArrDifference(arr1, arr2) {
  265. return arr1.concat(arr2).filter(function(v, i, arr) {
  266. return arr.indexOf(v) == arr.lastIndexOf(v);
  267. });
  268. },
  269. // 查看商品图片
  270. previewImage(current) {
  271. uni.previewImage({
  272. current,
  273. urls: [current] // 需要预览的图片http链接列表
  274. });
  275. }
  276. }
  277. };
  278. </script>
  279. <style lang="scss" scoped>
  280. .spec-contain {
  281. border-radius: 10rpx 10rpx 0 0;
  282. overflow: hidden;
  283. position: relative;
  284. .close {
  285. position: absolute;
  286. right: 10rpx;
  287. top: 10rpx;
  288. }
  289. .header {
  290. padding: 30rpx;
  291. padding-right: 70rpx;
  292. align-items: flex-start;
  293. border: $-solid-border;
  294. }
  295. .spec-main {
  296. padding: 0 24rpx;
  297. .spec-list {
  298. padding: 30rpx 20rpx;
  299. .spec-item {
  300. line-height: 54rpx;
  301. padding: 0 30rpx;
  302. background-color: #F4F4F4;
  303. border-radius: 60rpx;
  304. margin: 0 20rpx 20rpx 0;
  305. border: 1px solid #F4F4F4;
  306. &.checked {
  307. background-color: #FFE9EB;
  308. color: $-color-primary;
  309. border-color: currentColor;
  310. }
  311. &.spec-disabled {
  312. // opacity: .5;
  313. position: relative;
  314. }
  315. &.spec-disabled::after {
  316. content: '缺货';
  317. position: absolute;
  318. right: -20rpx;
  319. top: -24rpx;
  320. color: $-color-white;
  321. width: 70rpx;
  322. height: 36rpx;
  323. text-align: center;
  324. line-height: 36rpx;
  325. font-size: 22rpx;
  326. border-radius: 20rpx 20rpx 20rpx 0;
  327. background-color: $-color-primary;
  328. }
  329. &.unspec-disabled::after {
  330. background-color: gray;
  331. }
  332. }
  333. }
  334. }
  335. // 底部按钮无法选择
  336. .disabled {
  337. opacity: 0.4;
  338. }
  339. .btns {
  340. height: 120rpx;
  341. padding: 0 30rpx;
  342. margin-top: 80rpx;
  343. .add-cart {
  344. background-color: #FF9E1E;
  345. }
  346. .btn {
  347. margin: 0 10rpx;
  348. flex: 1;
  349. }
  350. }
  351. .vip-price {
  352. margin: 0 24rpx;
  353. background-color: #FFE9BA;
  354. line-height: 36rpx;
  355. border-radius: 6rpx;
  356. overflow: hidden;
  357. .price-name {
  358. background-color: #101010;
  359. padding: 3rpx 12rpx;
  360. color: #FFD4B7;
  361. position: relative;
  362. overflow: hidden;
  363. &::after {
  364. content: '';
  365. display: block;
  366. width: 20rpx;
  367. height: 20rpx;
  368. position: absolute;
  369. right: -15rpx;
  370. background-color: #FFE9BA;
  371. border-radius: 50%;
  372. top: 50%;
  373. transform: translateY(-50%);
  374. box-sizing: border-box;
  375. }
  376. }
  377. }
  378. }
  379. </style>