good.vue 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468
  1. <template>
  2. <view class="goods-detail-wrapper">
  3. <uv-swiper :list="goods.thumb" height="400" indicator indicatorMode="dot" circular></uv-swiper>
  4. <view class="content">
  5. <view class="title">{{ goods.name }}</view>
  6. <view class="desc">{{ goods.descript }}</view>
  7. <view class="price">{{ formatPrice(goods.price) }}</view>
  8. <uv-row gutter="16" class="express">
  9. <uv-col :span="6">运费:免运费</uv-col>
  10. <uv-col :span="6">剩余:{{goods.stock}}</uv-col>
  11. </uv-row>
  12. </view>
  13. <view class="detail">
  14. <uv-parse :content="goods.detail"></uv-parse>
  15. </view>
  16. <view class="navigation">
  17. <view class="left">
  18. <view class="item" @click="like">
  19. <uv-icon name="heart" size="25" :color="likeColor"></uv-icon>
  20. <view class="text uv-line-1">收藏</view>
  21. </view>
  22. <view class="item car" @click="toCart">
  23. <uv-badge :value="cartCount" type="error" :offset="[0,0]" max="99" numberType="overflow" absolute
  24. :customStyle="{
  25. zIndex:99
  26. }"></uv-badge>
  27. <uv-icon name="shopping-cart" size="30" color="content"></uv-icon>
  28. <view class="text uv-line-1">购物车</view>
  29. </view>
  30. </view>
  31. <view class="right">
  32. <view class="cart btn uv-line-1" @click="showSkuPop('cart')">加入购物车</view>
  33. <view class="buy btn uv-line-1" @click="showSkuPop('buy')">立即购买</view>
  34. </view>
  35. </view>
  36. <uv-popup ref="popup" mode="bottom" :closeable="true">
  37. <view class="sku">
  38. <view class="goods">
  39. <uv-row>
  40. <uv-col :span="4" class="left">
  41. <uv-image width="170rpx" height="170rpx" :src="goods.picture"></uv-image>
  42. </uv-col>
  43. <uv-col :span="8" class="right">
  44. <view class="price">{{formatPrice(price)}}</view>
  45. <view class="stock">剩余<text class="stock_num">{{stock}}</text>件</view>
  46. <template v-if="!sku.none_sku">
  47. <view class="tips">{{hasSel?'已选择':'请选择'}}
  48. <text v-for="(item,index) in sku.tree" :key="index" style="padding-left:10rpx;">
  49. {{item.sel?item.v[parseInt(item.sel)].name:item.k}}
  50. </text>
  51. </view>
  52. </template>
  53. </uv-col>
  54. </uv-row>
  55. </view>
  56. <template v-if="!sku.none_sku">
  57. <view class="skuv-list">
  58. <block v-for="(category,index) in sku.tree" :key="index">
  59. <view class="tree">
  60. <view class="title">{{category.k}}</view>
  61. </view>
  62. <view class="node-list">
  63. <view class="node" v-for="(node,index2) in category.v" :key="index2">
  64. <uv-tags :type="node.mode=='disable'?'info':'warning'" :text="node.name"
  65. :plain="node.mode=='default'?true:(node.mode=='select'?false:true)"
  66. @click="selSku(category,node)">
  67. </uv-tags>
  68. </view>
  69. </view>
  70. </block>
  71. </view>
  72. </template>
  73. <view class="count uv-flex uv-row-between">
  74. <view>购买数量</view>
  75. <view>
  76. <uv-number-box v-model="count" :max="stock"></uv-number-box>
  77. </view>
  78. </view>
  79. <view class="action">
  80. <uv-button type="error" shape="circle" @click="buy">确定</uv-button>
  81. </view>
  82. </view>
  83. </uv-popup>
  84. </view>
  85. </template>
  86. <script>
  87. import {
  88. getGoods,
  89. like,
  90. noLisk,
  91. addCart,
  92. getCartCount
  93. } from '@/request/api/shop.js'
  94. export default {
  95. data() {
  96. return {
  97. ifLike: false,
  98. likeColor: 'content',
  99. cartCount: '',
  100. showSku: false,
  101. stock: '',
  102. price: '',
  103. hasSel: false,
  104. sku: {
  105. tree: [],
  106. list: [],
  107. price: '0', // 默认价格(单位元)
  108. stock_num: 0, // 商品总库存
  109. collection_id: 0, // 无规格商品 skuId 取 collection_id,否则取所选 sku 组合对应的 id
  110. none_sku: false, // 是否无规格商品
  111. hide_stock: false, // 是否隐藏剩余库存
  112. sel: {}
  113. },
  114. count: 1,
  115. offline: false,
  116. goods: {
  117. id: '',
  118. name: '',
  119. price: 0,
  120. express: '免运费',
  121. remain: 0,
  122. thumb: [],
  123. stock: ''
  124. },
  125. actionType: 'buy'
  126. }
  127. },
  128. onLoad(option) {
  129. this.goods.id = option.id
  130. this.init()
  131. },
  132. methods: {
  133. async init() {
  134. this.getCartCount();
  135. let goodData = await getGoods(this.goods.id);
  136. if (goodData.state) {
  137. let res = goodData.data;
  138. let goods = res.goods
  139. this.offline = !goods.isOnSale
  140. let sku = res.sku
  141. sku.price = (sku.price / 100).toFixed(2)
  142. if (!sku.none_sku) {
  143. for (var i in sku.tree) {
  144. for (var m in sku.tree[i].v) {
  145. sku.tree[i].v[m].mode = 'default';
  146. }
  147. }
  148. }
  149. this.sku = sku;
  150. this.stock = goods.stock;
  151. this.price = goods.price;
  152. goods.thumb = new Array()
  153. goods.picture = this.shopImage(goods.pic);
  154. const gallery = goods.gallery.split(',')
  155. for (var index in gallery) {
  156. goods.thumb.push(this.shopImage(gallery[index]));
  157. }
  158. this.goods = goods;
  159. if (res.favorite === true) {
  160. this.likeColor = 'error'
  161. this.ifLike = true
  162. }
  163. }
  164. },
  165. toCart() {
  166. this.$navigateTo('/subPages/shopPage/cart/cart');
  167. },
  168. formatPrice(price) {
  169. return '¥' + (price / 100).toFixed(2)
  170. },
  171. like() {
  172. if (this.ifLike === false) {
  173. like(this.goods.id).then(res => {
  174. this.$toast('收藏成功')
  175. this.ifLike = true
  176. this.likeColor = 'error'
  177. })
  178. } else {
  179. noLisk(this.goods.id).then(res => {
  180. this.$toast('取消收藏成功')
  181. this.ifLike = false
  182. this.likeColor = 'content'
  183. })
  184. }
  185. },
  186. showSkuPop(type) {
  187. this.actionType = type;
  188. this.$refs.popup.open();
  189. },
  190. async buy() {
  191. let idSku = '';
  192. if (!this.sku.none_sku) {
  193. if(this.sku.sel) idSku = this.sku.sel.id;
  194. if (!idSku) {
  195. return this.$toast('请选择商品规格')
  196. }
  197. }
  198. const params = {
  199. idGoods: this.goods.id,
  200. count: this.count,
  201. idSku: idSku
  202. }
  203. let cartData = await addCart(params);
  204. if (cartData.state) {
  205. this.$refs.popup.close();
  206. if ('cart' == this.actionType) {
  207. this.$toast('成功加入购物车');
  208. this.init();
  209. } else {
  210. this.toCart()
  211. }
  212. }
  213. },
  214. //todo 商品规格选择算法待优化
  215. selSku(category, node) {
  216. if (node.mode == 'disable') {
  217. return;
  218. }
  219. this.hasSel = true;
  220. let selItem = {};
  221. const skuDataId = node.id;
  222. const categoryks = category.k_s;
  223. let sku = this.sku;
  224. let skuTree = sku.tree;
  225. let list = sku.list;
  226. let anotherArr = new Array();
  227. //提取当前选择的所有组合选项。
  228. for (const i in list) {
  229. const item = list[i];
  230. if (item[categoryks] == skuDataId) {
  231. anotherArr.push(item);
  232. }
  233. }
  234. for (const i in skuTree) {
  235. const ks = skuTree[i].k_s;
  236. for (const j in skuTree[i].v) {
  237. const id = skuTree[i].v[j].id;
  238. if (categoryks == ks) {
  239. //统一类规格中,设置当前选项为select,其他选项为default
  240. if (skuDataId == id) {
  241. skuTree[i].v[j].mode = 'select';
  242. skuTree[i].sel = j
  243. selItem[ks] = skuTree[i].v[j].id;
  244. } else {
  245. skuTree[i].v[j].mode = 'default';
  246. }
  247. } else {
  248. let disable = true;
  249. for (const m in anotherArr) {
  250. if (anotherArr[m][ks] == id) {
  251. disable = false;
  252. }
  253. }
  254. if (disable) {
  255. skuTree[i].v[j].mode = 'disable';
  256. } else {
  257. if (skuTree[i].v[j].mode !== 'select') {
  258. skuTree[i].v[j].mode = 'default';
  259. } else {
  260. skuTree[i].sel = j
  261. selItem[ks] = skuTree[i].v[j].id;
  262. }
  263. }
  264. }
  265. }
  266. }
  267. this.sku = {};
  268. sku.tree = skuTree;
  269. this.sku = sku;
  270. let skuSelItem;
  271. for (const i in list) {
  272. const item = list[i];
  273. let sel = true;
  274. for (const j in skuTree) {
  275. const key = skuTree[j].k_s;
  276. if (selItem[key] !== item[key]) {
  277. sel = false;
  278. break;
  279. }
  280. }
  281. if (sel) {
  282. skuSelItem = item;
  283. break;
  284. }
  285. }
  286. if (skuSelItem) {
  287. this.stock = skuSelItem.stock_num;
  288. this.price = skuSelItem.price;
  289. this.sku.sel = skuSelItem;
  290. } else {
  291. this.sku.sel = {};
  292. }
  293. },
  294. async getCartCount() {
  295. let countData = await getCartCount();
  296. if (countData.state) this.cartCount = countData.data;
  297. }
  298. }
  299. }
  300. </script>
  301. <style lang="scss" scoped>
  302. .goods-detail-wrapper {
  303. padding-bottom: 140rpx;
  304. }
  305. .content {
  306. padding: 30rpx;
  307. background: #fff;
  308. .title {
  309. font-size: 32rpx;
  310. }
  311. .desc {
  312. font-size: 12px;
  313. color: #999999;
  314. letter-spacing: 0;
  315. line-height: 18px;
  316. margin: 6px 0;
  317. }
  318. .price {
  319. color: #FA3534;
  320. font-size: 40rpx;
  321. }
  322. .express {
  323. color: #999;
  324. font-size: 24rpx;
  325. padding: 10rpx 0rpx;
  326. }
  327. }
  328. .navigation {
  329. background-color: #ffffff;
  330. box-shadow: 0px 2px 10px rgba(3, 3, 3, 0.1);
  331. position: fixed;
  332. bottom: 0;
  333. left: 0;
  334. right: 0;
  335. height: 100rpx;
  336. display: flex;
  337. align-items: center;
  338. justify-content: flex-end;
  339. padding-bottom: 0;
  340. padding-bottom: constant(safe-area-inset-bottom);
  341. padding-bottom: env(safe-area-inset-bottom);
  342. padding-right: 40rpx;
  343. padding-left: 40rpx;
  344. .left {
  345. display: flex;
  346. align-items: center;
  347. font-size: 20rpx;
  348. flex: 1;
  349. width: 0;
  350. .item {
  351. margin: 0 30rpx;
  352. &.car {
  353. text-align: center;
  354. position: relative;
  355. .text {
  356. position: relative;
  357. top: -4rpx;
  358. }
  359. }
  360. }
  361. }
  362. .right {
  363. display: flex;
  364. font-size: 28rpx;
  365. align-items: center;
  366. .btn {
  367. line-height: 66rpx;
  368. padding: 0 40rpx;
  369. border-radius: 36rpx;
  370. color: #ffffff;
  371. }
  372. .cart {
  373. background-color: #ed3f14;
  374. margin-right: 30rpx;
  375. }
  376. .buy {
  377. background-color: #ff7900;
  378. }
  379. }
  380. }
  381. .sku {
  382. padding: 24rpx 32rpx;
  383. .goods {
  384. .right {
  385. padding-left: 20rpx;
  386. .price {
  387. color: #FA3534;
  388. font-size: 34rpx;
  389. }
  390. .stock {
  391. margin-top: 8px;
  392. color: #969799;
  393. font-size: 12px;
  394. .stock_num {
  395. margin-right: 16rpx;
  396. margin-left: 16rpx;
  397. }
  398. }
  399. .tips {
  400. margin-top: 8px;
  401. color: #969799;
  402. font-size: 12px;
  403. line-height: 16px;
  404. }
  405. }
  406. }
  407. .skuv-list {
  408. .tree {
  409. margin-top: 30rpx;
  410. .title {
  411. padding-bottom: 12px;
  412. }
  413. }
  414. .node-list {
  415. display: flex;
  416. .node {
  417. justify-content: center;
  418. min-width: 80rpx;
  419. margin: 0 24rpx 24rpx 0;
  420. line-height: 24rpx;
  421. vertical-align: middle;
  422. }
  423. }
  424. }
  425. .count {
  426. padding: 24rpx 0rpx;
  427. overflow: hidden;
  428. line-height: 60rpx;
  429. padding-bottom: 60rpx;
  430. }
  431. }
  432. </style>