From 7728cc456aebdf95888c05967ae823f6f58d2a36 Mon Sep 17 00:00:00 2001 From: XinWei <2718030729@qq.com> Date: Fri, 27 Sep 2024 13:55:01 +0800 Subject: [PATCH] =?UTF-8?q?=E6=8E=A8=E8=8D=90=E5=95=86=E5=93=81=E7=B1=BB?= =?UTF-8?q?=E5=9E=8B-=E5=8C=85=E6=8B=AC=E4=BF=83=E9=94=80=E6=8E=A8?= =?UTF-8?q?=E8=8D=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- yudao-admin-vue3/.vscode/settings.json | 2 +- .../views/mall/product/spu/form/SkuForm.vue | 42 +++++++++++++++++ .../src/views/mall/product/spu/form/index.vue | 6 +++ .../product/enums/ErrorCodeConstants.java | 2 + .../admin/spu/ProductSpuController.java | 11 ++++- .../admin/spu/vo/ProductSpuRespVO.java | 20 ++++++++ .../admin/spu/vo/ProductSpuSaveReqVO.java | 19 ++++++++ .../app/spu/AppProductSpuController.java | 46 +++++++++++++++++++ .../vo/AppProductSpuRecommendPageReqVo.java | 26 +++++++++++ .../dal/dataobject/spu/ProductSpuDO.java | 27 +++++++++++ .../service/spu/ProductSpuService.java | 7 +++ .../service/spu/ProductSpuServiceImpl.java | 31 +++++++++++++ 12 files changed, 237 insertions(+), 2 deletions(-) create mode 100644 yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/app/spu/vo/AppProductSpuRecommendPageReqVo.java diff --git a/yudao-admin-vue3/.vscode/settings.json b/yudao-admin-vue3/.vscode/settings.json index 54be7d8..e4ec7cd 100644 --- a/yudao-admin-vue3/.vscode/settings.json +++ b/yudao-admin-vue3/.vscode/settings.json @@ -86,7 +86,7 @@ "source.fixAll.eslint": "explicit" }, "[vue]": { - "editor.defaultFormatter": "rvest.vs-code-prettier-eslint" + "editor.defaultFormatter": "Vue.volar" }, "i18n-ally.localesPaths": ["src/locales"], "i18n-ally.keystyle": "nested", diff --git a/yudao-admin-vue3/src/views/mall/product/spu/form/SkuForm.vue b/yudao-admin-vue3/src/views/mall/product/spu/form/SkuForm.vue index 9cc6192..89f3008 100644 --- a/yudao-admin-vue3/src/views/mall/product/spu/form/SkuForm.vue +++ b/yudao-admin-vue3/src/views/mall/product/spu/form/SkuForm.vue @@ -17,6 +17,43 @@ 多规格 + + + 热卖单品 + + + 促销单品 + + + 精品推荐 + + + 新品单品 + + + 优品推荐 + + ({ specType: false, // 商品规格 subCommissionType: false, // 分销类型 + recommendHot: 0, // 热卖推荐 + recommendBenefit: 0, // 优惠推荐 + recommendBest: 0, // 精品推荐 + recommendNew: 0, // 新品推荐 + recommendGood: 0, // 优品推荐 skus: [] }) const rules = reactive({ diff --git a/yudao-admin-vue3/src/views/mall/product/spu/form/index.vue b/yudao-admin-vue3/src/views/mall/product/spu/form/index.vue index e4bef3d..1ba5f5e 100644 --- a/yudao-admin-vue3/src/views/mall/product/spu/form/index.vue +++ b/yudao-admin-vue3/src/views/mall/product/spu/form/index.vue @@ -92,6 +92,11 @@ const formData = ref({ brandId: undefined, // 商品品牌 specType: false, // 商品规格 subCommissionType: false, // 分销类型 + recommendHot: 0, // 热卖推荐 + recommendBenefit: 0, // 优惠推荐 + recommendBest: 0, // 精品推荐 + recommendNew: 0, // 新品推荐 + recommendGood: 0, // 优品推荐 skus: [ { price: 0, // 商品价格 @@ -141,6 +146,7 @@ const getDetail = async () => { } }) formData.value = res + } finally { formLoading.value = false } diff --git a/yudao-module-mall/yudao-module-product-api/src/main/java/cn/iocoder/yudao/module/product/enums/ErrorCodeConstants.java b/yudao-module-mall/yudao-module-product-api/src/main/java/cn/iocoder/yudao/module/product/enums/ErrorCodeConstants.java index 1d0ea18..7d32aad 100644 --- a/yudao-module-mall/yudao-module-product-api/src/main/java/cn/iocoder/yudao/module/product/enums/ErrorCodeConstants.java +++ b/yudao-module-mall/yudao-module-product-api/src/main/java/cn/iocoder/yudao/module/product/enums/ErrorCodeConstants.java @@ -53,4 +53,6 @@ public interface ErrorCodeConstants { ErrorCode FAVORITE_EXISTS = new ErrorCode(1_008_008_000, "该商品已经被收藏"); ErrorCode FAVORITE_NOT_EXISTS = new ErrorCode(1_008_008_001, "商品收藏不存在"); + ErrorCode PARAMETER_ERROR = new ErrorCode(1_008_008_002, "参数错误"); + } diff --git a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/spu/ProductSpuController.java b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/spu/ProductSpuController.java index 0c30897..e8d00f9 100644 --- a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/spu/ProductSpuController.java +++ b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/spu/ProductSpuController.java @@ -6,6 +6,9 @@ import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.util.object.BeanUtils; import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils; import cn.iocoder.yudao.module.product.controller.admin.spu.vo.*; +import cn.iocoder.yudao.module.product.controller.app.spu.AppProductSpuController; +import cn.iocoder.yudao.module.product.controller.app.spu.vo.AppProductSpuRecommendPageReqVo; +import cn.iocoder.yudao.module.product.controller.app.spu.vo.AppProductSpuRespVO; import cn.iocoder.yudao.module.product.convert.spu.ProductSpuConvert; import cn.iocoder.yudao.module.product.dal.dataobject.sku.ProductSkuDO; import cn.iocoder.yudao.module.product.dal.dataobject.spu.ProductSpuDO; @@ -87,7 +90,13 @@ public class ProductSpuController { } // 查询商品 SKU List skus = productSkuService.getSkuListBySpuId(spu.getId()); - return success(ProductSpuConvert.INSTANCE.convert(spu, skus)); + ProductSpuRespVO convert = ProductSpuConvert.INSTANCE.convert(spu, skus); + convert.setRecommendHot(spu.getRecommendHot()); + convert.setRecommendBenefit(spu.getRecommendBenefit()); + convert.setRecommendBest(spu.getRecommendBest()); + convert.setRecommendNew(spu.getRecommendNew()); + convert.setRecommendGood(spu.getRecommendGood()); + return success(convert); } @GetMapping("/list-all-simple") diff --git a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/spu/vo/ProductSpuRespVO.java b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/spu/vo/ProductSpuRespVO.java index 20a541c..1fe90eb 100644 --- a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/spu/vo/ProductSpuRespVO.java +++ b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/spu/vo/ProductSpuRespVO.java @@ -9,6 +9,7 @@ import com.alibaba.excel.annotation.ExcelProperty; import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; +import javax.validation.Valid; import java.time.LocalDateTime; import java.util.List; @@ -65,6 +66,25 @@ public class ProductSpuRespVO { @ExcelProperty("创建时间") private LocalDateTime createTime; + @Schema(description = "是否热卖推荐") + @ExcelProperty("是否热卖推荐") + private Integer recommendHot; + + @Schema(description = "是否促销推荐") + @ExcelProperty("是否优惠推荐") + private Integer recommendBenefit; + + @Schema(description = "是否精品推荐") + @ExcelProperty("是否精品推荐") + private Integer recommendBest; + + @Schema(description = "是否新品推荐") + @ExcelProperty("是否新品推荐") + private Integer recommendNew; + + @Schema(description = "是否优品推荐") + @ExcelProperty("是否优品推荐") + private Integer recommendGood; // ========== SKU 相关字段 ========= @Schema(description = "规格类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "true") diff --git a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/spu/vo/ProductSpuSaveReqVO.java b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/spu/vo/ProductSpuSaveReqVO.java index c5805f6..d3798b2 100644 --- a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/spu/vo/ProductSpuSaveReqVO.java +++ b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/spu/vo/ProductSpuSaveReqVO.java @@ -99,4 +99,23 @@ public class ProductSpuSaveReqVO { @Valid private List skus; + @Schema(description = "是否热卖推荐") + @Valid + private Integer recommendHot; + + @Schema(description = "是否促销推荐") + @Valid + private Integer recommendBenefit; + + @Schema(description = "是否精品推荐") + @Valid + private Integer recommendBest; + + @Schema(description = "是否新品推荐") + @Valid + private Integer recommendNew; + + @Schema(description = "是否优品推荐") + @Valid + private Integer recommendGood; } diff --git a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/app/spu/AppProductSpuController.java b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/app/spu/AppProductSpuController.java index f64e6f5..7706e1c 100644 --- a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/app/spu/AppProductSpuController.java +++ b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/app/spu/AppProductSpuController.java @@ -10,16 +10,21 @@ import cn.iocoder.yudao.module.member.api.user.MemberUserApi; import cn.iocoder.yudao.module.member.api.user.dto.MemberUserRespDTO; import cn.iocoder.yudao.module.product.controller.app.spu.vo.AppProductSpuDetailRespVO; import cn.iocoder.yudao.module.product.controller.app.spu.vo.AppProductSpuPageReqVO; +import cn.iocoder.yudao.module.product.controller.app.spu.vo.AppProductSpuRecommendPageReqVo; import cn.iocoder.yudao.module.product.controller.app.spu.vo.AppProductSpuRespVO; import cn.iocoder.yudao.module.product.dal.dataobject.sku.ProductSkuDO; import cn.iocoder.yudao.module.product.dal.dataobject.spu.ProductSpuDO; +import cn.iocoder.yudao.module.product.enums.ErrorCodeConstants; import cn.iocoder.yudao.module.product.enums.spu.ProductSpuStatusEnum; import cn.iocoder.yudao.module.product.service.history.ProductBrowseHistoryService; import cn.iocoder.yudao.module.product.service.sku.ProductSkuService; import cn.iocoder.yudao.module.product.service.spu.ProductSpuService; +import cn.iocoder.yudao.module.product.service.spu.ProductSpuServiceImpl; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.tags.Tag; +import org.apache.commons.math3.stat.descriptive.summary.Product; +import org.apache.poi.ss.formula.functions.T; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; @@ -33,6 +38,7 @@ import java.util.List; import java.util.Set; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; +import static cn.iocoder.yudao.framework.common.pojo.CommonResult.error; import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId; import static cn.iocoder.yudao.module.product.enums.ErrorCodeConstants.SPU_NOT_ENABLE; @@ -91,6 +97,23 @@ public class AppProductSpuController { return success(voPageResult); } + @GetMapping("/get-recommend-page") + @Operation(summary = "获得推荐类型对应的商品 SPU 分页") + public CommonResult> getRecommendPage(@Valid AppProductSpuRecommendPageReqVo recommendPageVo){ + List recommendProductList = productSpuService.getRecommendProductIdList(recommendPageVo); + PageResult pageResult = getPaginatedList(recommendProductList, recommendPageVo.getPageNo(), recommendPageVo.getPageSize()); + if (CollUtil.isEmpty(pageResult.getList())) { + return success(PageResult.empty(pageResult.getTotal())); + } + // 拼接返回 + pageResult.getList().forEach(spu -> spu.setSalesCount(spu.getSalesCount() + spu.getVirtualSalesCount())); + PageResult voPageResult = BeanUtils.toBean(pageResult, AppProductSpuRespVO.class); + // 处理 vip 价格 + MemberLevelRespDTO memberLevel = getMemberLevel(); + voPageResult.getList().forEach(vo -> vo.setVipPrice(calculateVipPrice(vo.getPrice(), memberLevel))); + return success(voPageResult); + } + @GetMapping("/get-detail") @Operation(summary = "获得商品 SPU 明细") @Parameter(name = "id", description = "编号", required = true) @@ -148,5 +171,28 @@ public class AppProductSpuController { return price - newPrice; } + /** + * 逻辑分页 + * @param list + * @param pageSize + * @param pageNumber + * @return java.util.List + */ + private PageResult getPaginatedList(List list, int pageNumber, int pageSize) { + // 计算起始索引 + int fromIndex = (pageNumber - 1) * pageSize; + // 计算结束索引 + int toIndex = Math.min(fromIndex + pageSize, list.size()); + PageResult pageResult = new PageResult<>(); + pageResult.setTotal((long)list.size()); + // 如果起始索引超出范围,返回空列表 + if (fromIndex >= list.size() || fromIndex < 0) { + return pageResult; + } + // 逻辑分页 + List result = list.subList(fromIndex, toIndex); + pageResult.setList(result); + return pageResult; + } // TODO 芋艿:商品的浏览记录; } diff --git a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/app/spu/vo/AppProductSpuRecommendPageReqVo.java b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/app/spu/vo/AppProductSpuRecommendPageReqVo.java new file mode 100644 index 0000000..c2d6858 --- /dev/null +++ b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/app/spu/vo/AppProductSpuRecommendPageReqVo.java @@ -0,0 +1,26 @@ +package cn.iocoder.yudao.module.product.controller.app.spu.vo; + +import cn.iocoder.yudao.framework.common.pojo.PageParam; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.ToString; + +import java.util.List; + +@Schema(description = "用户 App - 推荐商品 SPU 分页 Request VO") +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +public class AppProductSpuRecommendPageReqVo extends PageParam { + @Schema(description = "热卖推荐", example = "1") + private Integer recommendHot; + @Schema(description = "促销推荐", example = "1") + private Integer recommendBenefit; + @Schema(description = "精品推荐", example = "1") + private Integer recommendBest; + @Schema(description = "新品推荐", example = "1") + private Integer recommendNew; + @Schema(description = "优品推荐", example = "1") + private Integer recommendGood; +} diff --git a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/dal/dataobject/spu/ProductSpuDO.java b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/dal/dataobject/spu/ProductSpuDO.java index 0c017f2..03ba4db 100644 --- a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/dal/dataobject/spu/ProductSpuDO.java +++ b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/dal/dataobject/spu/ProductSpuDO.java @@ -6,11 +6,13 @@ import cn.iocoder.yudao.module.product.dal.dataobject.brand.ProductBrandDO; import cn.iocoder.yudao.module.product.dal.dataobject.category.ProductCategoryDO; import cn.iocoder.yudao.module.product.dal.dataobject.sku.ProductSkuDO; import cn.iocoder.yudao.module.product.enums.spu.ProductSpuStatusEnum; +import com.alibaba.excel.annotation.ExcelProperty; import com.baomidou.mybatisplus.annotation.KeySequence; import com.baomidou.mybatisplus.annotation.TableField; import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableName; import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler; +import io.swagger.v3.oas.annotations.media.Schema; import lombok.*; import java.util.List; @@ -88,6 +90,31 @@ public class ProductSpuDO extends BaseDO { */ private Integer status; + /** + * 是否热卖推荐 + */ + private Integer recommendHot; + + /** + * 是否优惠推荐 + */ + private Integer recommendBenefit; + + /** + * 是否精品推荐 + */ + private Integer recommendBest; + + /** + * 是否新品推荐 + */ + private Integer recommendNew; + + /** + * 是否优品推荐 + */ + private Integer recommendGood; + // ========== SKU 相关字段 ========= /** diff --git a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/service/spu/ProductSpuService.java b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/service/spu/ProductSpuService.java index 63b5dd6..635091d 100644 --- a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/service/spu/ProductSpuService.java +++ b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/service/spu/ProductSpuService.java @@ -5,6 +5,7 @@ import cn.iocoder.yudao.module.product.controller.admin.spu.vo.ProductSpuPageReq import cn.iocoder.yudao.module.product.controller.admin.spu.vo.ProductSpuSaveReqVO; import cn.iocoder.yudao.module.product.controller.admin.spu.vo.ProductSpuUpdateStatusReqVO; import cn.iocoder.yudao.module.product.controller.app.spu.vo.AppProductSpuPageReqVO; +import cn.iocoder.yudao.module.product.controller.app.spu.vo.AppProductSpuRecommendPageReqVo; import cn.iocoder.yudao.module.product.dal.dataobject.spu.ProductSpuDO; import org.springframework.scheduling.annotation.Async; @@ -131,4 +132,10 @@ public interface ProductSpuService { @Async void updateBrowseCount(Long id, int incrCount); + /** + * 获取推荐类型的 SPU ID列表 + * @param pageVo + * @return + */ + List getRecommendProductIdList(AppProductSpuRecommendPageReqVo pageVo); } diff --git a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/service/spu/ProductSpuServiceImpl.java b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/service/spu/ProductSpuServiceImpl.java index 9b145b0..bc2df3a 100644 --- a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/service/spu/ProductSpuServiceImpl.java +++ b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/service/spu/ProductSpuServiceImpl.java @@ -6,12 +6,15 @@ import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; import cn.iocoder.yudao.framework.common.util.object.BeanUtils; +import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; +import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; import cn.iocoder.yudao.module.product.controller.admin.category.vo.ProductCategoryListReqVO; import cn.iocoder.yudao.module.product.controller.admin.spu.vo.ProductSkuSaveReqVO; import cn.iocoder.yudao.module.product.controller.admin.spu.vo.ProductSpuPageReqVO; import cn.iocoder.yudao.module.product.controller.admin.spu.vo.ProductSpuSaveReqVO; import cn.iocoder.yudao.module.product.controller.admin.spu.vo.ProductSpuUpdateStatusReqVO; import cn.iocoder.yudao.module.product.controller.app.spu.vo.AppProductSpuPageReqVO; +import cn.iocoder.yudao.module.product.controller.app.spu.vo.AppProductSpuRecommendPageReqVo; import cn.iocoder.yudao.module.product.dal.dataobject.category.ProductCategoryDO; import cn.iocoder.yudao.module.product.dal.dataobject.spu.ProductSpuDO; import cn.iocoder.yudao.module.product.dal.mysql.spu.ProductSpuMapper; @@ -160,6 +163,34 @@ public class ProductSpuServiceImpl implements ProductSpuService { productSpuMapper.updateBrowseCount(id , incrCount); } + @Override + public List getRecommendProductIdList(AppProductSpuRecommendPageReqVo pageVo) { + LambdaQueryWrapperX wrapperX = new LambdaQueryWrapperX<>(); + Integer recommendHot = pageVo.getRecommendHot(); + Integer recommendBenefit = pageVo.getRecommendBenefit(); + Integer recommendBest = pageVo.getRecommendBest(); + Integer recommendNew = pageVo.getRecommendNew(); + Integer recommendGood = pageVo.getRecommendGood(); + if (recommendHot != null && recommendHot != 0){ + wrapperX.eq(ProductSpuDO::getRecommendHot, recommendHot); + } + if (recommendBenefit != null && recommendBenefit != 0){ + wrapperX.eq(ProductSpuDO::getRecommendBenefit, recommendBenefit); + } + if (recommendBest != null && recommendBest != 0){ + wrapperX.eq(ProductSpuDO::getRecommendBest, recommendBest); + } + if (recommendNew != null && recommendNew != 0){ + wrapperX.eq(ProductSpuDO::getRecommendNew, recommendNew); + } + if (recommendGood != null && recommendGood != 0){ + wrapperX.eq(ProductSpuDO::getRecommendGood, recommendGood); + } + wrapperX.eq(ProductSpuDO::getStatus, 1); + wrapperX.last(" ORDER BY (sales_count + virtual_sales_count) DESC, sort DESC, id DESC"); + return productSpuMapper.selectList(wrapperX); + } + @Override @Transactional(rollbackFor = Exception.class) public void deleteSpu(Long id) { -- 2.45.2