feat(mall): 新增活动管理功能
- 添加活动列表、活动开启、活动关闭、活动删除等功能
- 实现活动商品的复制和价格计算
- 优化活动页面样式和交互
1 files added
10 files modified
| | |
| | | import org.springframework.web.bind.annotation.*; |
| | | |
| | | import javax.validation.Valid; |
| | | import javax.validation.constraints.NotNull; |
| | | import java.util.Map; |
| | | |
| | | @Slf4j |
| | |
| | | @PostMapping("add") |
| | | @ControllerEndpoint(operation = "新增", exceptionMessage = "新增失败") |
| | | public FebsResponse addActivity(@RequestBody @Valid AdminActivityAddDto adminActivityAddDto) { |
| | | return iActivityService.addActivity(adminActivityAddDto); |
| | | return iActivityService.addAdminActivity(adminActivityAddDto); |
| | | } |
| | | |
| | | /** |
| | |
| | | public FebsResponse discountUpdate(@RequestBody @Valid AdminDiscountVO adminDiscountVO) { |
| | | return iActivityService.discountUpdate(adminDiscountVO); |
| | | } |
| | | |
| | | |
| | | @GetMapping(value = "/activityList") |
| | | public FebsResponse activityList() { |
| | | return new FebsResponse().success().data(iActivityService.getAdminActivityList()); |
| | | } |
| | | |
| | | /** |
| | | * 活动-开启 |
| | | */ |
| | | @GetMapping("changeState/{id}/{state}") |
| | | @ControllerEndpoint(operation = "活动-开启", exceptionMessage = "操作失败") |
| | | public FebsResponse changeState(@NotNull(message = "{required}") @PathVariable Long id |
| | | ,@NotNull(message = "{required}") @PathVariable Integer state) { |
| | | return iActivityService.changeAdminState(id,state); |
| | | } |
| | | |
| | | /** |
| | | * 活动-删除 |
| | | */ |
| | | @GetMapping("delActivity/{id}") |
| | | @ControllerEndpoint(operation = "活动-删除", exceptionMessage = "删除失败") |
| | | public FebsResponse delActivity(@NotNull(message = "{required}") @PathVariable Long id) { |
| | | return iActivityService.delAdminActivity(id); |
| | | } |
| | | } |
| | |
| | | import io.swagger.annotations.ApiModelProperty; |
| | | import lombok.Data; |
| | | |
| | | import javax.validation.constraints.NotNull; |
| | | |
| | | /** |
| | | * @author wzy |
| | | * @date 2021-09-17 |
| | |
| | | @ApiModelProperty(value = "是否热卖", example = "1是2否") |
| | | private Integer isHot; |
| | | |
| | | @ApiModelProperty(value = "是否套餐 1-普通商品 2-套餐", example = "2") |
| | | @ApiModelProperty(value = "是否套餐 1-普通商品 2-积分商品 3-活动商品 ", example = "2") |
| | | private Integer isNormal; |
| | | |
| | | @ApiModelProperty(value = "是否套餐 1-普通商品 2-积分商品 3-活动商品 ", example = "2") |
| | | private Long activityId; |
| | | |
| | | @ApiModelProperty(value = "1-付费商品 2-积分商品") |
| | | private Integer goodsType; |
| | | |
| | |
| | | import cc.mrbird.febs.mall.entity.MallActivity; |
| | | import cc.mrbird.febs.mall.vo.AdminActivityAddDto; |
| | | import cc.mrbird.febs.mall.vo.AdminDiscountVO; |
| | | import cc.mrbird.febs.mall.vo.AdminMallActivityListVo; |
| | | import com.baomidou.mybatisplus.core.metadata.IPage; |
| | | import com.baomidou.mybatisplus.extension.service.IService; |
| | | |
| | | import java.util.List; |
| | | |
| | | public interface IActivityService extends IService<MallActivity> { |
| | | |
| | | IPage<MallActivity> getAdminListInPage(MallActivity mallActivity, QueryRequest request); |
| | | |
| | | FebsResponse addActivity(AdminActivityAddDto adminActivityAddDto); |
| | | FebsResponse addAdminActivity(AdminActivityAddDto adminActivityAddDto); |
| | | |
| | | FebsResponse discountUpdate(AdminDiscountVO adminDiscountVO); |
| | | |
| | | List<AdminMallActivityListVo> getAdminActivityList(); |
| | | |
| | | FebsResponse changeAdminState(Long id, Integer state); |
| | | |
| | | FebsResponse delAdminActivity(Long id); |
| | | } |
| | |
| | | package cc.mrbird.febs.mall.service.impl; |
| | | |
| | | import cc.mrbird.febs.common.configure.FebsConfigure; |
| | | import cc.mrbird.febs.common.entity.FebsResponse; |
| | | import cc.mrbird.febs.common.entity.QueryRequest; |
| | | import cc.mrbird.febs.common.enumerates.ActivityTypeEnum; |
| | |
| | | import cc.mrbird.febs.mall.service.IActivityService; |
| | | import cc.mrbird.febs.mall.vo.AdminActivityAddDto; |
| | | import cc.mrbird.febs.mall.vo.AdminDiscountVO; |
| | | import cc.mrbird.febs.mall.vo.AdminMallActivityListVo; |
| | | import cn.hutool.core.collection.CollUtil; |
| | | import cn.hutool.core.util.ObjectUtil; |
| | | import cn.hutool.core.util.StrUtil; |
| | |
| | | |
| | | import java.math.BigDecimal; |
| | | import java.math.RoundingMode; |
| | | import java.util.ArrayList; |
| | | import java.util.List; |
| | | import java.util.concurrent.CompletableFuture; |
| | | import java.util.stream.Collectors; |
| | | |
| | | @Slf4j |
| | |
| | | private final MallGoodsMapper mallGoodsMapper; |
| | | private final MallGoodsSkuMapper mallGoodsSkuMapper; |
| | | private final MallGoodsStyleMapper mallGoodsStyleMapper; |
| | | private final FebsConfigure febsConfigure; |
| | | @Override |
| | | public IPage<MallActivity> getAdminListInPage(MallActivity mallActivity, QueryRequest request) { |
| | | Page<MallActivity> page = new Page<>(request.getPageNum(), request.getPageSize()); |
| | |
| | | } |
| | | |
| | | @Override |
| | | public FebsResponse addActivity(AdminActivityAddDto adminActivityAddDto) { |
| | | public FebsResponse addAdminActivity(AdminActivityAddDto adminActivityAddDto) { |
| | | |
| | | MallActivity mallActivity = new MallActivity(); |
| | | mallActivity.setName(adminActivityAddDto.getName()); |
| | |
| | | } |
| | | |
| | | private void addDiscountGoods(Long activityId,BigDecimal discountSize, List<Long> goodsIds){ |
| | | MallActivity mallActivity = this.baseMapper.selectById(activityId); |
| | | //将对应选中的商品按照这个折扣力度重新计算一遍 |
| | | List<MallGoods> mallGoods = mallGoodsMapper.selectList( |
| | | new LambdaQueryWrapper<MallGoods>() |
| | |
| | | .in(MallGoods::getId, goodsIds) |
| | | ); |
| | | if(CollUtil.isNotEmpty(mallGoods)){ |
| | | List<CompletableFuture<Void>> futures = new ArrayList<>(); |
| | | //复制对应的商品的规则 |
| | | for(MallGoods mallGoodsItem:mallGoods){ |
| | | CompletableFuture<Void> uCompletableFuture = CompletableFuture.runAsync(() -> { |
| | | Long goodsId = mallGoodsItem.getId(); |
| | | //新增活动商品 |
| | | Long goodsIdNew = addGoodsEntity(activityId, discountSize, mallGoodsItem); |
| | | Long goodsIdNew = addGoodsEntity(mallActivity, discountSize, mallGoodsItem); |
| | | |
| | | List<MallGoodsStyle> mallGoodsStyles = mallGoodsStyleMapper.selectList( |
| | | new LambdaQueryWrapper<MallGoodsStyle>() |
| | |
| | | } |
| | | } |
| | | } |
| | | },febsConfigure.asyncThreadPoolTaskExecutor()); |
| | | futures.add(uCompletableFuture); |
| | | } |
| | | // 等待所有任务完成 |
| | | CompletableFuture<Void> allOf = CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])); |
| | | allOf.join(); |
| | | } |
| | | } |
| | | |
| | | private Long addGoodsEntity(Long activityId,BigDecimal discountSize,MallGoods mallGoodsItem){ |
| | | MallActivity mallActivity = this.baseMapper.selectById(activityId); |
| | | private Long addGoodsEntity(MallActivity mallActivity,BigDecimal discountSize,MallGoods mallGoodsItem){ |
| | | mallGoodsItem.setIsNormal(GoodsTypeEnum.HUO_DONG.getValue()); |
| | | mallGoodsItem.setGoodsName(mallActivity.getName()+mallGoodsItem.getGoodsName()); |
| | | mallGoodsItem.setGoodsName(mallActivity.getName()+"-"+(StrUtil.isEmpty(mallGoodsItem.getGoodsName()) ? "":mallGoodsItem.getGoodsName())); |
| | | mallGoodsItem.setGoodsNo(mallGoodsItem.getGoodsNo()+"-"+mallActivity.getCode()); |
| | | mallGoodsItem.setActivityId(mallActivity.getId()); |
| | | BigDecimal presentPrice = new BigDecimal(mallGoodsItem.getPresentPrice()).multiply(discountSize).setScale(2, RoundingMode.DOWN); |
| | | mallGoodsItem.setPresentPrice(presentPrice.toString()); |
| | |
| | | public FebsResponse discountUpdate(AdminDiscountVO adminDiscountVO) { |
| | | return null; |
| | | } |
| | | |
| | | @Override |
| | | public List<AdminMallActivityListVo> getAdminActivityList() { |
| | | List<AdminMallActivityListVo> mallActivityList = new ArrayList<>(); |
| | | |
| | | List<MallActivity> mallActivities = this.baseMapper.selectList( |
| | | new LambdaQueryWrapper<MallActivity>() |
| | | .select(MallActivity::getId, MallActivity::getName) |
| | | .eq(MallActivity::getState, YesOrNoOrIngEnum.YES.getValue()) |
| | | ); |
| | | if(CollUtil.isNotEmpty(mallActivities)){ |
| | | for (MallActivity mallActivity : mallActivities) { |
| | | AdminMallActivityListVo adminMallActivityListVo = new AdminMallActivityListVo(); |
| | | adminMallActivityListVo.setId(mallActivity.getId()); |
| | | adminMallActivityListVo.setName(mallActivity.getName()); |
| | | mallActivityList.add(adminMallActivityListVo); |
| | | } |
| | | } |
| | | return mallActivityList; |
| | | } |
| | | |
| | | @Override |
| | | public FebsResponse changeAdminState(Long id, Integer state) { |
| | | MallActivity mallActivity = this.baseMapper.selectById(id); |
| | | mallActivity.setState(state); |
| | | this.baseMapper.updateById(mallActivity); |
| | | return new FebsResponse().success().message("操作成功"); |
| | | } |
| | | |
| | | @Override |
| | | public FebsResponse delAdminActivity(Long id) { |
| | | this.baseMapper.deleteById(id); |
| | | return new FebsResponse().success().message("操作成功"); |
| | | } |
| | | } |
| | |
| | | package cc.mrbird.febs.mall.service.impl; |
| | | |
| | | import cc.mrbird.febs.mall.entity.MallAddressWorld; |
| | | import cc.mrbird.febs.mall.entity.MallGoods; |
| | | import cc.mrbird.febs.mall.entity.MallGoodsCategory; |
| | | import cc.mrbird.febs.mall.mapper.MallAddressWorldMapper; |
| | | import cc.mrbird.febs.mall.mapper.MallGoodsCategoryMapper; |
| | | import cc.mrbird.febs.mall.mapper.MallGoodsMapper; |
| | | import cc.mrbird.febs.mall.service.IApiMallGoodsCategoryService; |
| | | import cc.mrbird.febs.mall.vo.AdminWorldAddressVo; |
| | | import cc.mrbird.febs.mall.vo.MallGoodsCategoryVo; |
| | |
| | | public class ApiMallGoodsCategoryServiceImpl extends ServiceImpl<MallGoodsCategoryMapper, MallGoodsCategory> implements IApiMallGoodsCategoryService { |
| | | |
| | | private final MallAddressWorldMapper mallAddressWorldMapper; |
| | | private final MallGoodsMapper mallGoodsMapper; |
| | | |
| | | @Override |
| | | public List<MallGoodsCategoryVo> findAllCategoryList() { |
| | | return this.baseMapper.selectAllCategoryList(); |
| | | List<MallGoodsCategoryVo> mallGoodsCategoryVos = this.baseMapper.selectAllCategoryList(); |
| | | if(CollUtil.isNotEmpty(mallGoodsCategoryVos)){ |
| | | mallGoodsCategoryVos.forEach(item -> { |
| | | Long id = item.getId(); |
| | | List<MallGoods> mallGoods = mallGoodsMapper.selectList( |
| | | new LambdaQueryWrapper<MallGoods>() |
| | | .select(MallGoods::getId) |
| | | .eq(MallGoods::getCategoryId, id)); |
| | | if(CollUtil.isNotEmpty(mallGoods)){ |
| | | mallGoodsCategoryVos.remove(item); |
| | | } |
| | | }); |
| | | } |
| | | return mallGoodsCategoryVos; |
| | | } |
| | | |
| | | @Override |
New file |
| | |
| | | package cc.mrbird.febs.mall.vo; |
| | | |
| | | import io.swagger.annotations.ApiModel; |
| | | import lombok.Data; |
| | | |
| | | @Data |
| | | @ApiModel(value = "AdminMallActivityListVo", description = "信息返回类") |
| | | public class AdminMallActivityListVo { |
| | | |
| | | private Long id; |
| | | |
| | | private String name; |
| | | } |
| | |
| | | select |
| | | a.id, |
| | | a.goods_name, |
| | | a.goods_introdution goodsIntroduction, |
| | | a.thumb, |
| | | a.unit, |
| | | a.original_price, |
| | | a.present_price, |
| | | a.level_one_price, |
| | | a.level_two_price, |
| | | a.level_three_price, |
| | | a.score, |
| | | a.is_hot, |
| | | a.carriage_type, |
| | | a.carriage_amount, |
| | | a.carriage_rule_id, |
| | | a.goods_weight, |
| | | a.order_number, |
| | | <if test="record.memberId != null"> |
| | | case when collection.id is null then 2 else 1 end hasCollect, |
| | |
| | | <if test="record.isNormal != null and record.isNormal != ''"> |
| | | and a.is_normal = #{record.isNormal} |
| | | </if> |
| | | <if test="record.isNormal == 3"> |
| | | and a.activity_id = #{record.activityId} |
| | | </if> |
| | | <if test="record.goodsType != null and record.goodsType != '' and record.goodsType == 1"> |
| | | and a.goods_type = #{record.goodsType} and a.present_price != 0 |
| | | </if> |
| | |
| | | iconfont: { |
| | | parent: 'hidden', |
| | | }, |
| | | // radio: true, |
| | | radio: true, |
| | | clickClose: true, |
| | | tree: { |
| | | show: true, |
| | |
| | | </div> |
| | | </form> |
| | | <table lay-filter="activityTable" lay-data="{id: 'activityTable'}"></table> |
| | | <style type="text/css"> |
| | | .layui-table-cell{ |
| | | text-align:center; |
| | | height: auto; |
| | | white-space: nowrap; /*文本不会换行,在同一行显示*/ |
| | | overflow: hidden; /*超出隐藏*/ |
| | | text-overflow: ellipsis; /*省略号显示*/ |
| | | } |
| | | .layui-table img{ |
| | | max-width:100px |
| | | } |
| | | </style> |
| | | <!-- <style type="text/css">--> |
| | | <!-- ::-webkit-scrollbar {--> |
| | | <!-- height: 20px !important;--> |
| | | <!-- background-color: #f4f4f4;--> |
| | | <!-- }--> |
| | | <!-- </style>--> |
| | | </div> |
| | | </div> |
| | | </div> |
| | |
| | | table.on('tool(activityTable)', function (obj) { |
| | | var data = obj.data, |
| | | layEvent = obj.event; |
| | | if (layEvent === 'closeAct') { |
| | | febs.modal.confirm('关闭', '确认关闭该活动?', function () { |
| | | closeAct(data.id); |
| | | }); |
| | | } |
| | | if (layEvent === 'startAct') { |
| | | febs.modal.confirm('开启', '确认开启该活动?', function () { |
| | | startAct(data.id); |
| | | }); |
| | | } |
| | | if (layEvent === 'delAct') { |
| | | febs.modal.confirm('删除', '确认删除该活动?', function () { |
| | | delAct(data.id); |
| | |
| | | }); |
| | | } |
| | | }); |
| | | function closeAct(id) { |
| | | febs.get(ctx + 'admin/act/closeMallAct/' + id, null, function () { |
| | | febs.alert.success('关闭成功'); |
| | | $query.click(); |
| | | }); |
| | | } |
| | | function startAct(id) { |
| | | febs.get(ctx + 'admin/act/startMallAct/' + id, null, function () { |
| | | febs.alert.success('开启成功'); |
| | | function changeState(id,state) { |
| | | febs.get(ctx + 'admin/activity/changeState/' + id+'/' + state, null, function (data) { |
| | | febs.alert.success(data.message); |
| | | $query.click(); |
| | | }); |
| | | } |
| | | function delAct(id) { |
| | | febs.get(ctx + 'admin/act/delMallAct/' + id, null, function () { |
| | | febs.alert.success('删除成功'); |
| | | febs.get(ctx + 'admin/activity/delActivity/' + id, null, function (data) { |
| | | febs.alert.success(data.message); |
| | | $query.click(); |
| | | }); |
| | | } |
| | |
| | | |
| | | form.on('switch(activityStateSwitch)', function (data) { |
| | | if (data.elem.checked) { |
| | | startAct(data.value); |
| | | changeState(data.value,1); |
| | | } else { |
| | | closeAct(data.value); |
| | | changeState(data.value,0); |
| | | } |
| | | }) |
| | | |
| | |
| | | autocomplete="off" class="layui-input" readonly> |
| | | </div> |
| | | </div> |
| | | <!-- <div class="layui-form-item">--> |
| | | <!-- <label class="layui-form-label febs-form-item-require">联系方式:</label>--> |
| | | <!-- <div class="layui-input-block">--> |
| | | <!-- <input type="text" name="sort" minlength="4" maxlength="10" --> |
| | | <!-- lay-verify="range|sort" autocomplete="off" class="layui-input" >--> |
| | | <!-- </div>--> |
| | | <!-- </div>--> |
| | | <div class="layui-form-item"> |
| | | <label class="layui-form-label febs-form-item-require">是否置顶:</label> |
| | | <div class="layui-input-block"> |
| | |
| | | <input type="radio" name="isTop" value="2" title="否"> |
| | | </div> |
| | | </div> |
| | | <!-- <div class="layui-form-item">--> |
| | | <!-- <label class="layui-form-label febs-form-item-require">显示端口:</label>--> |
| | | <!-- <div class="layui-input-block">--> |
| | | <!-- <input type="radio" name="showPort" value="1" title="pc" checked="">--> |
| | | <!-- <input type="radio" name="showPort" value="2" title="手机">--> |
| | | <!-- </div>--> |
| | | <!-- </div>--> |
| | | <div class="layui-form-item"> |
| | | <label class="layui-form-label febs-form-item-require">是否可跳转:</label> |
| | | <div class="layui-input-block"> |
| | |
| | | <input type="radio" name="isJump" value="2" title="否"> |
| | | </div> |
| | | </div> |
| | | |
| | | <div class="layui-form-item"> |
| | | <label class="layui-form-label">跳转链接:</label> |
| | | <label class="layui-form-label">关联活动:</label> |
| | | <div class="layui-input-block"> |
| | | <input type="text" name="jumpUrl" |
| | | autocomplete="off" class="layui-input" > |
| | | <div id="jumpUrl-list"></div> |
| | | </div> |
| | | <div class="layui-form-mid layui-word-aux">图片需要跳转到商品详情,请填写商品编号。</div> |
| | | </div> |
| | | |
| | | <div class="layui-form-item"> |
| | | <label class="layui-form-label febs-form-item-require">跳转外部或内部:</label> |
| | | <div class="layui-input-block"> |
| | |
| | | </div> |
| | | |
| | | <script data-th-inline="javascript"> |
| | | layui.use(['febs', 'form', 'formSelects', 'validate', 'treeSelect', 'eleTree','upload'], function () { |
| | | layui.use(['febs', 'form', 'formSelects', 'validate', 'treeSelect', 'eleTree','upload', 'xmSelect'], function () { |
| | | var $ = layui.$, |
| | | febs = layui.febs, |
| | | layer = layui.layer, |
| | |
| | | |
| | | form.render(); |
| | | |
| | | var activityList = xmSelect.render({ |
| | | el: '#jumpUrl-list', |
| | | language: 'zh', |
| | | prop : { |
| | | value : 'id', |
| | | children : 'child' |
| | | }, |
| | | iconfont: { |
| | | parent: 'hidden', |
| | | }, |
| | | radio: true,//单选按钮。如何想要多选直接注释掉此行代码 |
| | | clickClose: true, |
| | | tree: { |
| | | show: true, |
| | | //非严格模式 |
| | | strict: false, |
| | | }, |
| | | data: [] |
| | | }) |
| | | |
| | | febs.get(ctx + 'admin/activity/activityList', null, function(res) { |
| | | activityList.update({ |
| | | data : res.data, |
| | | autoRow: true, |
| | | }); |
| | | }) |
| | | |
| | | formSelects.render(); |
| | | |
| | | form.on('submit(banner-add-form-submit)', function (data) { |
| | | data.field.jumpUrl = activityList.getValue('valueStr'); |
| | | febs.post(ctx + 'admin/banner/platformBannerAdds', data.field, function () { |
| | | layer.closeAll(); |
| | | febs.alert.success('新增成功'); |
| | |
| | | <!-- <script src="https://unpkg.com/@wangeditor/editor@latest/dist/index.js"></script>--> |
| | | <script src="http://gosspublic.alicdn.com/aliyun-oss-sdk-6.17.0.min.js"></script> |
| | | <script type="text/javascript" src="https://cdn.jsdelivr.net/npm/wangeditor@latest/dist/wangEditor.min.js" ></script> |
| | | |
| | | <style type="text/css"> |
| | | ::-webkit-scrollbar { |
| | | height: 20px !important; |
| | | background-color: #f4f4f4; |
| | | } |
| | | </style> |
| | | <link rel="icon" th:href="@{febs/images/favicon.ico}" type="image/x-icon"/> |
| | | </head> |
| | | <body> |