帝可得-商品管理

news2025/1/20 10:57:12

商品管理

需求说明

商品管理主要涉及到三个功能模块,业务流程如下:

  1. 新增商品类型: 定义商品的不同分类,如饮料、零食、日用品等。
  2. 新增商品: 添加新的商品信息,包括名称、规格、价格、类型等。
  3. 设备货道管理: 将商品与售货机的货道关联,管理每个货道的商品信息。
登录系统
新增商品类型
新增商品
货道关联商品

对于商品和其他管理数据,下面是示意图:

  • 关系字段: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" /> &nbsp;&nbsp;<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, "商品管理数据");
}

会出现easyexcelpoi版本不匹配的情况,在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));
}

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2255776.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

前端知识1html

VScode一些快捷键 Ctrl/——注释 !——生成html框架元素 *n——生成n个标签 直接书写html的名字回车生成对应的标签 常见标签 span&#xff1a; <span style"color: red;">hello</span> <span>demo</span> span实现&#xff1a; 标题…

【后端面试总结】tcp为什么要设置TIME_WAIT

设置TIME_WAIT的原因 相信大家对tcp的三次握手和四次挥手的过程已经非常熟悉了&#xff0c;但是对于四次挥手来说&#xff0c;有个问题一直困扰着我&#xff0c;那就是为什么在server端发送LAST_ACK之后&#xff0c;还要等待TIME_WAIT时间呢&#xff1f;原因有二&#xff1a; …

vue中使用socket.io统计在线用户

目录 一、引入相关模块 二、store/modules 中封装socketio 三、后端代码(nodejs) 一、引入相关模块 main.js 中参考以下代码 ,另外socketio的使用在查阅其它相关文章时有出入,还是尽量以官方文档为准 import VueSocketIO from vue-socket.io import SocketIO from socket.io-…

Redis的五种数据类型(Set、Zset)

目录 1. Set 集合1.1 Set介绍1.2 常见命令1.2.1 SADD命令1.2.2 SMEMBERS命令1.2.3 SISMEMBER命令1.2.4 SCARD命令1.2.5 SPOP命令1.2.6 SMOVE命令1.2.7 SREM命令 1.3 集合间操作1.3.1 SINTER命令1.3.2 SINTERSTORE命令1.3.3 SUNION命令1.3.4 SUNIONSTORE命令1.3.5 SDIFF命令1.3.…

【CSS in Depth 2 精译_067】11.2 颜色的定义(中):CSS 中的色域与色彩空间

当前内容所在位置&#xff08;可进入专栏查看其他译好的章节内容&#xff09; 第四部分 视觉增强技术 ✔️【第 11 章 颜色与对比】 ✔️ 11.1 通过对比进行交流 11.1.1 模式的建立11.1.2 还原设计稿 11.2 颜色的定义 11.2.1 色域与色彩空间 ✔️11.2.2 深入理解颜色表示法 文…

MVC基础——市场管理系统(一)

文章目录 项目地址一、创建项目结构1.1 创建程序以及Controller1.2 创建View1.3 创建Models层,并且在Edit页面显示1.4 创建Layou模板页面1.5 创建静态文件css中间件二、Categories的CRUD2.1 使用静态仓库存储数据2.2 将Categorie的列表显示在页面中(List)2.3 创建_ViewImport.…

[241206] X-CMD 发布 v0.4.15:env 升级,mirror 支持华为/腾讯 npm 镜像,pb-wayland 剪贴板

目录 X-CMD 发布 v0.4.15&#x1f4c3;Changelog&#x1f4e6; env|pkg&#x1fa9e; mirror&#x1f4d1; pb&#x1f3a8; theme|starship|ohmyposh&#x1f916; chat&#x1f4dd; man✅ 升级指南 X-CMD 发布 v0.4.15 &#x1f4c3;Changelog &#x1f4e6; env|pkg 新增…

# 深入浅出 快速认识JAVA常用数据结构【栈, 队列, 链表, 数组】

快速认识JAVA常用数据结构【栈, 队列, 链表】 前言 什么是数据结构 一种用来存储和组织数据的方法&#xff0c;描述了数据之间的关系和操作方式。通过合理选择和使用数据结构&#xff0c;可以大幅提高程序的运行效率、存储效率以及代码可维护性。 数据结构的重要性 数据结构…

fastadmin 后台插件制作方法

目录 一&#xff1a;开发流程 二&#xff1a;开发过程 &#xff08;一&#xff09;&#xff1a;后台功能开发 &#xff08;二&#xff09;&#xff1a;功能打包到插件目录 &#xff08;三&#xff09;&#xff1a;打包插件 &#xff08;四&#xff09;&#xff1a;安装插件…

