首页>国内 > 正文

促销活动管理,文件导入导出都有了!

2022-04-28 16:51:39来源:江南一点雨

在​​上篇文章​​中,我们搞定了渠道管理功能,这个相对来说比较简单。今天我们来看看促销活动的管理,在这个模块中,会有许多涉及到脚手架本身的修改,在这个过程中可以加深我们对这个脚手架的理解。

先来看看最终效果图吧:

这个页面上,你看到的所有功能按钮,均已实现。所以,就不废话了,开搞。

1. 数据库设计

数据库这里主要修改的地方有两处。

1.1 修改字典表

首先是修改字典表。在前端展示活动类型的时候,有两种不同的取值:

年卡折扣券年卡代金券

像下面这样:

这里的活动类型下拉框我们当然可以直接在前端硬编码,但是既然用了这个脚手架,且这个脚手架又刚好提供了数据字典的功能,那么我们不妨将这两个选项加入到数据字典中,方便我们后面使用。

可以直接利用脚手架中的数据字典网页来添加,也可以直接在数据库表中来添加,我就省事一点,直接改表吧,修改两张表,分别是 sys_dict_type 和 sys_dict_data 两张表,其中 sys_dict_type 中加的是字典类型,而 sys_dict_data 中加的则是字典的具体值,我添加的数据分别如下:

sys_dict_type:

sys_dict_data:

1.2 添加促销活动表

接下来就是活动促销表了,这个没啥好说的,直接开整就行了:

2. 创建新模块2.1 新建模块

接下来创建一个专门写活动管理的新模块,有了前面写 channel 的经验,现在写 activity 不过是手到擒来的事。

新建一个名为 tienchin-activity 的模块,然后加入 common 依赖,如下图:

    促销活动模块                org.javaboy        tienchin-common    

当然这个新建的 activity 模块也拿去给 admin 模块依赖一下,将来在 admin 模块中调用 activity 模块的 service。

2.2 自动生成代码

MP 相关的依赖我们在​​上篇文章​​中已经配过了,这里咱就直接开始用就行了。

我们在 admin 模块的单元测试中新加一个方法,来用生成基础操作代码,如下:

