KKSU
2025-01-08 a0bf7c7be9e52bf179db59117fc3660c24604a96
feat(mall): 新增活动管理功能

- 添加活动列表、活动开启、活动关闭、活动删除等功能
- 实现活动商品的复制和价格计算
- 优化活动页面样式和交互
1 files added
10 files modified
292 ■■■■■ changed files
src/main/java/cc/mrbird/febs/mall/controller/AdminActivityController.java 28 ●●●●● patch | view | raw | blame | history
src/main/java/cc/mrbird/febs/mall/dto/MallGoodsQueryDto.java 7 ●●●●● patch | view | raw | blame | history
src/main/java/cc/mrbird/febs/mall/service/IActivityService.java 11 ●●●●● patch | view | raw | blame | history
src/main/java/cc/mrbird/febs/mall/service/impl/ActivityServiceImpl.java 95 ●●●● patch | view | raw | blame | history
src/main/java/cc/mrbird/febs/mall/service/impl/ApiMallGoodsCategoryServiceImpl.java 18 ●●●●● patch | view | raw | blame | history
src/main/java/cc/mrbird/febs/mall/vo/AdminMallActivityListVo.java 13 ●●●●● patch | view | raw | blame | history
src/main/resources/mapper/modules/MallGoodsMapper.xml 11 ●●●● patch | view | raw | blame | history
src/main/resources/templates/febs/views/modules/activity/add.html 2 ●●● patch | view | raw | blame | history
src/main/resources/templates/febs/views/modules/activity/list.html 48 ●●●● patch | view | raw | blame | history
src/main/resources/templates/febs/views/modules/banner/platformBannerAdd.html 52 ●●●●● patch | view | raw | blame | history
src/main/resources/templates/index.html 7 ●●●●● patch | view | raw | blame | history
src/main/java/cc/mrbird/febs/mall/controller/AdminActivityController.java
@@ -14,6 +14,7 @@
import org.springframework.web.bind.annotation.*;
import javax.validation.Valid;
import javax.validation.constraints.NotNull;
import java.util.Map;
@Slf4j
@@ -38,7 +39,7 @@
    @PostMapping("add")
    @ControllerEndpoint(operation = "新增", exceptionMessage = "新增失败")
    public FebsResponse addActivity(@RequestBody @Valid AdminActivityAddDto adminActivityAddDto) {
        return iActivityService.addActivity(adminActivityAddDto);
        return iActivityService.addAdminActivity(adminActivityAddDto);
    }
    /**
@@ -49,4 +50,29 @@
    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);
    }
}
src/main/java/cc/mrbird/febs/mall/dto/MallGoodsQueryDto.java
@@ -4,8 +4,6 @@
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import javax.validation.constraints.NotNull;
/**
 * @author wzy
 * @date 2021-09-17
@@ -35,9 +33,12 @@
    @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;
src/main/java/cc/mrbird/febs/mall/service/IActivityService.java
@@ -5,14 +5,23 @@
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);
}
src/main/java/cc/mrbird/febs/mall/service/impl/ActivityServiceImpl.java
@@ -1,5 +1,6 @@
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;
@@ -11,6 +12,7 @@
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;
@@ -25,7 +27,9 @@
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
@@ -38,6 +42,7 @@
    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());
@@ -55,7 +60,7 @@
    }
    @Override
    public FebsResponse addActivity(AdminActivityAddDto adminActivityAddDto) {
    public FebsResponse addAdminActivity(AdminActivityAddDto adminActivityAddDto) {
        MallActivity mallActivity = new MallActivity();
        mallActivity.setName(adminActivityAddDto.getName());
@@ -90,6 +95,7 @@
    }
    private void addDiscountGoods(Long activityId,BigDecimal discountSize, List<Long> goodsIds){
        MallActivity mallActivity = this.baseMapper.selectById(activityId);
        //将对应选中的商品按照这个折扣力度重新计算一遍
        List<MallGoods> mallGoods = mallGoodsMapper.selectList(
                new LambdaQueryWrapper<MallGoods>()
@@ -97,42 +103,49 @@
                        .in(MallGoods::getId, goodsIds)
        );
        if(CollUtil.isNotEmpty(mallGoods)){
            List<CompletableFuture<Void>> futures = new ArrayList<>();
            //复制对应的商品的规则
            for(MallGoods mallGoodsItem:mallGoods){
                Long goodsId = mallGoodsItem.getId();
                //新增活动商品
                Long goodsIdNew = addGoodsEntity(activityId, discountSize, mallGoodsItem);
                CompletableFuture<Void> uCompletableFuture = CompletableFuture.runAsync(() -> {
                    Long goodsId = mallGoodsItem.getId();
                    //新增活动商品
                    Long goodsIdNew = addGoodsEntity(mallActivity, discountSize, mallGoodsItem);
                List<MallGoodsStyle> mallGoodsStyles = mallGoodsStyleMapper.selectList(
                        new LambdaQueryWrapper<MallGoodsStyle>()
                        .eq(MallGoodsStyle::getGoodsId, goodsId)
                );
                    List<MallGoodsStyle> mallGoodsStyles = mallGoodsStyleMapper.selectList(
                            new LambdaQueryWrapper<MallGoodsStyle>()
                                    .eq(MallGoodsStyle::getGoodsId, goodsId)
                    );
                if(CollUtil.isNotEmpty(mallGoodsStyles)){
                    for(MallGoodsStyle mallGoodsStyleItem:mallGoodsStyles){
                        Long styleItemId = mallGoodsStyleItem.getId();
                    if(CollUtil.isNotEmpty(mallGoodsStyles)){
                        for(MallGoodsStyle mallGoodsStyleItem:mallGoodsStyles){
                            Long styleItemId = mallGoodsStyleItem.getId();
                        Long styleItemIdNew = addGoodsStyleEntity(goodsIdNew, mallGoodsStyleItem);
                        List<MallGoodsSku> mallGoodsSkus = mallGoodsSkuMapper.selectList(
                                new LambdaQueryWrapper<MallGoodsSku>()
                                .eq(MallGoodsSku::getStyleId, styleItemId)
                                .eq(MallGoodsSku::getGoodsId, goodsId)
                        );
                        if(CollUtil.isNotEmpty(mallGoodsSkus)){
                            for(MallGoodsSku mallGoodsSkuItem:mallGoodsSkus){
                                addGoodsSkuEntity(goodsIdNew,styleItemIdNew,discountSize,mallGoodsSkuItem);
                            Long styleItemIdNew = addGoodsStyleEntity(goodsIdNew, mallGoodsStyleItem);
                            List<MallGoodsSku> mallGoodsSkus = mallGoodsSkuMapper.selectList(
                                    new LambdaQueryWrapper<MallGoodsSku>()
                                            .eq(MallGoodsSku::getStyleId, styleItemId)
                                            .eq(MallGoodsSku::getGoodsId, goodsId)
                            );
                            if(CollUtil.isNotEmpty(mallGoodsSkus)){
                                for(MallGoodsSku mallGoodsSkuItem:mallGoodsSkus){
                                    addGoodsSkuEntity(goodsIdNew,styleItemIdNew,discountSize,mallGoodsSkuItem);
                                }
                            }
                        }
                    }
                }
                },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());
@@ -179,4 +192,38 @@
    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("操作成功");
    }
}
src/main/java/cc/mrbird/febs/mall/service/impl/ApiMallGoodsCategoryServiceImpl.java
@@ -1,9 +1,11 @@
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;
@@ -27,10 +29,24 @@
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
src/main/java/cc/mrbird/febs/mall/vo/AdminMallActivityListVo.java
New file
@@ -0,0 +1,13 @@
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;
}
src/main/resources/mapper/modules/MallGoodsMapper.xml
@@ -86,20 +86,12 @@
            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,
@@ -123,6 +115,9 @@
                    <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>
src/main/resources/templates/febs/views/modules/activity/add.html
@@ -213,7 +213,7 @@
            iconfont: {
                parent: 'hidden',
            },
            // radio: true,
            radio: true,
            clickClose: true,
            tree: {
                show: true,
src/main/resources/templates/febs/views/modules/activity/list.html
@@ -28,18 +28,12 @@
                        </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>
@@ -97,16 +91,6 @@
        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);
@@ -140,21 +124,15 @@
                });
            }
        });
        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();
            });
        }
@@ -225,9 +203,9 @@
        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);
            }
        })
src/main/resources/templates/febs/views/modules/banner/platformBannerAdd.html
@@ -50,13 +50,6 @@
                       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">
@@ -64,13 +57,6 @@
                <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">
@@ -78,14 +64,14 @@
                <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">
@@ -100,7 +86,7 @@
</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,
@@ -132,9 +118,37 @@
        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('新增成功');
src/main/resources/templates/index.html
@@ -25,6 +25,13 @@
<!--    <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>