使用Dapper创建一个简单的查询

1.先在NuGet上下载Dapper包 2.创建对应的model 代码如下&#xff1a; using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks;namespace 数据显示 {public class User{public int UserId { get; set; }public…

雨晨 2610(2)0.2510 Windows 11 24H2 Iot 企业版 LTSC 2024 极简 2in1

文件: 雨晨 2610(2)0.2510 Windows 11 24H2 Iot 企业版 LTSC 2024 极简 2in1 install.esd 索引: 1 名称: Windows 11 IoT 企业版 LTSC 极简 26100.2510 描述: Windows 11 IoT 企业版 LTSC 极简 26100.2510 By YCDISM RTM 2025 24-12-07 大小: 8,176,452,990 个字节 索引: 2 …

Kubernetes 深入浅出系列 | 容器编排与作业调度之Deployment

目录 概述Deployment 的更新原理实验 概述 Kubernetes 中&#xff0c;Deployment 控制器是用于管理应用程序生命周期的核心对象。Deployment 通过管理 ReplicaSet 来间接控制 Pod&#xff0c;确保在任何时刻都能维持指定数量的 Pod 副本。这种间接管理使得 Deployment 功能比 …

网络练级宝典-> UDP传输层协议

目录 传输层 端口号 端口号和进程的关系 UDP协议 UDP协议格式 UDP数据封装&#xff1a; UDP数据分用&#xff1a; 面向数据报 UDP的缓冲区 UDP的缺点 基于UDP的应用层协议 传输层 端口号 我们知道端口号对应的其实就是一个进程的pid&#xff0c;在操作系统中二者的…

Ubuntu22.04系统源码编译OpenCV 4.10.0(包含opencv_contrib)

因项目需要使用不同版本的OpenCV&#xff0c;而本地的Ubuntu22.04系统装了ROS2自带OpenCV 4.5.4的版本&#xff0c;于是编译一个OpenCV 4.10.0&#xff08;带opencv_contrib&#xff09;版本&#xff0c;给特定的项目使用&#xff0c;这就不用换个设备后重新安装OpenCV 了&…

获取联通光猫的管理员密码

缘起&#xff1a;联通给免费更换了一个新的光猫&#xff0c;烽火的光路由&#xff0c;一个WAN口&#xff0c;4个LAN口&#xff0c;带USB接口&#xff0c;欣欣然接受。但是呢&#xff0c;发现以前的管理员密码CUAdmin不能用了。经过一系列查询&#xff0c;借助别人的经验&#x…

残差网络连接,使得输入与输出的尺寸一样

def forward(self, x):out self.layer1(x)out self.layer2(out)# 使用插值将输入x上采样至与layer2输出相同的尺寸x F.interpolate(x, size(out.size(2), out.size(3)), modebilinear, align_cornersFalse)# 确保x的通道数与out匹配x x[:, :out.size(1), :, :] # 选择前ou…

计算机网络原理之HTTP与HTTPS

一、前言 为了理解HTTP&#xff0c;我们有必要事先了解一下TCP/IP协议簇。 通常我们使用的网络&#xff08;包括互联网&#xff09;是在TCP/IP协议簇的基础上运作的。而HTTP属于它内部的一个子集。 计算机与网络设备要相互通信&#xff0c;双方必须基于相同的方法。比如&#…

实验三:Mybatis-动态 SQL

目录&#xff1a; 一 、实验目的&#xff1a; 通过 mybatis 提供的各种标签方法实现动态拼接 sql 二 、预习要求&#xff1a; 预习 if、choose、 when、where 等标签的用法 三、实验内容&#xff1a; 根据性别和名字查询用户使用 if 标签改造 UserMapper.xml使用 where 标签进行…

NLP论文速读(斯坦福大学)|使用Tree将语法隐藏到Transformer语言模型中正则化

论文速读|Sneaking Syntax into Transformer Language Models with Tree Regularization 论文信息&#xff1a; 简介&#xff1a; 本文的背景是基于人类语言理解的组合性特征&#xff0c;即语言处理本质上是层次化的&#xff1a;语法规则将词级别的意义组合成更大的成分的意义&…

C++STL容器vector容器大小相关函数

目录 前言 主要参考 vector::size vector::max_size vector::resize vector::capacity vector::empty vector::reserve vector::shrink_to_fit 共勉 前言 本文将讨论STL容器vector中与迭代器相关的函数&#xff0c;模板参数T为int类型。 主要参考 cpluscplus.com 侯…