@Testpublic void activityGenerator() {    FastAutoGenerator.create("jdbc:mysql:///tienchin?serverTimezone=Asia/Shanghai&useSSL=false", "root", "123")            .globalConfig(builder -> {                builder.author("javaboy") // 设置作者                        .disableOpenDir()                        .fileOverride() // 覆盖已生成文件                        .outputDir("/Users/sang/workspace/workspace02/tienchin/tienchin-activity/src/main/java"); // 指定输出目录            })            .packageConfig(builder -> {                builder.parent("org.javaboy") // 设置父包名                        .moduleName("activity") // 设置父包模块名                        .pathInfo(Collections.singletonMap(OutputFile.xml, "/Users/sang/workspace/workspace02/tienchin/tienchin-activity/src/main/resources/mapper/channel")); // 设置mapperXml生成路径            })            .strategyConfig(builder -> {                builder.addInclude("tienchin_activity") // 设置需要生成的表名                        .addTablePrefix("tienchin_");            })            .templateEngine(new FreemarkerTemplateEngine()) // 使用Freemarker引擎模板,默认的是Velocity引擎模板            .execute();}

将自动生成的 controller 删除掉,我们将来重新写,最终生成的代码如下:

3. 服务端接口

接下来我们来看看服务端接口的开发。

我们在 admin 模块中,新建 ActivityController,来准备开发活动相关的接口。

3.1 常规 CRUD

首先是常规的 CRUD。

@RestController@RequestMapping("/tienchin/activity")public class ActivityController extends BaseController {    @Autowired    IActivityService activityService;    @PreAuthorize("@ss.hasPermi("tienchin:activity:add")")    @Log(title = "促销活动", businessType = BusinessType.INSERT)    @PostMapping("/")    public AjaxResult add(@Validated @RequestBody Activity activity) {        activity.setCreateBy(getUsername());        return toAjax(activityService.saveActivity(activity));    }    /**     * 状态修改     */    @PreAuthorize("@ss.hasPermi("tienchin:activity:edit")")    @Log(title = "促销活动" , businessType = BusinessType.UPDATE)    @PutMapping("/changeStatus")    public AjaxResult changeStatus(@RequestBody Activity activity) {        activity.setUpdateTime(LocalDateTime.now());        activity.setUpdateBy(getUsername());        return toAjax(activityService.updateById(activity));    }    @PreAuthorize("@ss.hasPermi("tienchin:activity:edit")")    @Log(title = "促销活动" , businessType = BusinessType.UPDATE)    @PutMapping("/")    public AjaxResult edit(@Validated @RequestBody Activity activity) {        activity.setUpdateBy(getUsername());        activity.setUpdateTime(LocalDateTime.now());        return toAjax(activityService.saveOrUpdate(activity));    }    @PreAuthorize("@ss.hasPermi("tienchin:activity:query")")    @GetMapping("/list")    public TableDataInfo getActivityList(Activity activity) {        startPage();        List list = activityService.getActivityList(activity);        return getDataTable(list);    }    @PreAuthorize("@ss.hasPermi("tienchin:activity:remove")")    @Log(title = "促销活动" , businessType = BusinessType.DELETE)    @DeleteMapping("/{activityIds}")    public AjaxResult remove(@PathVariable Long[] activityIds) {        //待完善,将来加了其他功能后再继续完善        return toAjax(activityService.removeBatchByIds(Arrays.asList(activityIds)));    }    @PreAuthorize("@ss.hasPermi("tienchin:activity:query")")    @GetMapping(value = "/{id}")    public AjaxResult getActivityById(@PathVariable Long id) {        return AjaxResult.success(activityService.getById(id));    }}

这些都是基础操作,其实也没啥好说的,大部分都用了 MP 自动生成的代码,自己几乎不需要写啥。

其中分页加条件查询的 /list 接口,是我自己写的,因为涉及到几个查询条件,该方法的定义如下:

public List getActivityList(Activity activity) {    QueryWrapper qw = new QueryWrapper<>();    if (activity.getChannel() != null) {        qw.lambda().eq(Activity::getChannel, activity.getChannel());    }    if (activity.getStatus() != null) {        qw.lambda().eq(Activity::getStatus, activity.getStatus());    }    if (activity.getEndTime() != null && activity.getBeginTime() != null) {        qw.lambda().ge(Activity::getBeginTime, activity.getBeginTime()).le(Activity::getEndTime, activity.getEndTime());    }    return list(qw);}

用了 MP 的查询方法。涉及到一点点 Lambda,不过都很好懂。

另外这里还有一个小小细节,就是小伙伴们知道,从 JDK1.8 开始,推荐用 LocalDate 和 LocalDateTime,所以我这个项目涉及到时间的基本上都是用这两种类型,但是在原本的脚手架中,当涉及到对象和 JSON 的互转是,只支持对 Date 的转换,所以这块需要我自己手动处理下。

看了下,脚手架中相关的配置都放在 framework 中,具体位置在 org.javaboy.tienchin.framework.config,那么我的配置类就也写在这个位置吧,如下:

@Configurationpublic class LocalDateTimeSerializerConfig {    private static final String DATE_TIME_PATTERN = "yyyy-MM-dd HH:mm:ss";    private static final String DATE_PATTERN = "yyyy-MM-dd";    /**     * string转localdate     */    @Bean    public Converter localDateConverter() {        return new Converter() {            @Override            public LocalDate convert(String source) {                if (source.trim().length() == 0) {                    return null;                }                try {                    return LocalDate.parse(source);                } catch (Exception e) {                    return LocalDate.parse(source, DateTimeFormatter.ofPattern(DATE_PATTERN));                }            }        };    }    /**     * string转localdatetime     */    @Bean    public Converter localDateTimeConverter() {        return new Converter() {            @Override            public LocalDateTime convert(String source) {                if (source.trim().length() == 0) {                    return null;                }                // 先尝试ISO格式: 2019-07-15T16:00:00                try {                    return LocalDateTime.parse(source);                } catch (Exception e) {                    return LocalDateTime.parse(source, DateTimeFormatter.ofPattern(DATE_TIME_PATTERN));                }            }        };    }    /**     * 统一配置     */    @Bean    public Jackson2ObjectMapperBuilderCustomizer jsonCustomizer() {        JavaTimeModule module = new JavaTimeModule();        LocalDateTimeDeserializer localDateTimeDeserializer = new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));        module.addDeserializer(LocalDateTime.class, localDateTimeDeserializer);        return builder -> {            builder.simpleDateFormat(DATE_TIME_PATTERN);            builder.serializers(new LocalDateSerializer(DateTimeFormatter.ofPattern(DATE_PATTERN)));            builder.serializers(new LocalDateTimeSerializer(DateTimeFormatter.ofPattern(DATE_TIME_PATTERN)));            builder.modules(module);        };    }}

配置类本身到也没啥说的,配了之后,将来项目中 LocalDate 转 JSON,就都是 yyyy-MM-dd 格式,LocalDateTime 转 JSON 就都是 yyyy-MM-dd HH:mm:ss 格式,反过来也一样。

3.2 导入导出

再来看看跟数据导入导出相关的几个接口。

首先 Excel 导入导出相关工具在脚手架中已经有了,我们直接用即可,需要做的准备工作,首先是在 Activity 实体类上加上相关注解,配置将来生成 Excel 时表格的 title,具体如下:

@TableName("tienchin_activity")public class Activity implements Serializable {    /**     * id     */    @TableId(value = "id", type = IdType.AUTO)    private Long id;    /**     * 活动编号     */    @Excel(name = "活动编号")    private String code;    /**     * 活动名称     */    @Excel(name = "活动名称")    private String name;    /**     * 渠道来源     */    @Excel(name = "渠道来源")    private String channel;    /**     * 活动简介     */    @Excel(name = "活动简介")    private String info;    /**     * 活动类型     */    @Excel(name = "活动类型",readConverterExp = "1=年费折扣卡,2=年费代金券")    private String type;    /**     * 年费折扣     */    @Excel(name = "年费折扣")    private Float discount;    /**     * 年费代金券     */    @Excel(name = "年费代金券")    private Double voucher;    /**     * 状态     */    @Excel(name = "活动状态",readConverterExp = "0=正常,1=停用")    private String status;    /**     * 开始时间     */    @Excel(name = "活动开始时间", width = 30, dateFormat = "yyyy-MM-dd HH:mm:ss")    private LocalDateTime beginTime;    /**     * 结束时间     */    @Excel(name = "活动结束时间", width = 30, dateFormat = "yyyy-MM-dd HH:mm:ss")    private LocalDateTime endTime;    private LocalDateTime createTime;    private LocalDateTime updateTime;    @Excel(name = "活动创建人")    private String updateBy;    @Excel(name = "活动修改人")    private String createBy;}

没加 @Excel 注解的字段,也是将来导出 Excel 表格时不需要导出的字段。

这里有一个小问题,就是我的时间格式使用了 LocalDateTime,原本的脚手架在这块只支持 Date,LocalDateTime 的转换会有问题,为了支持 LocalDateTime,我这里修改了 org.javaboy.tienchin.common.utils.reflect.ReflectUtils#invokeMethodByName 方法,增加了对 LocalDateTime 的枚举,如下:

public static  E invokeMethodByName(final Object obj, final String methodName, final Object[] args) {    Method method = getAccessibleMethodByName(obj, methodName, args.length);    if (method == null) {        // 如果为空不报错,直接返回空。        logger.debug("在 [" + obj.getClass() + "] 中,没有找到 [" + methodName + "] 方法 ");        return null;    }    try {        // 类型转换(将参数数据类型转换为目标方法参数类型)        Class[] cs = method.getParameterTypes();        for (int i = 0; i < cs.length; i++) {            if (args[i] != null && !args[i].getClass().equals(cs[i])) {                if (cs[i] == String.class) {                    args[i] = Convert.toStr(args[i]);                    if (StringUtils.endsWith((String) args[i], ".0")) {                        args[i] = StringUtils.substringBefore((String) args[i], ".0");                    }                } else if (cs[i] == Integer.class) {                    args[i] = Convert.toInt(args[i]);                } else if (cs[i] == Long.class) {                    args[i] = Convert.toLong(args[i]);                } else if (cs[i] == Double.class) {                    args[i] = Convert.toDouble(args[i]);                } else if (cs[i] == Float.class) {                    args[i] = Convert.toFloat(args[i]);                } else if (cs[i] == Date.class) {                    if (args[i] instanceof String) {                        args[i] = DateUtils.parseDate(args[i]);                    } else {                        args[i] = DateUtil.getJavaDate((Double) args[i]);                    }                } else if (cs[i] == boolean.class || cs[i] == Boolean.class) {                    args[i] = Convert.toBool(args[i]);                } else if (cs[i] == LocalDateTime.class) {                    if (args[i] instanceof String) {                        args[i] = DateUtils.getLocalDateTime((String) args[i]);                    } else {                        args[i] = DateUtils.getLocalDateTimeOfTimestamp((Long) args[i]);                    }                }else if (cs[i] == LocalDate.class) {                    if (args[i] instanceof String) {                        args[i] = DateUtils.getLocalDate((String) args[i]);                    } else {                        args[i] = DateUtils.getLocalDateOfTimestamp((Long) args[i]);                    }                }            }        }        return (E) method.invoke(obj, args);    } catch (Exception e) {        String msg = "method: " + method + ", obj: " + obj + ", args: " + args + "";        throw convertReflectionExceptionToUnchecked(msg, e);    }}

这里涉及到了四个工具方法如下:

/** * 时间戳转 LocalDateTime * * @param timestamp * @return */public static LocalDateTime getLocalDateTimeOfTimestamp(long timestamp) {    Instant instant = Instant.ofEpochMilli(timestamp);    ZoneId zone = ZoneId.systemDefault();    return LocalDateTime.ofInstant(instant, zone);}/** * 时间戳转 LocalDate * * @param timestamp * @return */public static LocalDate getLocalDateOfTimestamp(long timestamp) {    Instant instant = Instant.ofEpochMilli(timestamp);    ZoneId zone = ZoneId.systemDefault();    return instant.atZone(zone).toLocalDate();}/** * 字符串转 LocalDateTime * * @param datetime * @return */public static LocalDateTime getLocalDateTime(String datetime) {    return LocalDateTime.parse(datetime, DateTimeFormatter.ofPattern(YYYY_MM_DD_HH_MM_SS));}/** * 字符串转 LocalDate * * @param date * @return */public static LocalDate getLocalDate(String date) {    return LocalDate.parse(date, DateTimeFormatter.ofPattern(YYYY_MM_DD));}

好了,最后我们再提供三个导入导出相关的接口:

@PostMapping("/importTemplate")public void importTemplate(HttpServletResponse response) {    ExcelUtil util = new ExcelUtil(Activity.class);    util.importTemplateExcel(response, "活动数据");}@Log(title = "促销活动" , businessType = BusinessType.EXPORT)@PreAuthorize("@ss.hasPermi("tienchin:activity:export")")@PostMapping("/export")public void export(HttpServletResponse response, Activity activity) {    List list = activityService.getActivityList(activity);    ExcelUtil util = new ExcelUtil(Activity.class);    util.exportExcel(response, list, "促销活动数据");}@Log(title = "促销活动" , businessType = BusinessType.IMPORT)@PreAuthorize("@ss.hasPermi("tienchin:activity:import")")@PostMapping("/importData")public AjaxResult importData(MultipartFile file) throws Exception {    ExcelUtil util = new ExcelUtil(Activity.class);    List activityList = util.importExcel(file.getInputStream());    return toAjax(activityService.saveBatch(activityList));}

这三个基本上也是照着用户接口写的,照猫画虎。

4. 前端页面开发

接下来开发前端页面。

4.1 请求接口

首先我们来开发请求接口,还是老规矩,新建一个 src/api/activity/index.js 文件,内容如下:

// 查询所有的活动信息export function listActivity(query) {  return request({    url: "/tienchin/activity/list",    method: "get",    params: query  })}// 根据 id 查询某一个活动的信息export function getActivity(activityId) {  return request({    url: "/tienchin/activity/" + activityId,    method: "get"  })}// 添加活动export function addActivity(data) {  return request({    url: "/tienchin/activity/",    method: "post",    data: data  })}// 更新活动信息export function updateActivity(data) {  return request({    url: "/tienchin/activity/",    method: "put",    data: data  })}// 更新活动状态export function changeActivityStatus(id, status) {  const data = {    id,    status  }  return request({    url: "/tienchin/activity/changeStatus",    method: "put",    data: data  })}// 根据 id 删除活动export function delActivity(activityIds) {  return request({    url: "/tienchin/activity/" + activityIds,    method: "delete"  })}

这个基本上就是我们活动增删改查的所有信息了。对于文件导入导出是请求是单独封装的,一会直接在 .vue 文件中调用即可。

4.2 页面开发

具体的页面开发倒是不难,我们来看下最终的效果:

还有其他的我就不一一截图了。前端 vue 也不难,能做出 vhr 的小伙伴都能做出来这里的页面。没有特别直接说的地方,我也就不贴代码了。小伙伴们可以直接 GitHub 上下载源码查看。有不懂的地方欢迎留言讨论。

5. 小结

好啦,这次提交的功能是促销活动管理~小伙伴们赶紧去给个 star 呀,star 越多更的越快哈哈~

关键词: 活动管理 数据字典 结束时间 开始时间 活动状态

相关新闻

Copyright 2015-2020   三好网  版权所有