商品管理
需求说明
商品管理主要涉及到三个功能模块,业务流程如下:
- 新增商品类型: 定义商品的不同分类,如饮料、零食、日用品等。
- 新增商品: 添加新的商品信息,包括名称、规格、价格、类型等。
- 设备货道管理: 将商品与售货机的货道关联,管理每个货道的商品信息。
对于商品和其他管理数据,下面是示意图:
- 关系字段:class_id、sku_id、vm_id
生成基础代码
需求
使用若依代码生成器,生成商品类型、商品管理前后端基础代码,并导入到项目中:
步骤
创建目录菜单
创建商品管理目录菜单
配置代码生成信息
导入二张表
配置商品类型表(参考原型)
配置商品表(参考原型)
下载代码并导入项目
选中商品表和商品类型表生成下载
解压ruoyi.zip
得到前后端代码和动态菜单sql
代码导入
商品类型改造
基础页面
需求
参考页面原型,完成基础布局展示改造
列表显示改造
在skuClass/index.vue视图组件中修改
<el-table v-loading="loading" :data="skuClassList" @selection-change="handleSelectionChange">
<!-- <el-table-column type="selection" width="55" align="center" /> -->
<el-table-column label="序号" align="center" prop="classId" />
<el-table-column label="商品类型" align="center" prop="className" />
<!-- 创建日期 -->
<el-table-column label="创建日期" align="center" prop="createTime" width="180" />
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
<template #default="scope">
<el-button link type="primary" @click="handleUpdate(scope.row)" v-hasPermi="['manage:skuClass:edit']">修改</el-button>
<el-button link type="primary" @click="handleDelete(scope.row)" v-hasPermi="['manage:skuClass:remove']">删除</el-button>
</template>
</el-table-column>
</el-table>
原来的商品类型表中没有create_time和update_time字段需要手动添加,并在xml中返回查询创建时间。
SkuClassMapper.xml
<sql id="selectSkuClassVo">
select class_id, class_name, parent_id,create_time, update_time from tb_sku_class
</sql>
<select id="selectSkuClassList" parameterType="SkuClass" resultMap="SkuClassResult">
<include refid="selectSkuClassVo"/>
<where>
<if test="className != null and className != ''"> and class_name like concat('%', #{className}, '%')</if>
</where>
</select>
对话框改造
商品管理中的商品与商品类型存在外键约束,不能删除有关联的商品类型,所以要设置一个全局异常处理器,拦截这些异常并返回前端能理解的提示。同时当添加已存在的数据时,返回的是违反数据完整性约束
,需要指定该异常信息为无法保存名称已存在
。
修改全局异常处理器,添加以下内容
/**
* 数据完整性异常
*/
@ExceptionHandler(DataIntegrityViolationException.class)
public AjaxResult handelDataIntegrityViolationException(DataIntegrityViolationException e) {
if (e.getMessage().contains("foreign")) {
return AjaxResult.error("无法删除,有其他数据引用");
}
if(e.getMessage().contains("Duplicate")){
return AjaxResult.error("无法保存,名称已存在");
}
return AjaxResult.error("数据完整性异常,请联系管理员");
}
商品管理改造
基础页面
需求
参考页面原型,完成基础布局展示改造
搜索区域改造
代码实现
<el-form :model="queryParams" ref="queryRef" :inline="true" v-show="showSearch" label-width="68px">
<el-form-item label="商品搜索" prop="skuName">
<el-input
v-model="queryParams.skuName"
placeholder="请输入"
clearable
@keyup.enter="handleQuery"
/>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="handleQuery">查询</el-button>
<!-- <el-button icon="Refresh" @click="resetQuery">重置</el-button> -->
</el-form-item>
</el-form>
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button
type="primary"
@click="handleAdd"
v-hasPermi="['manage:sku:add']"
>新增</el-button>
</el-col>
<!-- 后续修改实现方法 -->
<el-col :span="1.5">
<el-button
type="primary"
@click="handleExport"
v-hasPermi="['manage:sku:export']"
>导入</el-button>
</el-col>
<right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
</el-row>
列表显示改造
在sku/index.vue视图组件中修改
代码实现
商品类型展示的应该是商品的类型名,而不是商品类型id。
<el-table v-loading="loading" :data="skuList" @selection-change="handleSelectionChange">
<el-table-column label="商品编号" align="center" prop="skuId" />
<el-table-column label="商品名称" align="center" prop="skuName" />
<el-table-column label="商品图片" align="center" prop="skuImage" width="100">
<template #default="scope">
<image-preview :src="scope.row.skuImage" :width="50" :height="50"/>
</template>
</el-table-column>
<el-table-column label="品牌" align="center" prop="brandName" />
<el-table-column label="规格" align="center" prop="unit" />
<el-table-column label="商品价格" align="center" prop="price" />
</el-table-column>
<el-table-column label="商品类型" align="center" prop="classId" >
<template #default="scope">
<div v-for="item in skuClassList" :key="item.classId">
<span v-if="item.classId == scope.row.classId">{{ item.className }}</span>
</div>
</template>
</el-table-column>
<el-table-column label="创建日期" align="center" prop="createTime" width="180">
<template #default="scope">
<span>{{ parseTime(scope.row.createTime, '{y}-{m}-{d} {h}:{i}:{s}') }}</span>
</template>
</el-table-column>
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
<template #default="scope">
<el-button link type="primary" @click="handleUpdate(scope.row)" v-hasPermi="['manage:sku:edit']">修改</el-button>
<el-button link type="primary" @click="handleDelete(scope.row)" v-hasPermi="['manage:sku:remove']">删除</el-button>
</template>
</el-table-column>
</el-table>
import { listSkuClass } from "@/api/manage/skuClass";
import { loadAllParams } from "@/api/page";
// 查询商品类型列表
const skuClassList = ref([]);
function getSkuClassList() {
listSkuClass(loadAllParams).then(response => {
skuClassList.value = response.rows;
});
}
getSkuClassList();
将前端分改为元,为了突出这一列的可读性,可以通过增加tag标签对这一列进行高亮。
<el-table-column label="商品价格" align="center" prop="price">
<template #default="scope">
<el-tag >{{ scope.row.price/100 }} 元</el-tag>
</template>
</el-table-column>
添加或修改对话框改造
页面原型
代码实现
商品价格需要改为数字输入框,并且通过precision属性保留两位小数,并且通过step属性将增加减小的步径金额从1元改为0.5元。
<!-- 添加或修改商品管理对话框 -->
<el-dialog :title="title" v-model="open" width="500px" append-to-body>
<el-form ref="skuRef" :model="form" :rules="rules" label-width="80px">
<el-form-item label="商品名称" prop="skuName">
<el-input v-model="form.skuName" placeholder="请输入" />
</el-form-item>
<el-form-item label="品牌" prop="brandName">
<el-input v-model="form.brandName" placeholder="请输入" />
</el-form-item>
<el-form-item label="商品价格" prop="price">
<el-input-number :min="0.01" :max="999.99" :precision="2" :step="0.5" v-model="form.price" placeholder="请输入" style="width: 140px" /> <span>{{ '元' }}</span>
</el-form-item>
<el-form-item label="商品类型" prop="classId">
<el-select v-model="form.classId" placeholder="请选择" style="width: 140px">
<el-option v-for="item in skuClassList" :key="item.classId" :label="item.className" :value="item.classId" />
</el-select>
</el-form-item>
<el-form-item label="规格" prop="unit">
<el-input v-model="form.unit" placeholder="请输入规格" style="width: 140px" />
</el-form-item>
<el-form-item label="商品图片" prop="skuImage">
<image-upload v-model="form.skuImage"/>
</el-form-item>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button type="primary" @click="submitForm">确 定</el-button>
<el-button @click="cancel">取 消</el-button>
</div>
</template>
</el-dialog>
但是在修改对话框中回显的数据是分,原因是数据库中存储的数据是整形,也就是分。所以在回显之前将单位的分转换为元回显,同时提交修改的时候需要将元转换为分。
/** 修改按钮操作 */
function handleUpdate(row) {
reset();
const _skuId = row.skuId || ids.value
getSku(_skuId).then(response => {
form.value = response.data;
// 回显价格时,需要将分转为元。
form.value.price /= 100;
open.value = true;
title.value = "修改商品管理";
});
}
但是在新增是商品后,价格除了100。原因就是在提交的时候提交的单位是元
,在数据库存储的应该是分
,所以在新增修改在提交数据前需要将元转换为分。
代码:
/** 提交按钮 */
function submitForm() {
proxy.$refs["skuRef"].validate(valid => {
// 将价格单位从元转换为分
form.value.price *= 100;
if (valid) {
if (form.value.skuId != null) {
updateSku(form.value).then(response => {
proxy.$modal.msgSuccess("修改成功");
open.value = false;
getList();
});
} else {
addSku(form.value).then(response => {
proxy.$modal.msgSuccess("新增成功");
open.value = false;
getList();
});
}
}
});
}
再次添加数据:
商品删除
需求
在删除商品时,需要判断此商品是否被售货机的货道关联,如果关联则无法删除
-
物理外键约束:通过在子表中添加一个外键列和约束,该列与父表的主键列相关联,由数据库维护数据的一致性和完整性
-
逻辑外键约束:在不使用数据库外键约束的情况下,通常在应用程序中通过代码来检查和维护数据的一致性和完整性
使用逻辑外键约束的原因:我们在新增售货机货道记录时暂不指定商品,货道表中的SKU_ID
有默认值0,而这个值在商品表中并不存在,那么物理外键约束会阻止货道表的插入,因为0并不指向任何有效的商品记录
代码实现
SkuServiceImpl
@Autowired
private IChannelService channelService;
/**
* 批量删除商品管理
*
* @param skuIds 需要删除的商品管理主键
* @return 结果
*/
@Override
public int deleteSkuBySkuIds(Long[] skuIds)
{ //1. 判断商品的id集合是否有关联货道
int count = channelService.countChannelBySkuIds(skuIds);
if(count>0){
throw new ServiceException("此商品被货道关联,无法删除");
}
//2. 没有关联货道才能删除
return skuMapper.deleteSkuBySkuIds(skuIds);
}
IChannelService
/**
* 根据商品id集合统计货道数量
* @param skuIds
* @return 统计结果
*/
int countChannelBySkuIds(Long[] skuIds);
ChannelServiceImpl
/**
* 根据商品id集合统计货道数量
* @param skuIds
* @return 统计结果
*/
@Override
public int countChannelBySkuIds(Long[] skuIds) {
return channelMapper.countChannelBySkuIds(skuIds);
}
ChannelMapper接口和xml
/**
* 根据商品id集合统计货道数量
* @param skuIds
* @return 统计结果
*/
int countChannelBySkuIds(Long[] skuIds);
<select id="countChannelBySkuIds" resultType="java.lang.Integer">
select count(1) from tb_channel where sku_id in
<foreach item="id" collection="array" open="(" separator="," close=")">
#{id}
</foreach>
</select>
当删除被货道关联的商品时
导出功能详解
在若依中已经默认将商品的导出功能实现了,主要是基于后端实现的。
Excel工具类底层是若依封装的,底层是基于apache的poi,原生apache的poi代码写起来非常繁琐,若依就将poi的基础功能封装和抽取,实现了文件的上传和下载功能。
主要包括文件的导入解析和文件的导出操作。
代码生成器默认生成好了文件的导出功能
批量导入
接口文档分析
两个请求投,Content-Type的作用告诉后端是一个表单文件提交,Authorization是告诉后端前端的身份令牌信息,只有登录以后前端具有了令牌才能上传,非则就会被拦截器拦截。返回结果就是标准的ajax result,有提示信息和业务状态码。
前端
需求
点击导入数据弹出导入数据弹窗,实现商品的批量导入
代码实现
打开导入按钮的对话框:
<el-col :span="1.5">
<el-button
type="warning"
icon="Upload"
@click="handleImport"
v-hasPermi="['manage:sku:add']"
>导入</el-button>
</el-col>
<!-- 新增导入对话框 -->
<el-dialog title="数据导入" v-model="openImport" width="500px" append-to-body>
</el-dialog>
// 打开导入按钮
const openImport = ref(false);
function handleImport() {
openImport.value = true;
}
完善上传对话框:
使用element-plus官网的上传按钮的方法
<!-- 新增导入对话框 -->
<el-dialog
title="数据导入"
v-model="openImport"
width="500px"
append-to-body
>
<!-- 上传文件组件 -->
<el-upload
ref="uploadRef"
class="upload-demo"
action="https://run.mocky.io/v3/9d059bf9-4660-45f2-925d-ce80ad6c4d15"
:auto-upload="false"
>
<template #trigger>
<el-button type="primary">上传文件</el-button>
</template>
<el-button class="ml-3" type="success" @click="submitUpload">
上传
</el-button>
<template #tip>
<div class="el-upload__tip">
上传文件仅支持,xls/xlsx格式,文件大小不得超过1M
</div>
</template>
</el-upload>
</el-dialog>
// 上传excel文件
const uploadRef = ref({});
function submitUpload() {
uploadRef.value.submit();
}
请求地址是element官网提供的,需要将请求地址改为帝可得商品的批量导入路径,可以参考图片上传的组件。
/* 上传地址 */
const uploadExcelUrl = ref(import.meta.env.VITE_APP_BASE_API + "/manage/sku/import"); // 上传excel文件地址
/* 上传请求头 */
const headers = ref({ Authorization: "Bearer " + getToken() });
上传失败提供失败信息,上传成功以后关闭对话框,并提示上传成功的信息。
// 上传成功回调
function handleUploadSuccess(res, file) {
if (res.code === 200) {
proxy.$modal.msgSuccess(`上传excel文件成功`);
// 关闭导入对话框
openImport.value = false;
// 刷新列表
getList();
} else {
proxy.$modal.msgError(res.msg);
}
// 清理上传文件
uploadRef.value.clearFiles();
}
// 上传失败回调
function handleUploadError(err, file) {
proxy.$modal.msgError("上传excel文件失败");
// 清理上传文件
uploadRef.value.clearFiles();
}
限制文件上传的大小,在上传前loading加载中添加相应的方法。
// 上传前的校验
const props = defineProps({
modelValue: [String, Object, Array],
// 大小限制(MB)
fileSize: {
type: Number,
default: 1,
},
// 文件类型, 例如['xls', 'xlsx']
fileType: {
type: Array,
default: () => ["xls", "xlsx"],
},
});
// 上传前loading加载
function handleBeforeUpload(file) {
let isExcel = false;
if (props.fileType.length) {
let fileExtension = "";
if (file.name.lastIndexOf(".") > -1) {
fileExtension = file.name.slice(file.name.lastIndexOf(".") + 1);
}
isExcel = props.fileType.some(type => {
if (file.type.indexOf(type) > -1) return true;
if (fileExtension && fileExtension.indexOf(type) > -1) return true;
return false;
});
}
if (!isExcel) {
proxy.$modal.msgError(
`文件格式不正确, 请上传${props.fileType.join("/")}格式文件!`
);
return false;
}
if (props.fileSize) {
const isLt = file.size / 1024 / 1024 < props.fileSize;
if (!isLt) {
proxy.$modal.msgError(`上传excel文件大小不能超过 ${props.fileSize} MB!`);
return false;
}
}
proxy.$modal.loading("正在上传excel文件,请稍候...");
}
上传成功或者失败之后关闭loading加载状态。
// 上传成功回调
function handleUploadSuccess(res, file) {
if (res.code === 200) {
proxy.$modal.msgSuccess(`上传excel文件成功`);
// 关闭导入对话框
openImport.value = false;
// 刷新列表
getList();
} else {
proxy.$modal.msgError(res.msg);
}
// 清理上传文件
uploadRef.value.clearFiles();
// 关闭加载状态
proxy.$modal.closeLoading();
}
// 上传失败回调
function handleUploadError(err, file) {
proxy.$modal.msgError("上传excel文件失败");
// 清理上传文件
uploadRef.value.clearFiles();
// 关闭加载状态
proxy.$modal.closeLoading();
}
上传前的loading方法已经取消了多个文件上传,所以需要添加支持单个文件上传的属性limit。
<!-- 上传文件组件 -->
<el-upload
ref="uploadRef"
class="upload-demo"
:action="uploadExcelUrl"
:headers="headers"
:auto-upload="false"
:before-upload="handleBeforeUpload"
:on-success="handleUploadSuccess"
:on-error="handleUploadError"
:limit="1"
>
<template #trigger>
<el-button type="primary">上传文件</el-button>
</template>
<el-button class="ml-3" type="success" @click="submitUpload">
上传
</el-button>
<template #tip>
<div class="el-upload__tip">
上传文件仅支持,xls/xlsx格式,文件大小不得超过1M
</div>
</template>
</el-upload>
后端
需求
请求路径和接收参数
代码实现
SkuController:
/**
* 导出商品管理列表
*/
@PreAuthorize("@ss.hasPermi('manage:sku:export')")
@Log(title = "商品管理", businessType = BusinessType.EXPORT)
@PostMapping("/export")
// 接收前端的请求条件sku
public void export(HttpServletResponse response, Sku sku)
{
// 1. 调用service实现商品列表的查询
List<Sku> list = skuService.selectSkuList(sku);
// 2. 将查询的结果list集合,交给一个excel工具类,实现文件的下载和导出功能。在创建excel工具类的同时需要指定商品类型的字节码对象,因为它底层是一道反射来解析实体属性获取数据。
ExcelUtil<Sku> util = new ExcelUtil<Sku>(Sku.class);
// 3. 创建好工具类之后,通过exportExcel方法,实现excel文件的生成和下载功能。
util.exportExcel(response, list, "商品管理数据");
}
如何将商品名称封装到对应的skuName属性中,图片封装到image中?
实体类中每个属性的上方都有一个Excel注解,这个注解的作用就是将当前的列标题与属性建立关系,导出数据每列的标题也是由Excel注解提供的。
ISkuService:
/**
* 批量新增商品管理
* @param skuList
* @return 结果
*/
public int insertSkus(List<Sku> skuList);
/**
* 批量新增商品管理
* @param skuList
* @return 结果
*/
@Override
public int insertSkus(List<Sku> skuList) {
return skuMapper.insertSkus(skuList);
}
SKuMapper:
/**
* 批量新增商品管理
* @param skuList
* @return 结果
*/
public int insertSkus(List<Sku> skuList);
根据通义灵码将单个商品添加xml改造为批量添加:
假如你是一名软件开发工程师,请把下面的xml改为批量新增,不需要动态sql
<insert id="insertSkus" parameterType="java.util.List">
insert into tb_sku (
sku_name,
sku_image,
brand_Name,
unit,
price,
class_id,
is_discount,
create_time,
update_time
)
values
<foreach collection="list" item="sku" separator=",">
(
#{sku.skuName},
#{sku.skuImage},
#{sku.brandName},
#{sku.unit},
#{sku.price},
#{sku.classId},
#{sku.isDiscount},
#{sku.createTime},
#{sku.updateTime}
)
</foreach>
</insert>
发现添加的数据没有创建日期,因为传入的excel文件并没有创建时间和更新时间的数据,在插入的时候会添加null。创建时间和更新时间在插入数据的时候会自动添加当前系统时间。是否打折不应该由商品决定,而是由商品所关联的售货机的策略模式判断的。在xml中去掉这些字段
<insert id="insertSkus" parameterType="java.util.List" useGeneratedKeys="true" keyProperty="skuId">
insert into tb_sku (sku_name, sku_image, brand_Name, unit, price, class_id)
values
<foreach item="item" index="index" collection="list" separator=",">
(#{item.skuName}, #{item.skuImage}, #{item.brandName}, #{item.unit}, #{item.price}, #{item.classId})
</foreach>
</insert>
添加成功:
EasyExcel
介绍
官方地址:https://easyexcel.alibaba.com/
Java解析、生成Excel比较有名的框架有Apache poi、jxl。但他们都存在一个严重的问题就是非常的耗内存,poi有一套SAX模式的API可以一定程度的解决一些内存溢出的问题,但POI还是有一些缺陷,比如07版Excel解压缩以及解压后存储都是在内存中完成的,内存消耗依然很大。
easyexcel重写了poi对07版Excel的解析,一个3M的excel用POI sax解析依然需要100M左右内存,改用easyexcel可以降低到几M,并且再大的excel也不会出现内存溢出;03版依赖POI的sax模式,在上层做了模型转换的封装,让使用者更加简单方便
项目集成
文档地址
1、dkd-common\pom.xml
模块添加整合依赖
<!-- excel处理工具-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>easyexcel</artifactId>
<version>4.0.1</version>
</dependency>
2、在dkd-common\
模块的ExcelUtil.java
新增easyexcel
导出导入方法
/**
* 对excel表单默认第一个索引名转换成list(EasyExcel)
*
* @param is 输入流
* @return 转换后集合
*/
public List<T> importEasyExcel(InputStream is) throws Exception
{
return EasyExcel.read(is).head(clazz).sheet().doReadSync();
}
/**
* 对list数据源将其里面的数据导入到excel表单(EasyExcel)
*
* @param list 导出数据集合
* @param sheetName 工作表的名称
* @return 结果
*/
public void exportEasyExcel(HttpServletResponse response, List<T> list, String sheetName)
{
try
{
EasyExcel.write(response.getOutputStream(), clazz).sheet(sheetName).doWrite(list);
}
catch (IOException e)
{
log.error("导出EasyExcel异常{}", e.getMessage());
}
}
3、Sku.java修改为@ExcelProperty
注解
package com.dkd.manage.domain;
import com.alibaba.excel.annotation.ExcelIgnoreUnannotated;
import com.alibaba.excel.annotation.ExcelProperty;
import com.alibaba.excel.annotation.write.style.ColumnWidth;
import com.alibaba.excel.annotation.write.style.HeadFontStyle;
import com.alibaba.excel.annotation.write.style.HeadRowHeight;
import com.dkd.common.annotation.Excel;
import com.dkd.common.core.domain.BaseEntity;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;
/**
* 商品管理对象 tb_sku
*
* @author itheima
* @date 2024-07-15
*/
@ExcelIgnoreUnannotated// 注解表示在导出Excel时,忽略没有被任何注解标记的字段
@ColumnWidth(16)// 注解用于设置列的宽度
@HeadRowHeight(14)// 注解用于设置表头行的高度
@HeadFontStyle(fontHeightInPoints = 11)// 注解用于设置表头的字体样式
public class Sku extends BaseEntity
{
private static final long serialVersionUID = 1L;
/** 主键 */
private Long skuId;
/** 商品名称 */
@Excel(name = "商品名称")
@ExcelProperty("商品名称")
private String skuName;
/** 商品图片 */
@Excel(name = "商品图片")
@ExcelProperty("商品图片")
private String skuImage;
/** 品牌 */
@Excel(name = "品牌")
@ExcelProperty("品牌")
private String brandName;
/** 规格(净含量) */
@Excel(name = "规格(净含量)")
@ExcelProperty("规格(净含量)")
private String unit;
/** 商品价格 */
@Excel(name = "商品价格")
@ExcelProperty("商品价格")
private Long price;
/** 商品类型Id */
@Excel(name = "商品类型Id")
@ExcelProperty("商品类型Id")
private Long classId;
/** 是否打折促销 */
private Integer isDiscount;
// 其他略...
}
4、SkuController.java中将importExcel
改为importEasyExcel
/**
* 导入商品管理列表
*/
@PreAuthorize("@ss.hasPermi('manage:sku:add')")
@Log(title = "商品管理", businessType = BusinessType.IMPORT)
@PostMapping("/import")
public AjaxResult excelImport(MultipartFile file) throws Exception {
ExcelUtil<Sku> util = new ExcelUtil<Sku>(Sku.class);
List<Sku> skuList = util.importEasyExcel(file.getInputStream());
return toAjax(skuService.insertSkus(skuList));
}
5、SkuController.java中将exportExcel
改为exportEasyExcel
/**
* 导出商品管理列表
*/
@PreAuthorize("@ss.hasPermi('manage:sku:export')")
@Log(title = "商品管理", businessType = BusinessType.EXPORT)
@PostMapping("/export")
public void export(HttpServletResponse response, Sku sku) {
List<Sku> list = skuService.selectSkuList(sku);
ExcelUtil<Sku> util = new ExcelUtil<Sku>(Sku.class);
util.exportEasyExcel(response, list, "商品管理数据");
}
会出现easyexcel
与poi
版本不匹配的情况,在parent得xml中修改poi的版本号为5.2.3
即可。
货道关联商品
需求
在设备管理的操作中点击货道,会弹出一个货道设置的对话框。点击添加按钮会弹出商品的对话框,可以选择关联的商品。点击智能排货,会弹出智能排货的对话框。
对智能售货机内部的货道进行商品摆放的管理
此功能涉及四个后端接口
- 查询设备类型(已完成)
- 查询货道列表(待完成)
- 查询商品列表(已完成)
- 货道关联商品(待完成)
货道对话框
从资料中复制货道api请求js文件到api/manage目录下
从资料中复制货道的视图组件components到views/manage/vm目录下
修改index.vue视图,并引入css样式
<el-button link type="primary" @click="handleGoods(scope.row)" v-hasPermi="['manage:vm:edit']">货道</el-button>
<!-- 货道组件对话框 -->
<ChannelDialog :goodVisible="goodVisible" :goodData="goodData" @handleCloseGood="handleCloseGood"></ChannelDialog>
<!-- end -->
<script setup name="Vm">
// ********************货道********************
// 货道组件
import ChannelDialog from './components/ChannelDialog.vue';
const goodVisible = ref(false); //货道弹层显示隐藏
const goodData = ref({}); //货道信息用来拿取 vmTypeId和innerCode
// 打开货道弹层
const handleGoods = (row) => {
goodVisible.value = true;
goodData.value = row;
};
// 关闭货道弹层
const handleCloseGood = () => {
goodVisible.value = false;
};
// ********************货道end********************
</script>
<style lang="scss" scoped src="./index.scss"></style>
查询货道列表
前端向后端发送的请求是list/售货机编号,返回的信息不仅有货道的信息还有货道对应的商品信息。
需要先根据售货机编号inner_code
去货道表
中查询货道信息,再根据货道信息中的sku_id
去商品表
中嵌套查询
商品信息。将货道信息和商品信息封装到channelVo
中返回。
ChannelVo
@Data
public class ChannelVo extends Channel {
// 商品信息
private Sku sku;
}
ChannelMapper和xml
/**
* 根据售货机编号查询货道列表
*
* @param innerCode
* @return ChannelVo集合
*/
List<ChannelVo> selectChannelVoListByInnerCode(String innerCode);
<resultMap type="ChannelVo" id="ChannelVoResult">
<result property="id" column="id" />
<result property="channelCode" column="channel_code" />
<result property="skuId" column="sku_id" />
<result property="vmId" column="vm_id" />
<result property="innerCode" column="inner_code" />
<result property="maxCapacity" column="max_capacity" />
<result property="currentCapacity" column="current_capacity" />
<result property="lastSupplyTime" column="last_supply_time" />
<result property="createTime" column="create_time" />
<result property="updateTime" column="update_time" />
<association property="sku" javaType="Sku" column="sku_id" select="com.dkd.manage.mapper.SkuMapper.selectSkuBySkuId"/>
</resultMap>
<select id="selectChannelVoListByInnerCode" resultMap="ChannelVoResult">
<include refid="selectChannelVo"/>
where inner_code = #{innerCode}
</select>
IChannelService接口和实现
/**
* 根据售货机编号查询货道列表
*
* @param innerCode
* @return ChannelVo集合
*/
List<ChannelVo> selectChannelVoListByInnerCode(String innerCode);
/**
* 根据售货机编号查询货道列表
*
* @param innerCode
* @return ChannelVo集合
*/
@Override
public List<ChannelVo> selectChannelVoListByInnerCode(String innerCode) {
return channelMapper.selectChannelVoListByInnerCode(innerCode);
}
ChannelController
/**
* 根据售货机编号查询货道列表
*/
@PreAuthorize("@ss.hasPermi('manage:channel:list')")
@GetMapping("/list/{innerCode}")
public AjaxResult lisetByInnerCode(@PathVariable("innerCode") String innerCode) {
List<ChannelVo> voList = channelService.selectChannelVoListByInnerCode(innerCode);
return success(voList);
}
货道关联商品
完成货道的配置,实现货道关联商品,并通过点击按钮向后端发送请求更新数据库。根据channel_code和inner_code可以确定货道的唯一标识,然后可以更新货道中的sku_id。主要关注的是前端的请求参数,接口返回的数据就是一个标准的AjaxResult
DTO对象不能直接更新数据库货道表的,需要将DTO转换为与数据库对应的PO对象,也就是持久化对象,实体的属性名与货道的属性名做到一一对应。在转换的过程中需要对前端的数据进行校验,确保当前货道是存在的,所以需要根据售货机的编号和货道的编号来查询当前货道的信息。如果查到了就给当前的货道实体类设置skuId,这是其中一个货道的设置,需要遍历查询转换设置sku_id。
ChannelSkuDto
设计一个数据传输对象专门接收innerCode和channelCode,因为目前数据库对应的实体类没有这样的属性与前端的参数所对应,channelList用于接收每个货道的信息,也需要设计一个数据传输对象。
// 某个货道对应的sku信息
@Data
public class ChannelSkuDto {
private String innerCode; // 售货机编号
private String channelCode; // 货道编号
private Long skuId; // 商品ID
}
ChannelConfigDto
// 售货机货道配置,接收前端传输的整体数据
@Data
public class ChannelConfigDto {
// 售货机编号
private String innerCode;
// 货道配置
private List<ChannelSkuDto> channelList;
}
根据innerCode和channelCode查询对应的货道信息
ChannelMapper和xml
/**
* 根据售货机编号和货道编号查询货道信息
* @param innerCode
* @param channelCode
* @return 售货机货道
*/
@Select("select * from tb_channel where inner_code =#{innerCode} and channel_code=#{channelCode}")
Channel getChannelInfo(@Param("innerCode") String innerCode, @Param("channelCode") String channelCode);
/**
* 批量修改货道
* @param list
* @return 结果
*/
int batchUpdateChannel(List<Channel> list);
<update id="batchUpdateChannel" parameterType="java.util.List">
<foreach item="channel" collection="list" separator=";">
UPDATE tb_channel
<set>
<if test="channel.channelCode != null and channel.channelCode != ''">channel_code = #{channel.channelCode},</if>
<if test="channel.skuId != null">sku_id = #{channel.skuId},</if>
<if test="channel.vmId != null">vm_id = #{channel.vmId},</if>
<if test="channel.innerCode != null and channel.innerCode != ''">inner_code = #{channel.innerCode},</if>
<if test="channel.maxCapacity != null">max_capacity = #{channel.maxCapacity},</if>
<if test="channel.currentCapacity != null">current_capacity = #{channel.currentCapacity},</if>
<if test="channel.lastSupplyTime != null">last_supply_time = #{channel.lastSupplyTime},</if>
<if test="channel.createTime != null">create_time = #{channel.createTime},</if>
<if test="channel.updateTime != null">update_time = #{channel.updateTime},</if>
</set>
WHERE id = #{channel.id}
</foreach>
</update>
mybatis在连接数据库的时候,默认情况下一个请求只能执行一条sql语句。之前的批量新增是insert into values()();
而生成的动态更新是update xxx;update xxx;
这就包含多个更新语句了。
application-druid.yml
允许mybatis框架在单个请求中发送多个sql语句
IChannelService接口和实现
/**
* 货道关联商品
* @param channelConfigDto
* @return 结果
*/
int setChannel(ChannelConfigDto channelConfigDto);
/**
* 货道关联商品
* @param channelConfigDto
* @return 结果
*/
@Override
public int setChannel(ChannelConfigDto channelConfigDto) {
//1. dto转po
List<Channel> list = channelConfigDto.getChannelList().stream().map(c -> {
// 根据售货机编号和货道编号查询货道
Channel channel = channelMapper.getChannelInfo(c.getInnerCode(), c.getChannelCode());
if (channel != null) {
// 货道更新skuId
channel.setSkuId(c.getSkuId());
// 货道更新时间
channel.setUpdateTime(DateUtils.getNowDate());
}
return channel;
}).collect(Collectors.toList());
//2. 批量修改货道
return channelMapper.batchUpdateChannel(list);
}
ChannelController
/**
* 货道关联商品
*/
@PreAuthorize("@ss.hasPermi('manage:channel:edit')")
@Log(title = "售货机货道", businessType = BusinessType.UPDATE)
@PutMapping("/config")
public AjaxResult setChannel(@RequestBody ChannelConfigDto channelConfigDto) {
return toAjax(channelService.setChannel(channelConfigDto));
}