后端 + 前端:添加到购物车
							parent
							
								
									4300ce141d
								
							
						
					
					
						commit
						b807a63966
					
				|  | @ -45,3 +45,24 @@ export function createOrder(params) { | |||
|   }); | ||||
| } | ||||
| 
 | ||||
| // Cart
 | ||||
| 
 | ||||
| export function addCart(skuId, quantity) { | ||||
|   return request({ | ||||
|     url: '/order-api/users/cart/add', | ||||
|     method: 'post', | ||||
|     params: { | ||||
|       skuId, | ||||
|       quantity, | ||||
|     } | ||||
|   }); | ||||
| } | ||||
| 
 | ||||
| export function countCart() { | ||||
|   return request({ | ||||
|     url: '/order-api/users/cart/count', | ||||
|     method: 'get', | ||||
|     params: { | ||||
|     } | ||||
|   }); | ||||
| } | ||||
|  |  | |||
|  | @ -8,8 +8,7 @@ | |||
|                     style="background:#fff" | ||||
|             > | ||||
|                 <template slot="thumb"> | ||||
|                     <!--  TODO 芋艿 暂时去掉,等会就恢复 --> | ||||
| <!--                    <img :src="product.picUrls[0]"/>--> | ||||
|                     <img :src="product.picUrls[0]"/> | ||||
|                     <!--  TODO 芋艿 暂时去掉 --> | ||||
| <!--                    <p v-if="product.imageTag!=null&&product.imageTag!=''" class="image_tag">{{product.imageTag}}</p>--> | ||||
|                 </template> | ||||
|  |  | |||
|  | @ -208,6 +208,7 @@ service.interceptors.response.use( | |||
|               // this.$router.push({ path: '/login' })
 | ||||
|               // TODO 跳转到登陆页.不是很优雅
 | ||||
|               location.replace('/#login'); | ||||
|               location.reload(); | ||||
|             } | ||||
|           } | ||||
|         }); | ||||
|  |  | |||
|  | @ -58,7 +58,8 @@ const routes = [ | |||
|     component: () => import('../page/user/info/detail'), | ||||
|     name: 'user', | ||||
|     meta: { | ||||
|       title: '个人信息' | ||||
|       title: '个人信息', | ||||
|       requireAuth: true, | ||||
|     } | ||||
|   }, | ||||
|   { | ||||
|  | @ -178,7 +179,8 @@ const routes = [ | |||
|     path: '/order', | ||||
|     component: () => import('../page/shipping/order'), | ||||
|     meta: { | ||||
|       title: '确认订单' | ||||
|       title: '确认订单', | ||||
|       requireAuth: true, | ||||
|     } | ||||
|   }, | ||||
|   { | ||||
|  |  | |||
|  | @ -10,68 +10,30 @@ | |||
|     <van-checkbox-group class="card-goods" v-model="checkedGoods"> | ||||
| 
 | ||||
|       <div class="promotion-group"> | ||||
|         <div  v-for="(item,index) in goods" | ||||
|           :key="index" class="card-goods__item">  | ||||
|         <div  v-for="(item,index) in goods" :key="index" class="card-goods__item"> | ||||
|           <van-checkbox :name="item.id"></van-checkbox> | ||||
| 
 | ||||
|           <product-card :product='item' :iscard='true' > | ||||
|             <template slot> | ||||
|               <van-cell value="修改" > | ||||
|                   <template slot="title"> | ||||
|                       <van-tag type="danger">促销</van-tag> | ||||
|                       <span class="van-cell-text" >满60元减5元</span> | ||||
|                   </template> | ||||
|               </van-cell> | ||||
|             </template> | ||||
|           <product-card :product='item' :iscard='false' > | ||||
| <!--            <template slot>--> | ||||
| <!--              <van-cell value="修改" >--> | ||||
| <!--                  <template slot="title">--> | ||||
| <!--                      <van-tag type="danger">促销</van-tag>--> | ||||
| <!--                      <span class="van-cell-text" >满60元减5元</span>--> | ||||
| <!--                  </template>--> | ||||
| <!--              </van-cell>--> | ||||
| <!--            </template>--> | ||||
|           </product-card> | ||||
|         </div> | ||||
|       </div> | ||||
| 
 | ||||
|       <div class="promotion-group"> | ||||
| <!--      <div class="promotion-group">--> | ||||
| 
 | ||||
|        <van-cell  is-link class="head"> | ||||
|         <template slot="title"> | ||||
|           <van-checkbox v-model="checkedAll" >京东自营</van-checkbox> | ||||
|         </template> | ||||
|       </van-cell> | ||||
|        | ||||
|        | ||||
|       <div  v-for="(item,index) in goods" | ||||
|         :key="index+10" class="card-goods__item">  | ||||
|         <van-checkbox :name="item.id"></van-checkbox> | ||||
|        | ||||
|         <product-card :product='item' :iscard='true' > | ||||
|           <template slot> | ||||
|             <van-cell value="修改" > | ||||
|                 <template slot="title"> | ||||
|                     <van-tag type="danger">促销</van-tag> | ||||
|                     <span class="van-cell-text" >满60元减5元</span> | ||||
|                 </template> | ||||
|             </van-cell> | ||||
|           </template> | ||||
|         </product-card> | ||||
|       </div> | ||||
|       <van-cell value="去凑单" is-link class="promotion"> | ||||
|         <template slot="title"> | ||||
|           <p><van-tag type="danger">满减</van-tag>购满60元,可减5元</p> | ||||
|         </template> | ||||
|       </van-cell> | ||||
|       <div  v-for="(item,index) in goods" | ||||
|         :key="index+20" class="card-goods__item">  | ||||
|         <van-checkbox :name="item.id"></van-checkbox> | ||||
|        | ||||
|         <product-card :product='item' :iscard='true' > | ||||
|           <template slot> | ||||
|             <van-cell value="修改" > | ||||
|                 <template slot="title"> | ||||
|                     <van-tag type="danger">促销</van-tag> | ||||
|                     <span class="van-cell-text" >满60元减5元</span> | ||||
|                 </template> | ||||
|             </van-cell> | ||||
|           </template> | ||||
|         </product-card> | ||||
|       </div> | ||||
|         </div> | ||||
| <!--       <van-cell  is-link class="head">--> | ||||
| <!--        <template slot="title">--> | ||||
| <!--          <van-checkbox v-model="checkedAll" >京东自营</van-checkbox>--> | ||||
| <!--        </template>--> | ||||
| <!--      </van-cell>--> | ||||
| <!--        </div>--> | ||||
|     </van-checkbox-group> | ||||
| 
 | ||||
|     <div style="height:50px;"></div> | ||||
|  | @ -103,7 +65,7 @@ export default { | |||
|         desc: '3.18kg/件', | ||||
|         price: '200.00', | ||||
|         quantity: 1, | ||||
|         imageURL: 'https://img.yzcdn.cn/public_files/2017/10/24/2f9a36046449dafb8608e99990b3c205.jpeg', | ||||
|         picUrls: ['https://img.yzcdn.cn/public_files/2017/10/24/2f9a36046449dafb8608e99990b3c205.jpeg'], | ||||
|         imageTag:'比加入时降5元', | ||||
|       }, { | ||||
|         id: '2', | ||||
|  | @ -111,7 +73,7 @@ export default { | |||
|         desc: '约600g', | ||||
|         price: '690.00', | ||||
|         quantity: 1, | ||||
|         imageURL: 'https://img.yzcdn.cn/public_files/2017/10/24/f6aabd6ac5521195e01e8e89ee9fc63f.jpeg', | ||||
|         picUrls: ['https://img.yzcdn.cn/public_files/2017/10/24/f6aabd6ac5521195e01e8e89ee9fc63f.jpeg'], | ||||
|           gift: [ | ||||
|             { | ||||
|               title: "星巴克(Starbucks)星冰乐小熊吊饰星巴克(Starbucks)星冰乐小熊吊饰", | ||||
|  | @ -128,7 +90,7 @@ export default { | |||
|         desc: '约680g/3个', | ||||
|         price: '2680.00', | ||||
|         quantity: 1, | ||||
|         imageURL: 'https://img.yzcdn.cn/public_files/2017/10/24/320454216bbe9e25c7651e1fa51b31fd.jpeg' | ||||
|         picUrls: ['https://img.yzcdn.cn/public_files/2017/10/24/320454216bbe9e25c7651e1fa51b31fd.jpeg'] | ||||
|       }] | ||||
|     }; | ||||
|   }, | ||||
|  |  | |||
|  | @ -74,7 +74,7 @@ | |||
|       <van-goods-action-mini-btn icon="like-o" @click="sorry"> | ||||
|         收藏 | ||||
|       </van-goods-action-mini-btn> | ||||
|       <van-goods-action-mini-btn icon="cart" @click="onClickCart"> | ||||
|       <van-goods-action-mini-btn icon="cart" :info="cartCount > 0 ? cartCount : undefined" @click="onClickCart"> | ||||
|         购物车 | ||||
|       </van-goods-action-mini-btn> | ||||
|       <van-goods-action-big-btn @click="showSku"> | ||||
|  | @ -167,6 +167,8 @@ | |||
| <script> | ||||
|   // import skuData from '../../data/sku'; | ||||
|   import {getProductSpuInfo} from '../../api/product'; | ||||
|   import {addCart, countCart} from '../../api/order'; | ||||
|   import { Dialog } from 'vant'; | ||||
| 
 | ||||
|   export default { | ||||
|     components: {}, | ||||
|  | @ -190,26 +192,12 @@ | |||
|         }, | ||||
|         attrValueMap: new Map(), // 规格值的映射 | ||||
| 
 | ||||
|         // show:false, | ||||
|         // showTag:false, | ||||
|         // goods: { | ||||
|         //   title: '【每日一粒益智又长高】 Lifeline Care 儿童果冻鱼油DHA维生素D3聪明长高 软糖 30粒 2件装', | ||||
|         //   subtitle:'【品牌直采】Q弹美味,无腥味果冻鱼油,每粒含足量鱼油DHA,帮助视网膜和大脑健康发育,让你的宝宝明眼又聪明,同时补充400国际单位维生素D3,强壮骨骼和牙齿。特含DPA,让宝宝免疫力更强,没病来扰。', | ||||
|         //   price: 2680, | ||||
|         //   market_price:9999, | ||||
|         //   express: '免运费', | ||||
|         //   remain: 19, | ||||
|         //   thumb: [ | ||||
|         //     'https://img.yzcdn.cn/public_files/2017/10/24/e5a5a02309a41f9f5def56684808d9ae.jpeg', | ||||
|         //     'https://img.yzcdn.cn/public_files/2017/10/24/1791ba14088f9c2be8c610d0a6cc0f93.jpeg' | ||||
|         //   ], | ||||
|         //   info:'<p style="text-align:center;"><img src="https://haitao.nosdn2.127.net/ac19460151ee4d95a6657202bcfc653c1531470912089jjjq8ml410763.jpg" ></p><p style="text-align:center;"><img src="https://haitao.nos.netease.com/2a91cfad22404e5498d347672b1440301531470912182jjjq8mnq10764.jpg" ></p><p style="text-align:center;"><img src="https://haitao.nos.netease.com/caddd5a213de4c1cb1347c267e8275731531470912412jjjq8mu410765.jpg" ></p>', | ||||
|         // }, | ||||
| 
 | ||||
|         showBase: false, // 是否显示 sku 坦诚 | ||||
|         showBase: false, // 是否显示 sku 弹层 | ||||
|         closeOnClickOverlay: true, // 是否在点击蒙层后关闭 | ||||
|         hideStock: true, // 是否显示商品剩余库存 | ||||
| 
 | ||||
|         cartCount: 0, | ||||
| 
 | ||||
|       }; | ||||
|     }, | ||||
|     methods: { | ||||
|  | @ -231,9 +219,9 @@ | |||
|         this.initialSku.quantity = value; | ||||
|       }, | ||||
|       skuSelected({skuValue, selectedSku, selectedSkuComb}) { // 选择 sku | ||||
|         console.log(skuValue); | ||||
|         console.log(selectedSku); | ||||
|         console.log(selectedSkuComb); | ||||
|         // console.log(skuValue); | ||||
|         // console.log(selectedSku); | ||||
|         // console.log(selectedSkuComb); | ||||
|         this.initialSku = { | ||||
|           ...selectedSkuComb, | ||||
|           quantity: 1, | ||||
|  | @ -256,25 +244,36 @@ | |||
|         this.showTag = true; | ||||
|       }, | ||||
|       onBuyClicked(data) { | ||||
|         const { selectedNum, goodsId } = data; | ||||
|         console.log(data); | ||||
|         const { selectedNum } = data; | ||||
|         // debugger; | ||||
|         // console.log(data); | ||||
|         // this.$toast(JSON.stringify(data)); | ||||
|         this.$router.push({ | ||||
|           path:'/order', | ||||
|           query:{ | ||||
|             skuId: goodsId, | ||||
|             skuId: data.selectedSkuComb.id, | ||||
|             quantity: selectedNum, | ||||
|           } | ||||
|         }); | ||||
|       }, | ||||
|       onAddCartClicked(data) { | ||||
|         this.$toast(JSON.stringify(data)); | ||||
|         const { selectedNum } = data; | ||||
|         // debugger; | ||||
|         addCart(data.selectedSkuComb.id,selectedNum).then(data => { | ||||
|           // 修改购物车数量 | ||||
|           this.cartCount = data; | ||||
|           // 提示 | ||||
|           Dialog.alert({ | ||||
|             title: '系统提示', | ||||
|             message: '添加购物车成功', | ||||
|           }); | ||||
|         }); | ||||
|       }, | ||||
|     }, | ||||
|     mounted() { | ||||
|       // 获得商品数据 | ||||
|       let id = this.$route.params.id; // 商品编号 | ||||
|       let response = getProductSpuInfo(id); | ||||
|       response.then(data => { | ||||
|       getProductSpuInfo(id).then(data => { | ||||
|         // 设置 spu | ||||
|         this.spu = data; | ||||
|         // 初始化 vanSku | ||||
|  | @ -337,6 +336,10 @@ | |||
|         this.initialSku = vanSku.list[0]; | ||||
|         this.initialSku.quantity = 1; | ||||
|       }); | ||||
|       // 获得购物车数量 | ||||
|       countCart().then(data => { | ||||
|         this.cartCount = data; | ||||
|       }) | ||||
|     } | ||||
|   }; | ||||
| </script> | ||||
|  |  | |||
|  | @ -18,9 +18,19 @@ | |||
|     </van-cell> | ||||
|       <div style="height:15px;"></div> | ||||
| 
 | ||||
|     <div class="card" v-for="(product,i) in products" :key="i"> | ||||
|       <product-card :product='product'/> | ||||
| 
 | ||||
| 
 | ||||
| <!--    <div class="card" v-for="(product,i) in products" :key="i">--> | ||||
| <!--      <product-card :product='product'/>--> | ||||
| <!--    </div>--> | ||||
|     <div v-for="(itemGroup, i) in itemGroups" > | ||||
|         <div class="card" v-for="(item, j) in itemGroup.items" :key="j"> | ||||
|             <product-card :product='convertProduct(item)'/> | ||||
|         </div> | ||||
|         <div style="height:15px;"></div> | ||||
|     </div> | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
|     <div style="height:15px;"></div> | ||||
|     <van-cell-group> | ||||
|  | @ -61,7 +71,6 @@ | |||
|   import {createOrder, getConfirmCreateOrder} from '../../api/order'; | ||||
|   import {GetDefaultAddress} from '../../api/user'; | ||||
|   import orderStore from '../../store/order' | ||||
|   import eventBus from '../eventBus'; | ||||
| 
 | ||||
|   export default { | ||||
|     data() { | ||||
|  | @ -141,8 +150,17 @@ | |||
|           remark, | ||||
|         }) | ||||
|       }, | ||||
|       convertProduct() { | ||||
| 
 | ||||
|       convertProduct(item) { | ||||
|         // debugger; | ||||
|         return { | ||||
|           ...item.spu, | ||||
|           quantity: item.buyQuantity, | ||||
|           price: item.price, | ||||
|           sku: { | ||||
|             ...item, | ||||
|             spu: undefined, | ||||
|           } | ||||
|         }; | ||||
|       } | ||||
|     }, | ||||
|     mounted: function() { | ||||
|  |  | |||
|  | @ -3,14 +3,109 @@ package cn.iocoder.mall.order.application.controller.users; | |||
| import cn.iocoder.common.framework.vo.CommonResult; | ||||
| import cn.iocoder.mall.order.api.CartService; | ||||
| import cn.iocoder.mall.order.api.OrderService; | ||||
| import cn.iocoder.mall.order.api.bo.CartItemBO; | ||||
| import cn.iocoder.mall.order.application.vo.UsersOrderConfirmCreateVO; | ||||
| import cn.iocoder.mall.user.sdk.context.UserSecurityContextHolder; | ||||
| import com.alibaba.dubbo.config.annotation.Reference; | ||||
| import org.apache.ibatis.annotations.Param; | ||||
| import org.springframework.web.bind.annotation.GetMapping; | ||||
| import org.springframework.web.bind.annotation.PostMapping; | ||||
| import org.springframework.web.bind.annotation.RequestMapping; | ||||
| import org.springframework.web.bind.annotation.RestController; | ||||
| 
 | ||||
| import java.util.Collections; | ||||
| import java.util.List; | ||||
| 
 | ||||
| @RestController | ||||
| @RequestMapping("users/cart") | ||||
| public class UsersCartController { | ||||
| 
 | ||||
|     // TODO 注入
 | ||||
|     @Reference(validation = "true") | ||||
|     private CartService cartService; | ||||
|     // TODO 注入
 | ||||
|     @Reference(validation = "true") | ||||
|     private OrderService orderService; | ||||
| 
 | ||||
|     @PostMapping("add") | ||||
|     public CommonResult<Integer> add(@Param("skuId") Integer skuId, | ||||
|                                      @Param("quantity") Integer quantity) { | ||||
|         // 添加到购物车
 | ||||
|         CommonResult<Boolean> addResult = cartService.add(UserSecurityContextHolder.getContext().getUserId(), | ||||
|                 skuId, quantity); | ||||
|         // 添加失败,则直接返回错误
 | ||||
|         if (addResult.isError()) { | ||||
|             return CommonResult.error(addResult); | ||||
|         } | ||||
|         // 获得目前购物车商品总数量
 | ||||
|         return cartService.count(UserSecurityContextHolder.getContext().getUserId()); | ||||
|     } | ||||
| 
 | ||||
|     @PostMapping("update_quantity") | ||||
|     public CommonResult<Integer> updateQuantity(@Param("skuId") Integer skuId, | ||||
|                                                 @Param("quantity") Integer quantity) { | ||||
|         // 添加到购物车
 | ||||
|         CommonResult<Boolean> updateQuantityResult = cartService.updateQuantity(UserSecurityContextHolder.getContext().getUserId(), | ||||
|                 skuId, quantity); | ||||
|         // 添加失败,则直接返回错误
 | ||||
|         if (updateQuantityResult.isError()) { | ||||
|             return CommonResult.error(updateQuantityResult); | ||||
|         } | ||||
|         // 获得目前购物车商品总数量
 | ||||
|         // TODO 芋艿,需要改成价格计算
 | ||||
|         return cartService.count(UserSecurityContextHolder.getContext().getUserId()); | ||||
|     } | ||||
| 
 | ||||
|     @PostMapping("update_selected") | ||||
|     public CommonResult<Integer> updateSelected(@Param("skuId") Integer skuId, | ||||
|                                                 @Param("selected") Boolean selected) { | ||||
|         // 添加到购物车
 | ||||
|         CommonResult<Boolean> updateSelectedResult = cartService.updateSelected(UserSecurityContextHolder.getContext().getUserId(), | ||||
|                 skuId, selected); | ||||
|         // 添加失败,则直接返回错误
 | ||||
|         if (updateSelectedResult.isError()) { | ||||
|             return CommonResult.error(updateSelectedResult); | ||||
|         } | ||||
|         // 获得目前购物车商品总数量
 | ||||
|         // TODO 芋艿,需要改成价格计算
 | ||||
|         return cartService.count(UserSecurityContextHolder.getContext().getUserId()); | ||||
|     } | ||||
| 
 | ||||
|     @GetMapping("count") | ||||
|     public CommonResult<Integer> count() { | ||||
|         return cartService.count(UserSecurityContextHolder.getContext().getUserId()); | ||||
|     } | ||||
| 
 | ||||
|     @GetMapping("/list") | ||||
|     public CommonResult<UsersOrderConfirmCreateVO> list() { | ||||
|         // 获得购物车中所有的
 | ||||
|         List<CartItemBO> cartItems = cartService.list(UserSecurityContextHolder.getContext().getUserId(), null); | ||||
|         // 购物车为空时,构造空的 UsersOrderConfirmCreateVO 返回
 | ||||
|         if (cartItems.isEmpty()) { | ||||
|             UsersOrderConfirmCreateVO result = new UsersOrderConfirmCreateVO(); | ||||
|             result.setItemGroups(Collections.emptyList()); | ||||
|             result.setFee(new UsersOrderConfirmCreateVO.Fee(0, 0, 0, 0)); | ||||
|             return CommonResult.success(result); | ||||
|         } | ||||
|         // 购物车非空时,获得具体结果
 | ||||
| 
 | ||||
|         return null; | ||||
|     } | ||||
| 
 | ||||
|     @GetMapping("/confirm_create_order") | ||||
|     public CommonResult<UsersOrderConfirmCreateVO> getConfirmCreateOrder() { | ||||
|         // 获得购物车中选中的
 | ||||
|         List<CartItemBO> cartItems = cartService.list(UserSecurityContextHolder.getContext().getUserId(), true); | ||||
|         // 购物车为空时,构造空的 UsersOrderConfirmCreateVO 返回
 | ||||
|         if (cartItems.isEmpty()) { | ||||
|             UsersOrderConfirmCreateVO result = new UsersOrderConfirmCreateVO(); | ||||
|             result.setItemGroups(Collections.emptyList()); | ||||
|             result.setFee(new UsersOrderConfirmCreateVO.Fee(0, 0, 0, 0)); | ||||
|             return CommonResult.success(result); | ||||
|         } | ||||
|         // 购物车非空时,获得具体结果
 | ||||
| //        return CommonResult.success(CartConvert.INSTANCE.convert(calcOrderPriceResult.getData()));
 | ||||
|         return null; | ||||
|     } | ||||
| 
 | ||||
|     public CommonResult<Object> confirmOrder() { | ||||
|         // 查询购物车列表(选中的)
 | ||||
| //        cartService.list(userId, true);
 | ||||
|  |  | |||
|  | @ -133,6 +133,15 @@ public class UsersOrderConfirmCreateVO { | |||
|          */ | ||||
|         private Integer presentTotal; | ||||
| 
 | ||||
|         public Fee() { | ||||
|         } | ||||
| 
 | ||||
|         public Fee(Integer originalTotal, Integer discountTotal, Integer postageTotal, Integer presentTotal) { | ||||
|             this.originalTotal = originalTotal; | ||||
|             this.discountTotal = discountTotal; | ||||
|             this.postageTotal = postageTotal; | ||||
|             this.presentTotal = presentTotal; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|  |  | |||
|  | @ -39,9 +39,10 @@ public interface CartService { | |||
|      * | ||||
|      * @param userId 用户编号 | ||||
|      * @param skuId 商品 SKU 编号 | ||||
|      * @param selected 是否选中 | ||||
|      * @return 是否成功 | ||||
|      */ | ||||
|     CommonResult<Boolean> updateSelected(Integer userId, Integer skuId); | ||||
|     CommonResult<Boolean> updateSelected(Integer userId, Integer skuId, Boolean selected); | ||||
| 
 | ||||
|     /** | ||||
|      * 购物车删除商品 | ||||
|  | @ -67,7 +68,7 @@ public interface CartService { | |||
|      * @param userId 用户编号 | ||||
|      * @return 商品数量 | ||||
|      */ | ||||
|     CommonResult<Integer> count(Integer userId, String nobody, Integer shopId); | ||||
|     CommonResult<Integer> count(Integer userId); | ||||
| 
 | ||||
|     /** | ||||
|      * 显示买家购物车中的商品列表,并根据 selected 进行过滤。 | ||||
|  |  | |||
|  | @ -0,0 +1,46 @@ | |||
| package cn.iocoder.mall.order.api.constant; | ||||
| 
 | ||||
| import java.util.Arrays; | ||||
| 
 | ||||
| public enum CartItemStatusEnum { | ||||
| 
 | ||||
|     ENABLE(1, "正常"), | ||||
|     DELETE_BY_MANUAL(2, "主动删除"), | ||||
|     DELETE_BY_(3, "下单删除"), | ||||
|     ; | ||||
| 
 | ||||
|     public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(CartItemStatusEnum::getValue).toArray(); | ||||
| 
 | ||||
|     /** | ||||
|      * 状态值 | ||||
|      */ | ||||
|     private Integer value; | ||||
|     /** | ||||
|      * 状态名 | ||||
|      */ | ||||
|     private String name; | ||||
| 
 | ||||
|     CartItemStatusEnum(Integer value, String name) { | ||||
|         this.value = value; | ||||
|         this.name = name; | ||||
|     } | ||||
| 
 | ||||
|     public Integer getValue() { | ||||
|         return value; | ||||
|     } | ||||
| 
 | ||||
|     public CartItemStatusEnum setValue(Integer value) { | ||||
|         this.value = value; | ||||
|         return this; | ||||
|     } | ||||
| 
 | ||||
|     public String getName() { | ||||
|         return name; | ||||
|     } | ||||
| 
 | ||||
|     public CartItemStatusEnum setName(String name) { | ||||
|         this.name = name; | ||||
|         return this; | ||||
|     } | ||||
| 
 | ||||
| } | ||||
|  | @ -27,6 +27,12 @@ public enum OrderErrorCodeEnum { | |||
|     ORDER_ITEM_ONLY_ONE(1008000004, "订单Item只有一个!"), | ||||
|     ORDER_ITEM_SOME_NOT_EXISTS(-1, "有不存在的商品"), // TODO 芋艿 后面改下错误码
 | ||||
| 
 | ||||
| 
 | ||||
|     // ========== 购物车 ==========
 | ||||
|     CARD_ITEM_NOT_FOUND(1008003000, "购物车项不存在"), | ||||
|     CARD_ITEM_SKU_NOT_FOUND(1008003001, "商品不存在"), | ||||
|     CARD_ITEM_SKU_QUANTITY_NOT_ENOUGH(1008003002, "商品库存不足"), | ||||
| 
 | ||||
|     ; | ||||
| 
 | ||||
|     private final int code; | ||||
|  |  | |||
|  | @ -0,0 +1,39 @@ | |||
| package cn.iocoder.mall.order.biz.dao; | ||||
| 
 | ||||
| import cn.iocoder.mall.order.biz.dataobject.CartItemDO; | ||||
| import org.apache.ibatis.annotations.Param; | ||||
| import org.springframework.stereotype.Repository; | ||||
| 
 | ||||
| import java.util.Collection; | ||||
| import java.util.List; | ||||
| 
 | ||||
| @Repository | ||||
| public interface CartMapper { | ||||
| 
 | ||||
|     CartItemDO selectById(@Param("id") Integer id); | ||||
| 
 | ||||
|     List<CartItemDO> selectByIds(@Param("ids") Collection<Integer> ids); | ||||
| 
 | ||||
|     CartItemDO selectByUserIdAndSkuIdAndStatus(@Param("userId") Integer userId, | ||||
|                                                @Param("skuId") Integer skuId, | ||||
|                                                @Param("status") Integer status); | ||||
| 
 | ||||
|     Integer selectQuantitySumByUserIdAndStatus(@Param("userId") Integer userId, | ||||
|                                                @Param("status") Integer status); | ||||
| 
 | ||||
| //    List<CartItemDO> selectListByStatus(@Param("status") Integer status);
 | ||||
| //
 | ||||
| //    List<CartItemDO> selectListByTitleLike(@Param("title") String title,
 | ||||
| //                                         @Param("offset") Integer offset,
 | ||||
| //                                         @Param("limit") Integer limit);
 | ||||
| 
 | ||||
| //    Integer selectCountByTitleLike(@Param("title") String title);
 | ||||
| 
 | ||||
|     void insert(CartItemDO cartItemDO); | ||||
| 
 | ||||
|     int update(CartItemDO cartItemDO); | ||||
| 
 | ||||
|     int updateQuantity(@Param("id") Integer id, | ||||
|                        @Param("quantityIncr") Integer quantityIncr); | ||||
| 
 | ||||
| } | ||||
|  | @ -13,4 +13,5 @@ import org.springframework.stereotype.Repository; | |||
| public interface OrderCancelMapper { | ||||
| 
 | ||||
|     int insert(OrderCancelDO orderCancelDO); | ||||
| 
 | ||||
| } | ||||
|  |  | |||
|  | @ -76,7 +76,7 @@ public class CartItemDO extends BaseDO { | |||
|     /** | ||||
|      * 订单编号 | ||||
|      */ | ||||
|     private String orderId; | ||||
|     private Integer orderId; | ||||
|     /** | ||||
|      * 订单创建时间 | ||||
|      */ | ||||
|  |  | |||
|  | @ -1,57 +0,0 @@ | |||
| package cn.iocoder.mall.order.biz.mock; | ||||
| 
 | ||||
| import cn.iocoder.common.framework.vo.CommonResult; | ||||
| import cn.iocoder.mall.product.api.ProductSpuService; | ||||
| import cn.iocoder.mall.product.api.bo.ProductSkuDetailBO; | ||||
| import cn.iocoder.mall.product.api.bo.ProductSpuBO; | ||||
| import cn.iocoder.mall.product.api.bo.ProductSpuDetailBO; | ||||
| import cn.iocoder.mall.product.api.bo.ProductSpuPageBO; | ||||
| import cn.iocoder.mall.product.api.dto.ProductSpuAddDTO; | ||||
| import cn.iocoder.mall.product.api.dto.ProductSpuPageDTO; | ||||
| import cn.iocoder.mall.product.api.dto.ProductSpuUpdateDTO; | ||||
| 
 | ||||
| import java.util.Collection; | ||||
| import java.util.List; | ||||
| 
 | ||||
| /** | ||||
|  * @author Sin | ||||
|  * @time 2019-03-24 15:24 | ||||
|  */ | ||||
| public class ProductSpuServiceMock implements ProductSpuService { | ||||
| 
 | ||||
|     @Override | ||||
|     public CommonResult<ProductSpuDetailBO> getProductSpuDetail(Integer id) { | ||||
|         return null; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public CommonResult<ProductSpuDetailBO> addProductSpu(Integer adminId, ProductSpuAddDTO productSpuAddDTO) { | ||||
|         return null; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public CommonResult<Boolean> updateProductSpu(Integer adminId, ProductSpuUpdateDTO productSpuUpdateDTO) { | ||||
|         return null; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public CommonResult<Boolean> updateProductSpuSort(Integer adminId, Integer spuId, Integer sort) { | ||||
|         return null; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public CommonResult<ProductSpuPageBO> getProductSpuPage(ProductSpuPageDTO productSpuPageDTO) { | ||||
|         return null; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public CommonResult<List<ProductSpuBO>> getProductSpuList(Collection<Integer> ids) { | ||||
|         return null; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public CommonResult<List<ProductSkuDetailBO>> getProductSkuDetailList(Collection<Integer> ids) { | ||||
|         return null; | ||||
|     } | ||||
| 
 | ||||
| } | ||||
|  | @ -1,5 +1,6 @@ | |||
| package cn.iocoder.mall.order.biz.service; | ||||
| 
 | ||||
| import cn.iocoder.common.framework.constant.CommonStatusEnum; | ||||
| import cn.iocoder.common.framework.util.ServiceExceptionUtil; | ||||
| import cn.iocoder.common.framework.vo.CommonResult; | ||||
| import cn.iocoder.mall.order.api.CartService; | ||||
|  | @ -7,12 +8,17 @@ import cn.iocoder.mall.order.api.bo.CalcOrderPriceBO; | |||
| import cn.iocoder.mall.order.api.bo.CartBO; | ||||
| import cn.iocoder.mall.order.api.bo.CartItemBO; | ||||
| import cn.iocoder.mall.order.api.bo.OrderCreateBO; | ||||
| import cn.iocoder.mall.order.api.constant.CartItemStatusEnum; | ||||
| import cn.iocoder.mall.order.api.constant.OrderErrorCodeEnum; | ||||
| import cn.iocoder.mall.order.api.dto.CalcOrderPriceDTO; | ||||
| import cn.iocoder.mall.order.biz.convert.CartConvert; | ||||
| import cn.iocoder.mall.order.biz.dao.CartMapper; | ||||
| import cn.iocoder.mall.order.biz.dataobject.CartItemDO; | ||||
| import cn.iocoder.mall.product.api.ProductSpuService; | ||||
| import cn.iocoder.mall.product.api.bo.ProductSkuBO; | ||||
| import cn.iocoder.mall.product.api.bo.ProductSkuDetailBO; | ||||
| import com.alibaba.dubbo.config.annotation.Reference; | ||||
| import org.springframework.beans.factory.annotation.Autowired; | ||||
| import org.springframework.stereotype.Service; | ||||
| 
 | ||||
| import java.util.*; | ||||
|  | @ -28,19 +34,97 @@ public class CartServiceImpl implements CartService { | |||
|     @Reference(validation = "true") | ||||
|     private ProductSpuService productSpuService; | ||||
| 
 | ||||
|     @Autowired | ||||
|     private CartMapper cartMapper; | ||||
| 
 | ||||
|     @Override | ||||
|     @SuppressWarnings("Duplicates") | ||||
|     public CommonResult<Boolean> add(Integer userId, Integer skuId, Integer quantity) { | ||||
|         return null; | ||||
|         // 查询 SKU 是否合法
 | ||||
|         CommonResult<ProductSkuBO> skuResult = productSpuService.getProductSku(skuId); | ||||
|         if (skuResult.isError()) { | ||||
|             return CommonResult.error(skuResult); | ||||
|         } | ||||
|         ProductSkuBO sku = skuResult.getData(); | ||||
|         if (sku == null | ||||
|                 || CommonStatusEnum.DISABLE.getValue().equals(sku.getStatus())) { // sku 被禁用
 | ||||
|             return ServiceExceptionUtil.error(OrderErrorCodeEnum.CARD_ITEM_SKU_NOT_FOUND.getCode()); | ||||
|         } | ||||
|         // TODO 芋艿,后续基于商品是否上下架进一步完善。
 | ||||
|         // 查询 CartItemDO
 | ||||
|         CartItemDO item = cartMapper.selectByUserIdAndSkuIdAndStatus(userId, skuId, CartItemStatusEnum.ENABLE.getValue()); | ||||
|         // 存在,则进行数量更新
 | ||||
|         if (item != null) { | ||||
|             return updateQuantity0(item, sku, quantity); | ||||
|         } | ||||
|         // 不存在,则进行插入
 | ||||
|         return add0(userId, sku, quantity); | ||||
|     } | ||||
| 
 | ||||
|     private CommonResult<Boolean> add0(Integer userId, ProductSkuBO sku, Integer quantity) { | ||||
|         // 校验库存
 | ||||
|         if (quantity > sku.getQuantity()) { | ||||
|             return ServiceExceptionUtil.error(OrderErrorCodeEnum.CARD_ITEM_SKU_NOT_FOUND.getCode()); | ||||
|         } | ||||
|         // 创建 CartItemDO 对象,并进行保存。
 | ||||
|         CartItemDO item = new CartItemDO() | ||||
|                 // 基础字段
 | ||||
|                 .setStatus(CartItemStatusEnum.ENABLE.getValue()).setSelected(true) | ||||
|                 // 买家信息
 | ||||
|                 .setUserId(userId) | ||||
|                 // 商品信息
 | ||||
|                 .setSpuId(sku.getSpuId()).setSkuId(sku.getId()).setQuantity(quantity); | ||||
|         item.setCreateTime(new Date()); | ||||
|         cartMapper.insert(item); | ||||
|         // 返回成功
 | ||||
|         return CommonResult.success(true); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     @SuppressWarnings("Duplicates") | ||||
|     public CommonResult<Boolean> updateQuantity(Integer userId, Integer skuId, Integer quantity) { | ||||
|         return null; | ||||
|         // 查询 SKU 是否合法
 | ||||
|         CommonResult<ProductSkuBO> skuResult = productSpuService.getProductSku(skuId); | ||||
|         if (skuResult.isError()) { | ||||
|             return CommonResult.error(skuResult); | ||||
|         } | ||||
|         ProductSkuBO sku = skuResult.getData(); | ||||
|         if (sku == null | ||||
|                 || CommonStatusEnum.DISABLE.getValue().equals(sku.getStatus())) { // sku 被禁用
 | ||||
|             return ServiceExceptionUtil.error(OrderErrorCodeEnum.CARD_ITEM_SKU_NOT_FOUND.getCode()); | ||||
|         } | ||||
|         // 查询 CartItemDO
 | ||||
|         CartItemDO item = cartMapper.selectByUserIdAndSkuIdAndStatus(userId, skuId, CartItemStatusEnum.ENABLE.getValue()); | ||||
|         if (item == null) { | ||||
|             return ServiceExceptionUtil.error(OrderErrorCodeEnum.CARD_ITEM_NOT_FOUND.getCode()); | ||||
|         } | ||||
|         // TODO 芋艿,后续基于商品是否上下架进一步完善。
 | ||||
|         return updateQuantity0(item, sku, quantity); | ||||
|     } | ||||
| 
 | ||||
|     private CommonResult<Boolean> updateQuantity0(CartItemDO item, ProductSkuBO sku, Integer quantity) { | ||||
|         // 校验库存
 | ||||
|         if (item.getQuantity() + quantity > sku.getQuantity()) { | ||||
|             return ServiceExceptionUtil.error(OrderErrorCodeEnum.CARD_ITEM_SKU_NOT_FOUND.getCode()); | ||||
|         } | ||||
|         // 更新 CartItemDO
 | ||||
|         cartMapper.updateQuantity(item.getId(), quantity); | ||||
|         // 返回成功
 | ||||
|         return CommonResult.success(true); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public CommonResult<Boolean> updateSelected(Integer userId, Integer skuId) { | ||||
|         return null; | ||||
|     public CommonResult<Boolean> updateSelected(Integer userId, Integer skuId, Boolean selected) { | ||||
|         // 查询 CartItemDO
 | ||||
|         CartItemDO item = cartMapper.selectByUserIdAndSkuIdAndStatus(userId, skuId, CartItemStatusEnum.ENABLE.getValue()); | ||||
|         if (item == null) { | ||||
|             return ServiceExceptionUtil.error(OrderErrorCodeEnum.CARD_ITEM_NOT_FOUND.getCode()); | ||||
|         } | ||||
|         // 更新 CartItemDO
 | ||||
|         CartItemDO updateCartItem = new CartItemDO().setId(item.getId()).setSelected(selected); | ||||
|         cartMapper.update(updateCartItem); | ||||
|         // 返回成功
 | ||||
|         return CommonResult.success(true); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|  | @ -54,8 +138,8 @@ public class CartServiceImpl implements CartService { | |||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public CommonResult<Integer> count(Integer userId, String nobody, Integer shopId) { | ||||
|         return null; | ||||
|     public CommonResult<Integer> count(Integer userId) { | ||||
|         return CommonResult.success(cartMapper.selectQuantitySumByUserIdAndStatus(userId, CartItemStatusEnum.ENABLE.getValue())); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|  |  | |||
|  | @ -0,0 +1,99 @@ | |||
| <?xml version="1.0" encoding="UTF-8"?> | ||||
| <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> | ||||
| <mapper namespace="cn.iocoder.mall.order.biz.dao.CartMapper"> | ||||
| 
 | ||||
|     <sql id="FIELDS"> | ||||
|         id, status, delete_time, selected, user_id, | ||||
|         spu_id, sku_id, quantity, order_id, order_create_time, | ||||
|         create_time | ||||
|     </sql> | ||||
| 
 | ||||
|     <select id="selectById" parameterType="Integer" resultType="CartItemDO"> | ||||
|         SELECT | ||||
|             <include refid="FIELDS" /> | ||||
|         FROM cart_item | ||||
|         WHERE id = #{id} | ||||
| --         AND deleted = 0 | ||||
|     </select> | ||||
| 
 | ||||
|     <select id="selectByIds" resultType="CartItemDO"> | ||||
|         SELECT | ||||
|             <include refid="FIELDS" /> | ||||
|         FROM cart_item | ||||
|         WHERE id IN | ||||
|             <foreach item="id" collection="ids" separator="," open="(" close=")" index=""> | ||||
|                 #{id} | ||||
|             </foreach> | ||||
| --         AND deleted = 0 | ||||
|     </select> | ||||
| 
 | ||||
|     <select id="selectByUserIdAndSkuIdAndStatus" resultType="CartItemDO"> | ||||
|         SELECT | ||||
|             <include refid="FIELDS" /> | ||||
|         FROM cart_item | ||||
|         WHERE user_id = #{userId} | ||||
|         AND sku_id = #{skuId} | ||||
|         AND status = #{status} | ||||
| --         AND deleted = 0 | ||||
|         LIMIT 1 | ||||
|     </select> | ||||
| 
 | ||||
|     <select id="selectQuantitySumByUserIdAndStatus" resultType="Integer"> | ||||
|         SELECT | ||||
|             SUM(quantity) | ||||
|         FROM cart_item | ||||
|         WHERE user_id = #{userId} | ||||
|         AND status = #{status} | ||||
| --         AND deleted = 0 | ||||
|     </select> | ||||
| 
 | ||||
|     <insert id="insert" parameterType="CartItemDO" useGeneratedKeys="true" keyColumn="id" keyProperty="id"> | ||||
|         INSERT INTO cart_item ( | ||||
|             status, delete_time, selected, user_id, | ||||
|             spu_id, sku_id, quantity, order_id, order_create_time, | ||||
|             create_time | ||||
|         ) VALUES ( | ||||
|             #{status}, #{deleteTime}, #{selected}, #{userId}, | ||||
|             #{spuId}, #{skuId}, #{quantity}, #{orderId}, #{orderCreateTime}, | ||||
|             #{createTime} | ||||
|         ) | ||||
|     </insert> | ||||
| 
 | ||||
|     <update id="update" parameterType="CartItemDO"> | ||||
|         UPDATE cart_item | ||||
|         <set> | ||||
|             <if test="status != null"> | ||||
|                 status = #{status}, | ||||
|             </if> | ||||
|             <if test="deleteTime != null"> | ||||
|                 delete_time = #{deleteTime}, | ||||
|             </if> | ||||
|             <if test="selected != null"> | ||||
|                 selected = #{selected}, | ||||
|             </if> | ||||
|             <if test="quantity != null"> | ||||
|                 quantity = #{quantity}, | ||||
|             </if> | ||||
|             <if test="orderId != null"> | ||||
|                 order_id = #{orderId}, | ||||
|             </if> | ||||
|             <if test="orderCreateTime != null"> | ||||
|                 order_create_time = #{orderCreateTime}, | ||||
|             </if> | ||||
|             <if test="price != null"> | ||||
|                 price = #{price}, | ||||
|             </if> | ||||
|             <if test="quantity != null"> | ||||
|                 quantity = #{quantity}, | ||||
|             </if> | ||||
|         </set> | ||||
|         WHERE id = #{id} | ||||
|     </update> | ||||
| 
 | ||||
|     <update id="updateQuantity" parameterType="CartItemDO"> | ||||
|         UPDATE cart_item | ||||
|         SET quantity = quantity + #{quantityIncr} | ||||
|         WHERE id = #{id} | ||||
|     </update> | ||||
| 
 | ||||
| </mapper> | ||||
|  | @ -17,6 +17,8 @@ public interface ProductSpuService { | |||
| 
 | ||||
|     CommonResult<List<ProductSpuBO>> getProductSpuList(Collection<Integer> ids); | ||||
| 
 | ||||
|     CommonResult<ProductSkuBO> getProductSku(Integer id); | ||||
| 
 | ||||
|     CommonResult<List<ProductSkuDetailBO>> getProductSkuDetailList(Collection<Integer> ids); | ||||
| 
 | ||||
|     CommonResult<ProductSpuDetailBO> addProductSpu(Integer adminId, ProductSpuAddDTO productSpuAddDTO); | ||||
|  |  | |||
|  | @ -6,15 +6,40 @@ import lombok.experimental.Accessors; | |||
| import java.io.Serializable; | ||||
| 
 | ||||
| /** | ||||
|  * 商品 SKU BOA | ||||
|  * 商品 SKU BO | ||||
|  */ | ||||
| @Data | ||||
| @Accessors(chain = true) | ||||
| public class ProductSkuBO implements Serializable { | ||||
| 
 | ||||
|     /** | ||||
|      * SKU 编号 | ||||
|      * sku 编号 | ||||
|      */ | ||||
|     private Integer id; | ||||
|     /** | ||||
|      * 商品编号 | ||||
|      */ | ||||
|     private Integer spuId; | ||||
|     /** | ||||
|      * 状态 | ||||
|      */ | ||||
|     private Integer status; | ||||
|     /** | ||||
|      * 图片地址 | ||||
|      */ | ||||
|     private String picURL; | ||||
| //    /**
 | ||||
| //     * 规格值数组
 | ||||
| //     */
 | ||||
| //    // TODO 芋艿,这个属性目前未进行设置
 | ||||
| //    private List<ProductAttrAndValuePairBO> attrs;
 | ||||
|     /** | ||||
|      * 价格,单位:分 | ||||
|      */ | ||||
|     private Integer price; | ||||
|     /** | ||||
|      * 库存数量 | ||||
|      */ | ||||
|     private Integer quantity; | ||||
| 
 | ||||
| } | ||||
|  |  | |||
|  | @ -1,10 +1,7 @@ | |||
| package cn.iocoder.mall.product.convert; | ||||
| 
 | ||||
| import cn.iocoder.common.framework.util.StringUtil; | ||||
| import cn.iocoder.mall.product.api.bo.ProductAttrAndValuePairBO; | ||||
| import cn.iocoder.mall.product.api.bo.ProductSkuDetailBO; | ||||
| import cn.iocoder.mall.product.api.bo.ProductSpuBO; | ||||
| import cn.iocoder.mall.product.api.bo.ProductSpuDetailBO; | ||||
| import cn.iocoder.mall.product.api.bo.*; | ||||
| import cn.iocoder.mall.product.api.dto.ProductSkuAddOrUpdateDTO; | ||||
| import cn.iocoder.mall.product.api.dto.ProductSpuAddDTO; | ||||
| import cn.iocoder.mall.product.api.dto.ProductSpuUpdateDTO; | ||||
|  | @ -73,6 +70,11 @@ public interface ProductSpuConvert { | |||
|     }) | ||||
|     ProductSkuDetailBO convert3(ProductSkuDO sku); | ||||
| 
 | ||||
|     @Mappings({ | ||||
| //            @Mapping(source = "attrs", target = "attrs", ignore = true) // TODO 芋艿 后续补充
 | ||||
|     }) | ||||
|     ProductSkuBO convert4(ProductSkuDO sku); | ||||
| 
 | ||||
|     @Mappings({}) // TODO 芋艿,后续细看下 mapstruct 的 API ,优化这块
 | ||||
|     default ProductSpuDetailBO convert2(ProductSpuDO spu, List<ProductSkuDO> skus, List<ProductAttrAndValuePairBO> productAttrDetailBOs) { | ||||
|         // 创建并转换 ProductSpuDetailBO 对象
 | ||||
|  |  | |||
|  | @ -213,6 +213,12 @@ public class ProductSpuServiceImpl implements ProductSpuService { | |||
|         return CommonResult.success(ProductSpuConvert.INSTANCE.convert(spus)); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public CommonResult<ProductSkuBO> getProductSku(Integer id) { | ||||
|         ProductSkuDO sku = productSkuMapper.selectById(id); | ||||
|         return CommonResult.success(ProductSpuConvert.INSTANCE.convert4(sku)); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public CommonResult<List<ProductSkuDetailBO>> getProductSkuDetailList(Collection<Integer> ids) { | ||||
|         // 查询 SKU 数组
 | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue
	
	 YunaiV
						YunaiV