1、项目介绍

在做完了若依的基础篇以后,终于迎来了应用篇,加油吧。

https://codesign.qq.com/s/426304924036117

简单来讲就是一个售货机的后端管理。

240805

240808

240812 传送门

240813 传送门

240814 传送门

240903 传送门

2、后端准备工作

2.1、fork原始库

打开原始库:https://gitee.com/yudian1991/dkd-parent.git

点击fork,把代码复制到自己的库。

点击克隆/下载按钮,就可以看到代码url。

2.2、拉取代码

打开idea,新建一个项目,选择从vcs获取,输入url,选择好路径,开始拉取代码。

注意关注maven配置是否正常。

2.3、初始化数据库配置连接

找到sql文件夹下面的sql语句,先创建一个教dkd的数据库,然后执行脚本。

所有表创建完毕。

来到项目,找到dkd-admin模块下的配置文件application-druid.yml

配置数据库信息。

2.4、配置redis

还是在dkd-admin模块下,找到application.yml,配置redis信息。

2.5、后端,启动!

找到dkd-admin,找到启动类,启动!

3、前端准备工作

3.1、fork代码

源代码地址:https://gitee.com/yudian1991/dkd-vue.git

和后端一样,fork到自己的仓库。

3.2、拉取代码

打开vscode,输入url,发现没gitee啊,那只能先配置环境了。

来到应用商场,下载gitee。其实不下载也行,傻逼了。

直接点存储库就行,选择好本地的目录,等他拉代码。

3.3、安装依赖

点击右上角调出终端。

输入命令安装依赖

npm install

至于npm环境啥的没安装的去看下基础篇。

3.3.1、npm安装卡顿

完了,安装不起来。

感觉像是镜像问题,配置个淘宝的镜像试试。

npm config set registry https://registry.npm.taobao.org

还是很卡,难道是我的网络有问题?等等吧。

再换个镜像试试。

npm install --registr=https://registry.npmmirror.com

终于完事了。

3.4、前端,启动!

还是熟悉的命令,启动!

npm run dev

启动完成会自动打开浏览器。

4、点位管理

该模块涉及到3个业务,分别是区域管理,合作商管理,点位管理

3个模块的关联如下:

4.1、生成sql,创建业务表

课程里面是让ai生成sql,我就不试了,直接拿来用。

完事了。

4.2、配置数据字典

这里涉及到数据字典的有点位表tb_node里面的bussiness_type字段,用来存储区域类型。

先配置字典

在配置字典项

4.3、创建菜单

需要创建的效果如下

一级目录:点位管理

二级目录:区域管理

二级目录:点位管理

二级目录:合作商管理

打开目录管理,配置一级目录,如下配置:

4.4、配置代码生成信息

代码生成页面,把3张表导入

4.4.1、区域模块配置

先配置区域表,老三样

4.4.2、合作商管理

按照需求配置就行

4.4.3、点位管理

4.5、代码和数据库导入

选中3个表,点击生成

4.5.1、数据库导入

先处理sql,把3个sql语句执行一下。

这个sql的作用是把目录写到目录表。

先把目录调整一下。排序和图标。

4.5.2、前端代码导入

4.5.3、后端代码导入

直接打开资源目录导入最快。

至此,基础代码功能都完成了。

4.6、基础代码改造

4.6.1、区域管理改造

看下查询页面的目标:

这是现在的样子:

需要修改好多:

1、取消复选框。

2、只保留新增按钮。

3、主键改为序号。

4、跨表查询点位数量。

5、增加查看详情按钮。

修改:

4.6.1.1、取消复选框

打开前端页面,直接把复选框注释掉。

4.6.1.2、只保留新增按钮

先保留,有用呢。

4.6.1.3、主键改为序号

直接改名字就行,增加排序就加个属性:type="index"

4.6.1.4、跨表查询点位数量

这里要改的就多了。

首先分析一下表结构,这里的点位数量是指区域下点位的数量,点位是在点位管理下面添加的,点位里有一个字段叫region_id,关联区域表,我们需要做的就是做一个Vo类,把点位数量这个字段写sql查询出来,再返回给前端。

4.6.1.4.1、写sql查询

目标是关联搜索,把每个区域下的点位数量也查询出来。

## 查询tb_region表的所有数据,还需要查询关联tb_node的数量
select tr.id,tr.region_name,tr.remark, count(tn.id) as node_count from tb_region tr left join tb_node tn on tr.id = tn.region_id group by tr.id;

查询结果如下:

4.6.1.4.2、创建vo类

这个vo类就是为了把新的node_count字段加进去,可以返回给前端,同时vo又有原来region的全部字段。

在manage自模块下面加上vo目录,新建RegionVo类,继承Region,加上nodeCount字段。

在加上@Date注解,生成getset方法。

package com.dkd.manage.domain.vo;

import com.dkd.manage.domain.Region;
import lombok.Data;

@Data
public class RegionVo extends Region {
    //点位数量
    private Integer nodeCount;
}
4.6.1.4.3、新增mapper方法

找到RegionMapper.java,加上一个方法,返回RegionVo集合

/***
     * 查询区域管理列表及对应点位数量
     * @param region
     * @return
     */
    public List<RegionVo> selectRegionVoList(Region region);

再打开RegionMapper.xml,添加对应的查询语句。

注意:

1、因为要加查询条件,所以sql语句后面不要加分号。

2、搜索条件添加复制过来修改一下。

3、默认驼峰转换是关的,node_count没法解析到nodeCount,打开dkd-admin/src/main/resources/mybatis/mybatis-config.xml,把配置项mapUnderscoreToCamelCase改为true即可。

<select id="selectRegionVoList" resultType="com.dkd.manage.domain.vo.RegionVo">
      select tr.id,tr.region_name,tr.remark, count(tn.id) as node_count from tb_region tr left join tb_node tn on tr.id = tn.region_id group by tr.id
      <where>
          <if test="regionName != null  and regionName != ''"> and tr.region_name like concat('%', #{regionName}, '%')</if>
      </where>
  </select>
4.6.1.4.4、新增service方法

接着修改RegionService.java,把查询添加进去。

/***
     * 查询区域管理列表及对应点位数量
     * @param region
     * @return regiovo集合
     */
    public List<RegionVo> selectRegionVoList(Region region);

impl里面加上实现,调用mapper的方法。

/***
     * 查询区域管理列表及对应点位数量
     * @param region
     * @return regiovo集合
     */
    @Override
    public List<RegionVo> selectRegionVoList(Region region) {
        return regionMapper.selectRegionVoList(region);
    }
4.6.1.4.5、新增controller方法

改造一下原来的查询方法就行。

原来的方法:
/**
     * 查询区域管理列表
     */
    @PreAuthorize("@ss.hasPermi('manage:region:list')")
    @GetMapping("/list")
    public TableDataInfo list(Region region)
    {
        startPage();
        List<Region> list = regionService.selectRegionList(region);
        return getDataTable(list);
    }
改造后:
@PreAuthorize("@ss.hasPermi('manage:region:list')")
    @GetMapping("/list")
    public TableDataInfo list(Region region)
    {
        startPage();
        //List<Region> list = regionService.selectRegionList(region);
        List<RegionVo> regionVos = regionService.selectRegionVoList(region);
        return getDataTable(regionVos);
    }

去前端调用一下,看到返回的集合里面已经有点位数量了。

{
    "total": 4,
    "rows": [
        {
            "createBy": null,
            "createTime": null,
            "updateBy": null,
            "updateTime": null,
            "remark": "北京市海淀区",
            "id": 2,
            "regionName": "北京市海淀区",
            "nodeCount": 1
        },
        {
            "createBy": null,
            "createTime": null,
            "updateBy": null,
            "updateTime": null,
            "remark": "常州市武进区是常州最大的区县",
            "id": 4,
            "regionName": "常州市武进区",
            "nodeCount": 2
        },
        {
            "createBy": null,
            "createTime": null,
            "updateBy": null,
            "updateTime": null,
            "remark": "新北区是我住的地方",
            "id": 5,
            "regionName": "常州市新北区",
            "nodeCount": 0
        },
        {
            "createBy": null,
            "createTime": null,
            "updateBy": null,
            "updateTime": null,
            "remark": "钟楼区是我爸妈住的地方",
            "id": 6,
            "regionName": "常州市钟楼区",
            "nodeCount": 0
        }
    ],
    "code": 200,
    "msg": "查询成功"
}
4.6.1.4.6、修改前端

在表单里面加上一列就行,名称就是后端传的nodeCount

<el-table v-loading="loading" :data="regionList" @selection-change="handleSelectionChange">
      <!-- <el-table-column type="selection" width="55" align="center" /> -->
      <el-table-column label="序号" type="index" align="center" prop="id"  width="55" />
      <el-table-column label="区域名称" align="center" prop="regionName" />
      <el-table-column label="点位数量" align="center" prop="nodeCount" />
      <el-table-column label="备注说明" align="center" prop="remark" />
      <el-table-column label="操作" align="center" class-name="small-padding fixed-width">
        <template #default="scope">
          <el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)" v-hasPermi="['manage:region:edit']">修改</el-button>
          <el-button link type="primary" icon="Delete" @click="handleDelete(scope.row)" v-hasPermi="['manage:region:remove']">删除</el-button>
        </template>
      </el-table-column>
    </el-table>

在看页面已经可以显示了。

4.6.1.4.7、查询bug修改

在做查询的时候,出现了问题,有报错

看着像sql有问题,把mapper里面的语句交给ai看看有啥问题。

这段代码看起来像是 MyBatis 的动态 SQL 片段,用于根据条件查询 tb_region 表中的记录,并统计每个区域下的节点数量。这里有几个问题需要注意:

缺少闭合标签:

<where> 标签没有闭合标签 </where>。

SQL 语句与 XML 格式混合:

SQL 语句直接与 MyBatis 的 <if> 标签混合在一起,需要确保语法正确。

SQL 注入风险:

使用 #{regionName} 直接拼接在 SQL 语句中可能会导致 SQL 注入风险。虽然 MyBatis 通过参数化方式减少了这种风险,但在实际应用中仍需注意。

逻辑判断:

<if> 标签内的条件判断逻辑使用了空格分隔,这在 Java 表达式中是不规范的,虽然大多数情况下可以正常工作,但最好避免。

修正后的代码如下:

<select>
    select tr.id, tr.region_name, tr.remark, count(tn.id) as node_count 
    from tb_region tr 
    left join tb_node tn on tr.id = tn.region_id 
    <where>
        <if test="regionName != null and regionName != ''">
            tr.region_name like concat('%', #{regionName}, '%')
        </if>
    </where>
    group by tr.id
</select>

添加了 <select> 和 </select> 标签来完整包裹 SQL 语句。

添加了 </where> 来闭合 <where> 标签。

移除了 and 前的 and 关键字,因为这是在 <where> 标签内部的第一个条件。

确保 <if> 标签内的条件判断逻辑没有多余的空格。

4.6.1.5、增加查看详情按钮

看下要展示的内容:

要去展示区域下的点位,和点位下设备的数量。

这一步要等下面4.6.3做完了才行,后端接口才够用。

这里主要就是前端的事情了,要根据区域id查询区域信息,和区域下的点位和点位对应的设备信息。

    <!-- 查看详情对话框 -->
    <el-dialog title="查看详情" v-model="detailOpen" width="500px" append-to-body>
      <el-form-item label="区域名称" prop="regionName">
          <el-input v-model="form.regionName" placeholder="请输入区域名称" disabled/>
      </el-form-item>
      <label>下属点位:</label>
      <el-table :data="nodeList">
        <el-table-column label="序号" width="50px" align="center" type="index" prop="id"/>
        <el-table-column label="点位名称" align="center" prop="nodeName" />
        <el-table-column label="设备数量" align="center" prop="vmCount" />
      </el-table>
    </el-dialog>


import { listNode } from "@/api/manage/node";


// 定义分页查询参数
const pageInfo = reactive({ pageNum: 1, pageSize: 10000, pageInfo: null });
const nodeList = ref([]);
const detailOpen = ref(false);
// 查看详情按钮操作
function handleDetail(row) {
  reset();
  const _id = row.id
  // 查询区域信息
  getRegion(_id).then(response => {
    form.value = response.data;
  });

  //查询点位信息
  pageInfo.regionId=row.id
  listNode(pageInfo).then(response => {
    nodeList.value = response.rows;
  });

  detailOpen.value = true;
}

4.6.2、合作商管理修改

目标状态:

现在状态:

差别如下:

1、主键改为序号。

2、顺序修改。

3、分成比例加上符号。

4、新建和修改页面不同。

5、加上查看详情按钮。

6、加上重置密码按钮。

7、增加点位数量显示。

开始修改

4.6.2.1、主键改为序号

直接改前端,加上type属性。

<el-table-column label="序号" align="center" type="index" width="80" /> 

4.6.2.2、顺序改一下

      <el-table-column type="selection" width="55" align="center" />
      <el-table-column label="序号" align="center" type="index" width="80" />
      <el-table-column label="合作商名称" align="center" prop="partnerName" />
      <el-table-column label="账号" align="center" prop="account" />
      <el-table-column label="分成比例" align="center" prop="profitRatio" />
      <el-table-column label="联系人" align="center" prop="contactPerson" />
      <el-table-column label="联系电话" align="center" prop="contactPhone" />
      <!-- <el-table-column label="备注" align="center" prop="remark" /> -->

4.6.2.3、分成比例加上符号

原始:
<el-table-column label="分成比例" align="center" prop="profitRatio" /> 
修改后:
      <el-table-column label="分成比例" align="center" >
        <template #default="scope">
          {{ scope.row.profitRatio }}%
        </template>
      </el-table-column>

4.6.2.4、修改页面展示的和新建页面不一致

目标:

为了不写两遍,用v-if标签,没有主键就是新建,有主键就是修改。

### 原始页面    
<el-dialog :title="title" v-model="open" width="500px" append-to-body>
      <el-form ref="partnerRef" :model="form" :rules="rules" label-width="100px">
        <el-form-item label="合作商名称" prop="partnerName">
          <el-input v-model="form.partnerName" placeholder="请输入合作商名称" />
        </el-form-item>
        <el-form-item label="联系人" prop="contactPerson">
          <el-input v-model="form.contactPerson" placeholder="请输入联系人" />
        </el-form-item>
        <el-form-item label="联系电话" prop="contactPhone">
          <el-input v-model="form.contactPhone" placeholder="请输入联系电话" />
        </el-form-item>
        <el-form-item label="分成比例" prop="profitRatio">
          <el-input v-model="form.profitRatio" placeholder="请输入分成比例" />
        </el-form-item>
        <el-form-item label="账号" prop="account">
          <el-input v-model="form.account" placeholder="请输入账号" />
        </el-form-item>
        <el-form-item label="密码" prop="password" >
          <el-input v-model="form.password" placeholder="请输入密码" type="password"/>
        </el-form-item>
        <el-form-item label="备注" prop="remark">
          <el-input v-model="form.remark" type="textarea" placeholder="请输入内容" />
        </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> 
### 修改后
    <el-dialog :title="title" v-model="open" width="500px" append-to-body>
      <el-form ref="partnerRef" :model="form" :rules="rules" label-width="100px">
        <el-form-item label="合作商名称" prop="partnerName">
          <el-input v-model="form.partnerName" placeholder="请输入合作商名称" />
        </el-form-item>
        <el-form-item label="联系人" prop="contactPerson">
          <el-input v-model="form.contactPerson" placeholder="请输入联系人" />
        </el-form-item>
        <el-form-item label="联系电话" prop="contactPhone">
          <el-input v-model="form.contactPhone" placeholder="请输入联系电话" />
        </el-form-item>
        <el-form-item label="创建时间" prop="createTime" v-if="form.id != null">
          <el-input v-model="form.createTime" placeholder="请输入创建时间" disabled />
        </el-form-item>
        <el-form-item label="分成比例" prop="profitRatio">
          <el-input v-model="form.profitRatio" placeholder="请输入分成比例" />
        </el-form-item>
        <el-form-item label="账号" prop="account" v-if="form.id == null">
          <el-input v-model="form.account" placeholder="请输入账号" />
        </el-form-item>
        <el-form-item label="密码" prop="password"  v-if="form.id == null">
          <el-input v-model="form.password" placeholder="请输入密码" type="password"/>
        </el-form-item>
        <el-form-item label="备注" prop="remark">
          <el-input v-model="form.remark" type="textarea" placeholder="请输入内容" />
        </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>

这个密码还需要用springsecurity加密一下。

找到PartnerServiceImpl.java,把insert方法改一下。

    /**
     * 新增合作商管理
     * 
     * @param partner 合作商管理
     * @return 结果
     */
    @Override
    public int insertPartner(Partner partner)
    {
        partner.setCreateTime(DateUtils.getNowDate());
        partner.setPassword(SecurityUtils.encryptPassword(partner.getPassword()));
        return partnerMapper.insertPartner(partner);
    }

4.6.2.5、增加查看详情按钮

后端代码不用动,都是前端改,先增加一个按钮。

<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
        <template #default="scope">
          <el-button link type="primary" icon="file" @click="handleDetail(scope.row)" v-hasPermi="['manage:partner:query']">查看详情</el-button>
          <el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)" v-hasPermi="['manage:partner:edit']">修改</el-button>
          <el-button link type="primary" icon="Delete" @click="handleDelete(scope.row)" v-hasPermi="['manage:partner:remove']">删除</el-button>
        </template>
      </el-table-column>

在添加一个展示框,并让他保持默认不显示状态。

    <!-- 显示合作商详情的对话框 -->
     <el-dialog :title="title" v-model="detaildio" width="500px" append-to-body>
      <el-row :gutter="10" class="mb8">
        <el-col :span="12">合作商名称: {{ form.partnerName }}</el-col>
        <el-col :span="12">联系人: {{ form.contactPerson }}</el-col>
      </el-row>
      <el-row :gutter="10" class="mb8">
        <el-col :span="12">联系电话: {{ form.contactPhone }}</el-col>
        <el-col :span="12">分成比例: {{ form.profitRatio }}%</el-col>
      </el-row>
    </el-dialog> 

写方法,点击按钮显示展示框,并把数据写上去。

const detaildio = ref(false);

/** 查看详情按钮 */
function handleDetail(row) {
  reset();
  const _id = row.id
  getPartner(_id).then(response => {
    form.value = response.data;
    detaildio.value = true;
    title.value = "查看合作商信息";
  });
} 

再用ai美化一下页面,不然太丑了。

<template>
  <el-dialog :title="title" v-model="detailDialogVisible" width="500px" center append-to-body>
    <el-card shadow="always" class="dialog-card">
      <div class="info-container">
        <div class="info-item">
          <label>合作商名称:</label>
          <span>{{ form.partnerName }}</span>
        </div>
        <div class="info-item">
          <label>联系人:</label>
          <span>{{ form.contactPerson }}</span>
        </div>
        <div class="info-item">
          <label>联系电话:</label>
          <span>{{ form.contactPhone }}</span>
        </div>
        <div class="info-item">
          <label>分成比例:</label>
          <span>{{ form.profitRatio }}%</span>
        </div>
      </div>
    </el-card>
  </el-dialog>
</template>

<style scoped>
.dialog-card {
  width: 100%;
  padding: 20px;
}

.info-container {
  display: flex;
  flex-direction: column;
  gap: 10px;
}

.info-item {
  display: flex;
  justify-content: space-between;
  align-items: center;
}

.label {
  font-weight: bold;
  color: #606266;
}
</style>

4.6.2.6、新增重置密码按钮

按钮图标直接修改icon属性,具体形状去elementplus官网上去找。

<el-button link type="primary" icon="Memo" @click="handleDetail(scope.row)" v-hasPermi="['manage:partner:query']">查看详情</el-button>
<el-button link type="primary" icon="RefreshLeft" @click="handleDetail(scope.row)" v-hasPermi="['manage:partner:query']">重置密码</el-button>

先看下具体实现方法:

前端请求url如下:

Path: manage/partner/resetPwd/id
Method: put

打开PartnerController.java,新增一个方法。

/***
     * 重置合作商密码
     */
    @PreAuthorize("@ss.hasPermi('manage:partner:edit')")
    @Log(title = "重置合作商密码", businessType = BusinessType.UPDATE)
    @PutMapping("/resetPwd/{id}")
    public AjaxResult resetPwd(@PathVariable Long id){
        //创建partner对象
        Partner partner = new Partner();
        partner.setId(id);
        partner.setPassword(SecurityUtils.encryptPassword("123456"));
        return toAjax(partnerService.updatePartner(partner));
    }

接着就是搞前端。

打开partner.js,复制一个请求方法。

//重置合作商密码
export function resetPartnerPwd(id) {
  return request({
    url: '/manage/partner/resetPwd/' + id,
    method: 'put'
  })
}

回到partner.vue,先把上面的方法引入,按钮上添加方法,编写方法。

<script setup name="Partner">
import { listPartner, getPartner, delPartner, addPartner, updatePartner,resetPartnerPwd } from "@/api/manage/partner";

// 重置密码
function handleResetPwd(row) {
  const _id = row.id ;
  const _partnerName = row.partnerName ;
  proxy.$modal.confirm('是否确认重置"' + _partnerName + '"的密码?').then(function() {
    return resetPartnerPwd(_id);
  }).then(() => {
    //getList();
    proxy.$modal.msgSuccess("重置成功");
  }).catch(() => {});
}
</script> 

<el-button link type="primary" icon="RefreshLeft" @click="handleResetPwd(scope.row)" v-hasPermi="['manage:partner:query']">重置密码</el-button> 

4.6.2.7、显示每个合作商的点位数量

还是老套路,在合作商的实体类上加一个字段,先把sql写好。

4.6.2.7.1、写sql查询
## 查询合作商表,并展示合作商的点位数量
select
    t1.*,
    ifnull(t2.count, 0) as node_count
from
    tb_partner t1
    left join (
        select
            partner_id,
            count(1) count
        from
            tb_node
        group by
            partner_id
    ) t2 on t1.id = t2.partner_id
4.6.2.7.2、创建合作商的vo类
package com.dkd.manage.domain.vo;

import com.dkd.manage.domain.Partner;
import lombok.Data;

@Data
public class PartnerVo extends Partner {
    //点位数量
    private Integer nodeCount;
}
4.6.2.7.3、新增mapper方法

先在mapper.java里面加上方法

/**
 * 查询合作商管理列表
 *
 * @param partner 合作商管理
 * @return 合作商管理集合
 */
public List<PartnerVo> selectPartnerListVo(Partner partner);

在xml里面写sql

<select id="selectPartnerListVo" resultType="com.dkd.manage.domain.vo.PartnerVo"
        parameterType="com.dkd.manage.domain.Partner">
select
    t1.*,
    ifnull(t2.count, 0) as node_count
from
    tb_partner t1
    left join (
        select
            partner_id,
            count(1) count
        from
            tb_node
        group by
            partner_id
    ) t2 on t1.id = t2.partner_id
    <where>
        <if test="partnerName != null  and partnerName != ''"> and p.partner_name like concat('%', #{partnerName}, '%')</if>
    </where>
</select>
4.6.2.7.4、新增service方法

先写接口IPartnerService.java

/**
 * 查询合作商管理列表
 * @param partner
 * @return vo集合
 */
public List<PartnerVo> selectPartnerListVo(Partner partner);

再写实现PartnerServiceImpl.java

/**
 * 根据合作伙伴对象查询合作伙伴列表
 * <p>
 * 此方法用于查询与给定合作伙伴相关联的所有合作伙伴列表它通过调用映射器(Mapper)方法实现
 * </p>
 *
 * @param partner 用于查询的合作伙伴对象,包含查询条件
 * @return 返回一个包含所有符合条件的合作伙伴的列表
 */
@Override
public List<PartnerVo> selectPartnerListVo(Partner partner) {
    return partnerMapper.selectPartnerListVo(partner);
}
4.6.2.7.5、新增controller方法

把list方法改一下

/**
 * 查询合作商管理列表
 */
@PreAuthorize("@ss.hasPermi('manage:partner:list')")
@GetMapping("/list")
public TableDataInfo list(Partner partner)
{
    startPage();
    List<PartnerVo> list = partnerService.selectPartnerListVo(partner);
    //List<Partner> list = partnerService.selectPartnerList(partner);
    return getDataTable(list);
}
4.6.2.7.6、前端新增列
      <el-table-column label="点位数量" align="center" >
        <template #default="scope">
          {{ scope.row.nodeCount }}
        </template>
      </el-table-column>

查看效果:

4.6.3、点位管理修改

先看看原型

现在是这样的:

需要改造的:

1、搜索栏区域改成下拉框。

2、序号修改和顺序修改。

3、新增框修改。

4、列表查询展示区域和工作商。

4.6.3.1、搜索栏区域改成下拉框

分析:有现成的接口用,直接用region的分页查询接口,分页数传1000就行了。这要改前端。

找到src\views\manage\node\index.vue,先把region的js导入。定义查询方法。把标签改成下拉框。

<script setup name="Node"> 
##导入region的js方法
import { listRegion } from "@/api/manage/region";

// 定义分页查询参数
const pageInfo = reactive({ pageNum: 1, pageSize: 10000 });
//定义分页查询结果
const regionList = ref([]);
// 查询区域列表
function getRegionList() {
  listRegion(pageInfo).then(response => {
    regionList.value = response.rows;
  });
}
</script>

       <el-form-item label="区域名称" prop="regionId" >
        <el-select v-model="queryParams.regionId" placeholder="请选择区域名称" clearable>
          <el-option
            v-for="item in regionList"
            :key="item.id"
            :label="item.regionName"
            :value="item.id"
          ></el-option>
        </el-select>
     </el-form-item>

其中有一个要注意的是定义分页查询条件和返回结果的时候一个是reactive一个是ref,表示一个是json一个是数组。要是pageInfo也定义成ref的话传过去的参数就会变成下面的样子:

4.6.3.2、序号修改和顺序修改

把第一列改成序号,再把其他的顺序改一下。

##原始
    <el-table v-loading="loading" :data="nodeList" @selection-change="handleSelectionChange">
      <el-table-column type="selection" width="55" align="center" />
      <el-table-column label="主键id" align="center" prop="id" />
      <el-table-column label="点位名称" align="center" prop="nodeName" />
      <el-table-column label="详细地址" align="center" prop="address" />
      <el-table-column label="商圈类型" align="center" prop="businessType">
        <template #default="scope">
          <dict-tag :options="bussiness_type" :value="scope.row.businessType"/>
        </template>
      </el-table-column>
      <el-table-column label="区域ID" align="center" prop="regionId" />
      <el-table-column label="合作商ID" align="center" prop="partnerId" />
      <el-table-column label="备注" align="center" prop="remark" />
      <el-table-column label="操作" align="center" class-name="small-padding fixed-width">
        <template #default="scope">
          <el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)" v-hasPermi="['manage:node:edit']">修改</el-button>
          <el-button link type="primary" icon="Delete" @click="handleDelete(scope.row)" v-hasPermi="['manage:node:remove']">删除</el-button>
        </template>
      </el-table-column>
    </el-table>
##修改后:
    <el-table v-loading="loading" :data="nodeList" @selection-change="handleSelectionChange">
      <el-table-column type="selection" width="55" align="center" />
      <el-table-column label="序号" width="55" type="index" align="center" prop="id" />
      <el-table-column label="点位名称" align="center" prop="nodeName" />
      <el-table-column label="区域名称" align="center" prop="regionId" />
      <el-table-column label="商圈类型" align="center" prop="businessType">
        <template #default="scope">
          <dict-tag :options="bussiness_type" :value="scope.row.businessType"/>
        </template>
      </el-table-column>
      <el-table-column label="合作商ID" align="center" prop="partnerId" />
      <el-table-column label="详细地址" align="center" prop="address" />
      <!-- <el-table-column label="备注" align="center" prop="remark" /> -->
      <el-table-column label="操作" align="center" class-name="small-padding fixed-width">
        <template #default="scope">
          <el-button link type="primary" icon="Memo" @click="handleUpdate(scope.row)" v-hasPermi="['manage:node:edit']">查看详情</el-button>
          <el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)" v-hasPermi="['manage:node:edit']">修改</el-button>
          <el-button link type="primary" icon="Delete" @click="handleDelete(scope.row)" v-hasPermi="['manage:node:remove']">删除</el-button>
        </template>
      </el-table-column>
    </el-table>

4.6.3.3、新增框修改

效果图:

主要工作量在查询出来所有的合作商。

和上面的区域搜索有点像,先把合作商的查询js方法导入到页面里面。

先改造新增页面的布局。这里把合作商也改造成下拉框。

<!-- 添加或修改点位管理对话框 -->
    <el-dialog :title="title" v-model="open" width="500px" append-to-body>
      <el-form ref="nodeRef" :model="form" :rules="rules" label-width="80px">
        <el-form-item label="点位名称" prop="nodeName">
          <el-input v-model="form.nodeName" placeholder="请输入点位名称" />
        </el-form-item>
        <el-form-item label="区域" prop="regionId">
          <!-- <el-input v-model="form.regionId" placeholder="请输入区域ID" /> -->
           <el-select v-model="form.regionId" placeholder="请选择区域">
            <el-option
              v-for="item in regionList"
              :key="item.id"
              :label="item.regionName"
              :value="item.id"
            ></el-option>
          </el-select>
        </el-form-item>
        <el-form-item label="商圈类型" prop="businessType">
          <el-select v-model="form.businessType" placeholder="请选择商圈类型">
            <el-option
              v-for="dict in bussiness_type"
              :key="dict.value"
              :label="dict.label"
              :value="parseInt(dict.value)"
            ></el-option>
          </el-select>
        </el-form-item>
        
        <el-form-item label="合作商" prop="partnerId">
          <!-- <el-input v-model="form.partnerId" placeholder="请输入合作商ID" /> -->
           <el-select v-model="form.partnerId" placeholder="请选择合作商">
            <el-option
              v-for="item in partnerList"
              :key="item.id"
              :label="item.partnerName"
              :value="item.id"
            ></el-option>
          </el-select>
        </el-form-item>
        <!-- <el-form-item label="备注" prop="remark">
          <el-input v-model="form.remark" type="textarea" placeholder="请输入内容" />
        </el-form-item> -->
        <el-form-item label="详细地址" prop="address">
          <el-input v-model="form.address" type="textarea" placeholder="请输入内容" />
        </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>

方法里面首先是引入合作商的js,定义返回集合,定义调用方法,去页面初始化的时候调用。分页条件pageInfo直接用的上面一样的。

// 定义返回的合作商集合
const partnerList = ref([]);
// 分页获取合作商信息
function getPartnerList() {
  listPartner(pageInfo).then(response => {
    partnerList.value = response.rows;
  });
}


getPartnerList();

4.6.3.4、列表展示区域和合作商

先分析一下对应关系。这里还加入了设备表。

看下最后返回前端的实体类结构。

这个的难点在要把区域和合作商的类嵌套到vo对象里面。有两种方法,一个是在查询后,在node的service里面进行二次查询,把两个类封装到vo对象里面。一个是直接在mapper.xml里面实现。

4.6.3.4.1、先写sql
select tb_node.*,count(tb_vending_machine.node_id) as vm_count from tb_node left join tb_vending_machine on tb_node.id = tb_vending_machine.node_id group by tb_node.id
4.6.3.4.2、建vo实体类

com/dkd/manage/domain/vo/NodeVo.java

package com.dkd.manage.domain.vo;

import com.dkd.manage.domain.Node;
import com.dkd.manage.domain.Partner;
import com.dkd.manage.domain.Region;
import lombok.Data;

@Data
public class NodeVo extends Node {

    //设备数量
    private Integer vmConut;

    // 区域信息
    private Region region;

    //  合作商信息
    private Partner partner;
}
4.6.3.4.3、新增mapper方法

com/dkd/manage/mapper/NodeMapper.java

    /**
     * 查询点位管理列表
     * @param node
     * @return
     */
    public List<NodeVo> selectNodeVoList(Node node);
4.6.3.4.4、新增mapper.xml方法
    <select id="selectNodeVoList" resultType="com.dkd.manage.domain.vo.NodeVo"
            parameterType="com.dkd.manage.domain.Node">
        select tb_node.*,count(tb_vending_machine.node_id) as vm_count from tb_node left join tb_vending_machine on tb_node.id = tb_vending_machine.node_id
        <where>
            <if test="nodeName != null  and nodeName != ''"> and node_name like concat('%', #{nodeName}, '%')</if>
            <if test="regionId != null "> and region_id = #{regionId}</if>
            <if test="partnerId != null "> and partner_id = #{partnerId}</if>
        </where>
        group by tb_node.id
    </select>
4.6.3.4.5、service层实现

先写接口com/dkd/manage/service/INodeService.java

    /**
     * 查询点位管理列表
     * @param node
     * @return
     */
    public List<NodeVo> selectNodeVoList(Node node);

再写实现com/dkd/manage/service/impl/NodeServiceImpl.java

    /**
     * 查询点位管理列表
     *
     * @param node 点位管理
     * @return 点位管理
     */
    @Override
    public List<NodeVo> selectNodeVoList(Node node) {
        // 获取查询的NodeVo集合
        List<NodeVo> nodeVoList = nodeMapper.selectNodeVoList(node);
        // 遍历nodeVoList集合
        for (NodeVo nodeVo : nodeVoList) {
            // 获取区域id
            Long regionId = nodeVo.getRegionId();
            // 查询区域对象
            if (regionId != null) {
                // 查询区域对象
                Region region = regionMapper.selectRegionById(regionId);
                // 把region对象赋值到nodeVo对象里面
                nodeVo.setRegion(region);
            }

            // 获取合作商id
            Long partnerId = nodeVo.getPartnerId();
            // 查询合作商对象
            if (partnerId != null) {
                // 查询合作商对象
                Partner partner = partnerMapper.selectPartnerById(partnerId);
                // 把region对象赋值到nodeVo对象里面
                nodeVo.setPartner(partner);
            }
        }
        return nodeVoList;
    }
4.6.3.4.6、controller层实现

com/dkd/manage/controller/NodeController.java

    /**
     * 查询点位管理列表
     */
    @PreAuthorize("@ss.hasPermi('manage:node:list')")
    @GetMapping("/list")
    public TableDataInfo list(Node node)
    {
        startPage();
        //List<Node> list = nodeService.selectNodeList(node);
        List<NodeVo> list = nodeService.selectNodeVoList(node);
        return getDataTable(list);
    }
4.6.3.4.7、前端修改
      <el-table-column label="区域名称" align="center" prop="region.regionName" />
      <el-table-column label="合作商" align="center" prop="partner.partnerName" />

4.6.3.5、查询报错修改

根据区域查询会报错。

修改一下sql语句,指定一下查询条件

    <select id="selectNodeVoList" resultType="com.dkd.manage.domain.vo.NodeVo"
            parameterType="com.dkd.manage.domain.Node">
        select tb_node.*,count(tb_vending_machine.node_id) as vm_count from tb_node left join tb_vending_machine on tb_node.id = tb_vending_machine.node_id
        <where>
            <if test="nodeName != null  and nodeName != ''"> and node_name like concat('%', #{nodeName}, '%')</if>
            <if test="regionId != null "> and tb_node.region_id = #{regionId}</if>
            <if test="partnerId != null "> and tb_node.partner_id = #{partnerId}</if>
        </where>
        group by tb_node.id

    </select>

4.6.4、删除机制优化

还有一个比较麻烦的事情,就是由于设置了外键约束,所以删除的时候,删除区域表,会把点位和合作商也删掉,这是数据库的配置。先看下现有的配置

把cascade改成no action,就不会级联删除外键了。

但是同时有一个问题,删除的时候页面直接报错,不友好,我们可以配置一个全局异常捕获器来处理。

找到若依现有的全局异常处理器,com/dkd/framework/web/exception/GlobalExceptionHandler.java,加一个方法。

    /***
     * 数据完整性异常处理
     */
    @ExceptionHandler(DataIntegrityViolationException.class)
    public AjaxResult handleDataIntegrityViolationException(DataIntegrityViolationException e)
    {
        log.error("数据完整性异常",e);
        String message = e.getMessage();
        String returnmess = "";
        if (message.contains("foreign key") && message.contains("tb_node")) {returnmess = "该条数据下有点位关联,无法删除!";} else {returnmess = "有其他数据关联,无法删除!";}
        return AjaxResult.error(returnmess);
    }

5、人员管理

先梳理一下业务范围。

再看看表结构吧,一共3个表。

关系字段:role_id、region_id

数据字典:status(1启用、0停用)

冗余字段:region_name、role_code、role_name

5.1、基础功能生成

使用若依框架,先把简单的单表增删改查实现出来。

5.1.1、新建目录

5.1.2、新建数据字典

太简单不写了。

5.1.3、配置代码生成

先导入表emp

三板斧

再来role表,这个不需要页面配置,所以直接默认就完事了。

三板斧

5.1.4、拷贝代码

role没有页面,不需要执行这个sql。其他的搞上就行。

其他的该放哪放哪。

5.2、人员管理修改

看下最终效果:

现在的样子:

开始改吧。需要优化的有以下几点:

1、主键改成序号。

2、搜索栏注释掉。

3、新增修改页面:角色改成下拉框。

4、新增修改页面:负责区域要下拉框。

5、修改页面:联系电话只读,创建时间只读。

6、后端写入区域和角色的具体信息。

7、区域关联修改emp的区域字段

5.2.1、主键改序号

找到前端页面,src\views\manage\emp\index.vue

原来:
<el-table-column label="主键" align="center" prop="id" />
修改后:
<el-table-column label="序号" align="center" prop="id" width="80" type="index" />

5.2.2、新增修改页面修改

角色和负责区域这两个都要改成下拉框,要先调用接口获取到角色和负责区域。

5.2.2.1、修改角色下拉框

先引入角色的分页查询方法

import { listRole } from "@/api/manage/role";

创建查询方法并调用

const roleList = ref([]);
const pageInfo = reactive({ pageNum: 1, pageSize: 10000, pageInfo: null });
/*** 获取角色列表 */
function getRoleList() {
  listRole(pageInfo).then(response => {
    roleList.value = response.rows;
  });
}

getRoleList();

修改前端改为下拉框

        <el-form-item label="角色" prop="roleId">
          <el-select v-model="form.roleId" placeholder="请选择角色">
            <el-option
              v-for="item in roleList"
              :key="item.roleId"
              :label="item.roleName"
              :value="item.roleId"
            />
          </el-select>
        </el-form-item>

实现后效果

5.2.2.1、修改区域下拉框

引入区域的分页查询方法

import { listRegion } from "@/api/manage/region";

编写查询区域方法

/***获取区域列表 */
const regionList = ref([]);
function getRegionList() {
  listRegion(pageInfo).then(response => {
    regionList.value = response.rows;
  });
}

getRegionList();

修改为下拉框

        <el-form-item label="负责区域" prop="mobile">
          <el-select v-model="form.regionId" placeholder="请选择负责区域">
            <el-option
              v-for="item in regionList"
              :key="item.id"
              :label="item.regionName"
              :value="item.id"
            />
          </el-select>
        </el-form-item>

最后效果:

5.2.3、修改页面的区别回显

使用v-if标签

        <el-form-item label="创建时间" prop="mobile" v-if="form.id != null">
          {{form.createTime }}
        </el-form-item>

5.2.4、后端补充角色和区域信息

在empserviceImpl里操作,修改insert和update两个方法

在里面插入数据

    @Autowired
    private RegionMapper regionMapper;

    @Autowired
    private RoleMapper roleMapper;

    @Override
    public int insertEmp(Emp emp)
    {
        //补充区域信息
        Region region = regionMapper.selectRegionById(emp.getRegionId());
        emp.setRegionName(region.getRegionName());
        //补充角色信息
        Role role = roleMapper.selectRoleByRoleId(emp.getRoleId());
        emp.setRoleName(role.getRoleName());
        emp.setRoleCode(role.getRoleCode());

        emp.setCreateTime(DateUtils.getNowDate());
        return empMapper.insertEmp(emp);
    }

5.2.5、关联修改区域信息

我也不知道为什么要这么设计,要把区域的信息存在emp表里,修改区域信息后,emp表里的区域名称字段不会修改。

要实现关联修改,要用到同步存储。

用到的方法是emp这边的mapper新增一个修改方法,在region的修改方法调用时调用。

在com/dkd/manage/mapper/RegionMapper.java里新增方法

@Update("update tb_emp set region_name =#{regionName} where region_id = #{regionId}")
public int updateEmpRegion(@Param("regionName") String regionName,@Param("regionId") Long regionId);

在com/dkd/manage/service/impl/RegionServiceImpl.java修改方法里面加上修改emp表的方法。

/**
 * 修改区域管理
 * 
 * @param region 区域管理
 * @return 结果
 */
@Override
@Transactional(rollbackFor = Exception.class)
public int updateRegion(Region region)
{
    //修改区域表
    region.setUpdateTime(DateUtils.getNowDate());
    int i = regionMapper.updateRegion(region);
    //修改员工表
    regionMapper.updateEmpRegion(region.getRegionName(),region.getId());
    return i;
}

6、文件存储到阿里oss

6.1、创建bucket

6.2、获取授权码配置本地

2). 配置AK & SK

管理员身份打开CMD命令行,执行如下命令,配置系统的环境变量。

set OSS_ACCESS_KEY_ID=LTAI5tXXXXXXXXXXXXXXXXXXXXM8TP
set OSS_ACCESS_KEY_SECRET=UzMcJXXXXXXXXXXXXXXXXXXXXdabTNafi

执行如下命令,验证环境变量是否生效。

echo %OSS_ACCESS_KEY_ID%
echo %OSS_ACCESS_KEY_SECRET%

6.3、使用阿里云sdk开发

文档url:https://help.aliyun.com/zh/oss/user-guide/regions-and-endpoints?spm=a2c4g.11186623.0.0.31855168rmtubR

6.3.1、引入依赖

<dependency>
    <groupId>com.aliyun.oss</groupId>
    <artifactId>aliyun-sdk-oss</artifactId>
    <version>3.17.4</version>
</dependency>

如果使用的是Java 9及以上的版本,则需要添加JAXB相关依赖。添加JAXB相关依赖示例代码如下:

<dependency>
    <groupId>javax.xml.bind</groupId>
    <artifactId>jaxb-api</artifactId>
    <version>2.3.1</version>
</dependency>
<dependency>
    <groupId>javax.activation</groupId>
    <artifactId>activation</artifactId>
    <version>1.1.1</version>
</dependency>
<!-- no more than 2.3.3-->
<dependency>
    <groupId>org.glassfish.jaxb</groupId>
    <artifactId>jaxb-runtime</artifactId>
    <version>2.3.3</version>
</dependency>

放在common组件里面,查看一下maven确认引入成功。

6.3.2、配置凭证

set OSS_ACCESS_KEY_ID=<ALIBABA_CLOUD_ACCESS_KEY_ID>
set OSS_ACCESS_KEY_SECRET=<ALIBABA_CLOUD_ACCESS_KEY_SECRET>

6.3.3、快速入门简单上传

按照实际情况修改demo代码,

6.3.4、借助x-file-storage上传文件

https://x-file-storage.xuyanwu.cn/#/%E5%BF%AB%E9%80%9F%E5%85%A5%E9%97%A8

按照快速入门操作。

先导入依赖dkd-common/pom.xml

        <dependency>
            <groupId>org.dromara.x-file-storage</groupId>
            <artifactId>x-file-storage-spring</artifactId>
            <version>2.2.1</version>
        </dependency>

在配置配置文件dkd-admin/src/main/resources/application.yml

dromara:
  x-file-storage: #文件存储配置
    default-platform: aliyun-oss-1 #默认使用的存储平台
    thumbnail-suffix: ".min.jpg" #缩略图后缀,例如【.min.jpg】【.png】
    #对应平台的配置写在这里,注意缩进要对齐
    aliyun-oss:
      - platform: aliyun-oss-1 # 存储平台标识
        enable-storage: true  # 启用存储
        access-key: 12
        secret-key: 12
        end-point: oss-cn-hangzhou.aliyuncs.com
        bucket-name: dkd-241028
        domain: https://dkd-241028.oss-cn-hangzhou.aliyuncs.com/ # 访问域名,注意“/”结尾,例如:https://abc.oss-cn-shanghai.aliyuncs.com/
        base-path: dkd-images/ # 基础路径

直接修改上传接口,找到方法dkd-admin/src/main/java/com/dkd/web/controller/common/CommonController.java

注释掉原来的方法,

@PostMapping("/upload")
    public AjaxResult uploadFile(MultipartFile file) throws Exception
    {
        try
        {
//            // 上传文件路径
//            String filePath = RuoYiConfig.getUploadPath();
//            // 上传并返回新文件名称
//            String fileName = FileUploadUtils.upload(filePath, file);
//            String url = serverConfig.getUrl() + fileName;

            //指定文件存储的位置,以时间年月日为格式
            //String fileStorageName = LocalTime.now().format(DateTimeFormatter.ofPattern("yyyy/MM/dd")) + "/";
            //获取当前时间,格式为yyyy/MM/dd
            String date = new SimpleDateFormat("yyyy/MM/dd").format(new java.util.Date());
            String fileStorageName = date+"/";
            log.info("fileStorageName: " + fileStorageName);
            FileInfo fileInfo = fileStorageService.of(file)
                    .setPath(fileStorageName) //保存到相对路径下,为了方便管理,不需要可以不写
                    .upload();


            AjaxResult ajax = AjaxResult.success();
            ajax.put("url", fileInfo.getUrl());
            ajax.put("fileName", fileInfo.getUrl());
            ajax.put("newFileName", fileInfo.getUrl());
            ajax.put("originalFilename", file.getOriginalFilename());
            return ajax;
        }
        catch (Exception e)
        {
            return AjaxResult.error(e.getMessage());
        }
    }

配合的前端也是,检测到图片里面带url以后就不拼接了。src\components\ImageUpload\index.vue

fileList.value = list.map(item => {
      if (typeof item === "string") {
        if (item.indexOf(baseUrl) === -1   && item.indexOf("http") === -1) {
          item = { name: baseUrl + item, url: baseUrl + item };
        } else {
          item = { name: item, url: item };
        }
      }
      return item;
    });

7、设备管理

页面概览

业务流程

库表设计

7.1、基础代码生成

7.1.1、创建目录

7.1.2、创建数据字典

设备状态

7.1.3、生成代码导入项目

导入表格

7.1.3.1、配置设备类型表

7.1.3.2、配置设备表

7.1.3.3、配置货道表

7.1.3.4、导入项目

把文件导出,添加到项目。

不用导入货道的sql,因为不需要这个页面。

7.2、代码优化

7.2.1、设备类型管理优化

目标状态:

这里就不多说了,简单的一些页面显示取消就行了。

编辑和添加页面的货道行列要在一行显示,且只能输入10以内的整数。

        <el-row :gutter="20">
          <el-col :span="10">
            <el-form-item label="货道行" prop="vmRow">
              <el-input-number v-model="form.vmRow" placeholder="行" :min="1" :max="10"></el-input-number>
            </el-form-item>
          </el-col>
          <el-col :span="10">
            <el-form-item label="货道列" prop="vmCol">
              <el-input-number v-model="form.vmCol" placeholder="列" :min="1" :max="10"></el-input-number>
            </el-form-item>
          </el-col>
        </el-row>
        <el-form-item label="货道容量" prop="channelMaxCapacity">
          <!-- <el-input v-model="form.channelMaxCapacity" placeholder="请输入设备容量" /> -->
          <el-input-number v-model="form.channelMaxCapacity" placeholder="请输入容量" :min="1" :max="10"></el-input-number>
        </el-form-item>

7.2.2、设备管理优化

7.2.2.1、搜索栏优化

去除不需要的搜索项

7.2.2.2、展示栏优化

合作商和设备型号改为展示具体名称

这里基本都是前端的修改工作量。src\views\manage\vm\index.vue

//引入合作商和设备类型的查询方法,并定义一个可以全量查询的分页参数
import { listPartner } from "@/api/manage/partner";
import { listVmType } from "@/api/manage/vmType";
const allParam = reactive({
  pageNum:1,pageSize:100000
})

//分别定义查询方法和结果集合,并在getList之前调用
const vmRef = ref([]);
// 获取设备类型列表
function getVmTypeList() {
  listVmType(allParam).then(response => {
    vmRef.value = response.rows;
});
}

const partnerList = ref([]);
//查询合作商列表
function getPartnerList() {
  listPartner(allParam).then(response => {
    partnerList.value = response.rows;
});
}

getVmTypeList();
getPartnerList();

//修改页面内容,遍历比对,比对上了就显示
      <el-table-column label="合作商" align="center" prop="partnerId" >
        <template #default="scope">
          <div v-for="item in partnerList" :key="item.id">
            <span v-if="item.id==scope.row.partnerId">{{item.partnerName}}</span>
          </div>
        </template>
      </el-table-column>
      <el-table-column label="设备型号" align="center" prop="vmTypeId" >
        <template #default="scope">
          <div v-for="item in vmRef" :key="item.id">
            <span v-if="item.id==scope.row.vmTypeId">{{item.model}}</span>
          </div>
        </template>
      </el-table-column>

7.2.2.3、添加页面优化

自动生成设备编码,且回显时只读。

点位和型号改为下拉框。

    <el-dialog :title="title" v-model="open" width="500px" append-to-body>
      <el-form ref="vmRef" :model="form" :rules="rules" label-width="80px">
        <el-form-item label="设备编号" prop="innerCode">
        //用一个三位运算符显示
          <span>{{form.innerCode != null ? form.innerCode : '系统将自动生成' }}</span>
        </el-form-item>
        <el-form-item label="点位" prop="nodeId">
          <!-- <el-input v-model="form.nodeId" placeholder="请输入点位Id" /> -->
          <el-select v-model="form.nodeId" placeholder="请选择点位" clearable>
            <el-option
              v-for="item in nodeList"
              :key="item.id"
              :label="item.nodeName"
              :value="item.id"
            />
          </el-select>
        </el-form-item>
        <el-form-item label="设备型号" prop="vmTypeId">
          <el-select v-model="form.vmTypeId" placeholder="请选择设备型号" clearable>
            <el-option
              v-for="item in vmTypeRef"
              :key="item.id"
              :label="item.name"
              :value="item.id"
            />
          </el-select>
        </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>


import { listNode } from "@/api/manage/node";
//查询点位列表
const nodeList = ref([]);
function getNodeList() {
  listNode(allParam).then(response => {
    nodeList.value = response.rows;
  });
}

getNodeList();

还留了一个坑,上面的变量vmRef要改成vmTypeRef,vmRef已经被别的用过了。

新增的时候还需要关注好关联字段的写入。

我们找到这个表的service实现类,进行改造。com/dkd/manage/service/impl/VendingMachineServiceImpl.java

找到insertVendingMachine方法。

首先是inner_code字段,通过工具类生成编号。

这边原来有个工具类,在dkd-common/src/main/java/com/dkd/common/utils/uuid/UUIDUtils.java,原来的方法默认生成8位的编码,不太实用,这边我改成传入参数,设置为传入数字生成数字位的编码。

    @Autowired
    private VendingMachineMapper vendingMachineMapper;

    @Autowired
    private IVmTypeService vmTypeService;

    @Autowired
    private INodeService nodeService;

    @Autowired
    private IChannelService channelService;


    /**
     * 新增设备管理
     * 
     * @param vendingMachine 设备管理
     * @return 结果
     */
    @Transactional
    @Override
    public int insertVendingMachine(VendingMachine vendingMachine)
    {
        // 1、新增设备
        vendingMachine.setCreateTime(DateUtils.getNowDate());
        vendingMachine.setUpdateTime(DateUtils.getNowDate());
        // 1-1、填入设备编码
        vendingMachine.setInnerCode(UUIDUtils.getUUID(8));
        // 1-2、设置设备最大容量
        VmType vmType = vmTypeService.selectVmTypeById(vendingMachine.getVmTypeId());
        vendingMachine.setChannelMaxCapacity(vmType.getChannelMaxCapacity());
        // 1-3、插入点位表相关信息
        Node node = nodeService.selectNodeById(vendingMachine.getNodeId());
        vendingMachine.setAddr(node.getAddress());
        vendingMachine.setBusinessType(node.getBusinessType());
        vendingMachine.setPartnerId(node.getPartnerId());
        vendingMachine.setRegionId(node.getRegionId());
        // 1-4、设置运行状态
        vendingMachine.setVmStatus(DkdContants.VM_STATUS_NODEPLOY);
        int result = vendingMachineMapper.insertVendingMachine(vendingMachine);
        // 2、新增货道
        for (int i = 1; i <= vmType.getVmRow(); i++) {
            for (int j = 1; j <= vmType.getVmCol(); j++) {
                Channel channel = new Channel();
                channel.setChannelCode(String.valueOf(i) +"-"+ String.valueOf(j));
                channel.setVmId(vendingMachine.getId());
                channel.setInnerCode(vendingMachine.getInnerCode());
                channel.setMaxCapacity(vmType.getChannelMaxCapacity());
                channel.setCreateTime(DateUtils.getNowDate());
                channel.setUpdateTime(DateUtils.getNowDate());
                channelService.insertChannel(channel);
            }
        }
        return result;
    }

7.2.2.4、修改页面优化

页面目标:

供货时间格式化:

        <el-form-item label="最近供货时间" v-if="form.innerCode != null">
          <span>{{ parseTime(form.lastSupplyTime,"{y}-{m}-{d} {h}:{m}:{s}") }}</span>
        </el-form-item>
需要把默认引入的parseTime方法注释掉
// import { parseTime } from "element-plus/es/components/time-select/src/utils.mjs";

显示设备类型:

<el-form-item label="设备类型" v-if="form.innerCode != null">
          <!-- <span>{{ form.vmTypeId }}</span> -->
          <div v-for="item in vmTypeRef" :key="item.id">
            <item v-if="item.id==form.vmTypeId">{{ item.name }}</item>
          </div>
        </el-form-item>

合作商同理

        <el-form-item label="合作商名称" v-if="form.innerCode != null">
          <div v-for="item in partnerList" :key="item.id">
            <item v-if="item.id==form.partnerId">{{ item.partnerName }}</item>
          </div>
        </el-form-item>

全量代码:

    <el-dialog :title="title" v-model="open" width="500px" append-to-body>
      <el-form ref="vmRef" :model="form" :rules="rules" label-width="100px">
        <el-form-item label="设备编号" prop="innerCode">
          <span>{{form.innerCode != null ? form.innerCode : '系统将自动生成' }}</span>
        </el-form-item>
        <el-form-item label="最近供货时间" v-if="form.innerCode != null">
          <span>{{ parseTime(form.lastSupplyTime,"{y}-{m}-{d} {h}:{m}:{s}") }}</span>
        </el-form-item>
        <el-form-item label="设备类型" v-if="form.innerCode != null">
          <!-- <span>{{ form.vmTypeId }}</span> -->
          <div v-for="item in vmTypeRef" :key="item.id">
            <item v-if="item.id==form.vmTypeId">{{ item.name }}</item>
          </div>
        </el-form-item>
        <el-form-item label="设备容量" v-if="form.innerCode != null">
          <span>{{ form.channelMaxCapacity }}</span>
        </el-form-item>
        <el-form-item label="点位" prop="nodeId">
          <!-- <el-input v-model="form.nodeId" placeholder="请输入点位Id" /> -->
          <el-select v-model="form.nodeId" placeholder="请选择点位" clearable>
            <el-option
              v-for="item in nodeList"
              :key="item.id"
              :label="item.nodeName"
              :value="item.id"
            />
          </el-select>
        </el-form-item>
        <el-form-item label="设备型号" prop="vmTypeId" v-if="form.nodeId == null">
          <el-select v-model="form.vmTypeId" placeholder="请选择设备型号" clearable>
            <el-option
              v-for="item in vmTypeRef"
              :key="item.id"
              :label="item.name"
              :value="item.id"
            />
          </el-select>
        </el-form-item>
        <el-form-item label="合作商名称" v-if="form.innerCode != null">
          <div v-for="item in partnerList" :key="item.id">
            <item v-if="item.id==form.partnerId">{{ item.partnerName }}</item>
          </div>
        </el-form-item>
        <el-form-item label="所属区域" v-if="form.innerCode != null">
          <div v-for="item in regionList" :key="item.id">
            <item v-if="item.id==form.regionId">{{ item.regionName }}</item>
          </div>
        </el-form-item>
        <el-form-item label="设备地址" v-if="form.innerCode != null">
          <span>{{form.addr}}</span>
        </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>

由于关联了好多其他表的数据,还得修改更新的service方法。

主要是把node表的更新过来。

    /**
     * 修改设备管理
     * 
     * @param vendingMachine 设备管理
     * @return 结果
     */
    @Override
    public int updateVendingMachine(VendingMachine vendingMachine)
    {
        vendingMachine.setUpdateTime(DateUtils.getNowDate());
        // 获取更新后的点位id
        Node node = nodeService.selectNodeById(vendingMachine.getNodeId());
        vendingMachine.setAddr(node.getAddress());
        vendingMachine.setRegionId(node.getRegionId());
        vendingMachine.setBusinessType(node.getBusinessType());
        vendingMachine.setPartnerId(node.getPartnerId());
        vendingMachine.setUpdateTime(DateUtils.getNowDate());
        return vendingMachineMapper.updateVendingMachine(vendingMachine);
    }

7.2.3、设备状态管理

由于没用框架创建页面,所以得去手动创建一个。

7.2.3.1、手动创建前端页面

src\views\manage\vmStatus\index.vue

直接先复制vm的页面用着。

7.2.3.2、配置菜单和路由

其中:

路由地址:地址栏显示的字符

组件路径:前端的页面路径(只写views后的路径就行)

权限字符:去后端controller方法里面去找

7.2.3.3、修改页面

目标状态:

需要修改的是:

删掉新增修改删除按钮

删掉复选框

新增序号列

新增设备状态列

太简单就不说了。

7.2.4、点位详情

点位详情还有个要修改的,需要新增一个查看详情按钮,显示点位下的设备。

新增按钮
<el-button link type="primary" icon="Memo" @click="showVmList(scope.row)" v-hasPermi="['manage:vm:list']">查看详情</el-button>

创建方法
import { listVm } from "@/api/manage/vm";
const { vm_status } = proxy.useDict('vm_status');
// 定义分页查询参数
const pageInfo = reactive({ pageNum: 1, pageSize: 10000 });
const vmdelList = ref([]);
const nodeDetail = ref(false);
//获取点位详情
function showVmList(row){
  pageInfo.nodeId = row.id;
  listVm(pageInfo).then(response => {
    vmdelList.value = response.rows;
    nodeDetail.value = true;
})
}
对话框修改
    <!-- 点位详情对话框 -->
     <el-dialog title="点位详情" v-model="nodeDetail" width="500px" append-to-body>
      <el-table  :data="vmdelList" >
      <el-table-column label="序号" type="index" width="55" align="center" />
      <el-table-column label="设备编号" align="center" prop="innerCode" />
      <el-table-column label="详细地址" align="center" prop="addr" />
      <el-table-column label="设备状态" align="center" prop="vmStatus">
        <template #default="scope">
          <dict-tag :options="vm_status" :value="scope.row.vmStatus"/>
        </template>
      </el-table-column>
      <el-table-column label="最后供货时间" align="center" prop="lastSupplyTime">
        <template #default="scope">
          <!-- {{ scope.row.lastSupplyTime }} -->
          {{ parseTime(scope.row.lastSupplyTime,'{y}-{m}-{d} {h}:{i}:{s}') }}
        </template>
      </el-table-column>
    </el-table>
     </el-dialog>

8、策略管理

这个模块主要是折扣策略的管理。

8.1、创建目录

8.2、代码生成

管理的表格是tb_policy

查询页面如下“

新增页面

修改页面

8.2.1、配置代码生成器

8.2.2、代码拷贝

跳过

8.3、页面改造

主要是新增修改页面的修改

    <el-dialog :title="title" v-model="open" width="500px" append-to-body>
      <el-form ref="policyRef" :model="form" :rules="rules" label-width="80px">
        <el-form-item label="策略名称" prop="policyName">
          <el-input v-model="form.policyName" placeholder="请输入策略名称" />
        </el-form-item>
        <el-form-item label="策略方案" prop="discount">
          {{ form.discount }} 折
          <el-slider v-model="form.discount" />
        </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>

还有详情页的展示,列表展示相关的设备。

这里主要是前端的修改,后端都是现成的,但是也要关注一下后端有个大坑。

    <!-- 策略详情对话框 -->
     <el-dialog title="策略详情" v-model="dialogVisibleVm" width="500px" append-to-body>
      <el-form-item label="策略名称" prop="policyName">
        <el-input v-model="form.policyName" placeholder="请输入策略名称" disabled/>
        
      </el-form-item>
      <label>关联设备:</label>
      <el-table :data="vmDetaillist">
        <el-table-column label="序号" type="index" width="50" align="center"></el-table-column>
        <el-table-column label="设备编码" prop="innerCode" width="100"></el-table-column>
        <el-table-column label="设备地址" prop="addr"></el-table-column>
        <el-table-column label="设备状态" prop="vmStatus" width="100">
          <template #default="scope">
            <span>{{ scope.row.vmStatus == 1? "在用":"停用" }}</span>
          </template>
        </el-table-column>
      </el-table>
    </el-dialog>


import { listVm } from "@/api/manage/vm";

/** 修改按钮操作 */
function handleUpdate(row) {
  reset();
  const _policyId = row.policyId || ids.value
  getPolicy(_policyId).then(response => {
    form.value = response.data;
    open.value = true;
    title.value = "修改策略管理";
  });
}


//定义一个分页对象
const pageInfo = reactive({
  pageNum: 1,
  pageSize: 10000,
  policyId: null
});
const vmDetaillist = ref([]);
//定义一个对话框
const dialogVisibleVm = ref(false);
//获取设备详情
function getPolicyInfo(row) {
  //1.获取策略信息
  form.vlaue = row;
  //2.调用方法获取设备信息
  pageInfo.policyId = row.policyId;
  listVm(pageInfo).then(response => {
    //3.获取设备信息
    vmDetaillist.value = response.rows;
    dialogVisibleVm.value = true;
    //alert(vmlist.value)
  });
}

发现一个问题,就是我虽然往后端传了policyId,但是没有过滤,最后发现是mapper里面少了过滤字段。

    <sql id="selectVendingMachineVo">
        select id, inner_code, channel_max_capacity, node_id, addr, last_supply_time, business_type, region_id, partner_id, vm_type_id, vm_status, running_status, longitudes, latitude, client_id, policy_id, create_time, update_time from tb_vending_machine
    </sql>

    <select id="selectVendingMachineList" parameterType="VendingMachine" resultMap="VendingMachineResult">
        <include refid="selectVendingMachineVo"/>
        <where>  
            <if test="innerCode != null  and innerCode != ''"> and inner_code = #{innerCode}</if>
            <if test="nodeId != null "> and node_id = #{nodeId}</if>
            <if test="businessType != null "> and business_type = #{businessType}</if>
            <if test="regionId != null "> and region_id = #{regionId}</if>
            <if test="partnerId != null "> and partner_id = #{partnerId}</if>
            <if test="vmTypeId != null "> and vm_type_id = #{vmTypeId}</if>
            <if test="vmStatus != null "> and vm_status = #{vmStatus}</if>
            <if test="policyId != null "> and policy_id = #{policyId}</if>
            <if test="runningStatus != null  and runningStatus != ''"> and running_status = #{runningStatus}</if>
        </where>
    </select>

注意以后查询那一栏看直接全选,大不了页面删掉就行。

8.4、策略配置

页面效果:

先在页面上添加一个按钮src\views\manage\vm\index.vue

<el-button link type="primary" icon="HelpFilled" @click="handleUpdate(scope.row)" v-hasPermi="['manage:vm:edit']">策略</el-button>

创建方法来处理请求

import { listPolicy } from "@/api/manage/policy";


//定义一个分页对象
const pageInfo = reactive({
  pageNum: 1,
  pageSize: 10000
});
const policyList = ref([]);
const policySpan = ref(false);
// 策略按钮操作
function handleUpdatePolicy(row) {
  reset();
  const _id = row.id || ids.value
  const _policyId = row.policyId
  form.value.id = _id;
  form.value.policyId = _policyId;
  listPolicy(pageInfo).then(response => {
    policyList.value = response.rows;
    console.log(policyList.value)
    policySpan.value = true;
  });
}

还是提交的form表单,穿一个id和策略id就可以实现修改。

9、商品管理

9.1、模块介绍

需求说明

库表设计

9.2、代码生成

9.2.1、商品类型

先看下商品类型的页面

三板斧:

9.2.2、商品管理

三板斧:

然后导入就完事了。

9.3、代码优化

9.3.1、商品类型改造

目标状态:

需要改造前端页面就行了。src\views\manage\skuClass\index.vue

这里就把序号改成index就行了,就不多说了。

    <el-table v-loading="loading" :data="skuClassList" @selection-change="handleSelectionChange">
      <!-- <el-table-column type="selection" width="55" align="center" /> -->
      <el-table-column label="序号"  type="index" width="55"  align="center" />
      <el-table-column label="类别名称" align="center" prop="className" />
      <el-table-column label="操作" align="center" class-name="small-padding fixed-width">
        <template #default="scope">
          <el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)" v-hasPermi="['manage:skuClass:edit']">修改</el-button>
          <el-button link type="primary" icon="Delete" @click="handleDelete(scope.row)" v-hasPermi="['manage:skuClass:remove']">删除</el-button>
        </template>
      </el-table-column>
    </el-table>

后端需要修改一个东西,数据库里名称做了唯一,所以输入重复名称会报错,这时候需要去异常捕获器里面去配置一下这种报错。

dkd-framework/src/main/java/com/dkd/framework/web/exception/GlobalExceptionHandler.java

    /***
     * 数据完整性异常处理
     */
    @ExceptionHandler(DataIntegrityViolationException.class)
    public AjaxResult handleDataIntegrityViolationException(DataIntegrityViolationException e)
    {
        log.error("数据完整性异常",e);
        String message = e.getMessage();
        String returnmess = "";
        if (message.contains("foreign key") && message.contains("tb_node")) {returnmess = "该条数据下有点位关联,无法删除!";} else if (message.contains("Duplicate")) {returnmess = "已有该条数据,请重新输入!";}{returnmess = "有其他数据关联,无法删除!";}

        return AjaxResult.error(returnmess);
    }

9.3.2、商品管理改造

页面目标:

9.3.2.1、展示页面改造

src\views\manage\sku\index.vue

   <el-table v-loading="loading" :data="skuList" @selection-change="handleSelectionChange">
      <!-- <el-table-column type="selection" width="55" align="center" /> -->
      <el-table-column label="序号" type="index" width="50" 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" >
        <template #default="scope">
          <el-tag>{{ scope.row.price/100 }} 元</el-tag>
        </template>
      </el-table-column>
      <el-table-column label="商品类型" align="center" prop="classId" >
        <template #default="scope">
          <div v-for="item in classOptions">
            <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}:{s}:{i}') }}</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" icon="Edit" @click="handleUpdate(scope.row)" v-hasPermi="['manage:sku:edit']">修改</el-button>
          <el-button link type="primary" icon="Delete" @click="handleDelete(scope.row)" v-hasPermi="['manage:sku:remove']">删除</el-button>
        </template>
      </el-table-column>
    </el-table>


import { listSkuClass } from "@/api/manage/skuClass";
const classOptions = ref([]);
const allParam = reactive({
  pageNum:1,pageSize:100000
})
// 获取商品类型下拉框列表
function getSkuClassOptions() {
  listSkuClass(allParam).then(response => {
    classOptions.value = response.rows;
  });
}

getSkuClassOptions();

9.3.2.2、新增修改页面改造

修改商品类型为下拉框选择

        <el-form-item label="商品类型" prop="classId">
          <el-select v-model="form.classId" placeholder="请选择商品类型" with="50px">
            <el-option v-for="item in skuclassOptions" :key="item.classId" :value="item.classId" :label="item.className" />
          </el-select>
        </el-form-item>

还需要把回显的价格单位分到元

/** 修改按钮操作 */
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 => {
    if (valid) {
      form.value.price*=100;//转换单位元到分
      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();
        });
      }
    }
  });
}

9.3.2.3、商品删除改造

找到dkd-manage/src/main/java/com/dkd/manage/service/impl/SkuServiceImpl.java

先引入依赖
@Autowired
private IChannelService channelService;

    /**
     * 批量删除商品管理
     * 
     * @param skuIds 需要删除的商品管理主键
     * @return 结果
     */
    @Override
    public int deleteSkuBySkuIds(Long[] skuIds)
    {
        //1、查询商品关联货道的数量
        int count = channelService.countChannelBySkuIds(skuIds);
        if (count > 0){
            //抛出业务异常
            throw new ServiceException("商品关联了货道,不能删除");
        }
        //2、没有关联则直接删除
        return skuMapper.deleteSkuBySkuIds(skuIds);
    }

创建查询方法dkd-manage/src/main/java/com/dkd/manage/service/IChannelService.java

/**
 * 根据商品id查询关联售货机货道数量
 * @param skuIds
 * @return
 */
public int countChannelBySkuIds(Long[] skuIds);

创建实现方法

dkd-manage/src/main/java/com/dkd/manage/service/impl/ChannelServiceImpl.java

/**
 * 删除售货机货道信息
 * 
 * @param id 售货机货道主键
 * @return 结果
 */
@Override
public int deleteChannelById(Long id)
{
    return channelMapper.deleteChannelById(id);
}

@Override
public int countChannelBySkuIds(Long[] skuIds) {
    int count = 0;
    for (Long skuId : skuIds) {
        Channel channel = new Channel();
        channel.setSkuId(skuId);
        List<Channel> channelList = channelMapper.selectChannelList(channel);
        count += channelList.size();
        System.out.println(channelList.size());
    }
    return count;
}

9.3.2.4、批量导入功能

新增一个导入对话框

修改src\views\manage\sku\index.vue

-----新增一个按钮     
 <el-col :span="1.5">
        <el-button
          type="warning"
          plain
          icon="Upload"
          @click="handleImport"
          v-hasPermi="['manage:sku:add']"
        >导入</el-button>
      </el-col>

-----定义一个方法打开对话框
const importOpen = ref(false);
function handleImport() {
  importOpen.value = true;
}

-----对话框和上传按钮
    <!-- 数据导入对话框 -->
    <el-dialog title="导入数据" v-model="importOpen" width="400px" append-to-body>
      <el-upload
        class="upload-demo"
        ref="uploadRef"
        :action="uploadExcelUrl"
        :headers="headers"
        :on-success="handleUploadSuccess"
        :on-error="handleUploadError"
        :before-upload="handleBeforeUpload"
        :limit="1"
        :auto-upload="false">
        <template #trigger>
          <el-button  type="primary" >上传文件</el-button>
        </template>
        <el-button style="margin-left: 10px;" size="small" type="success" @click="submitUpload">提交</el-button>
        <div slot="tip" class="el-upload__tip">上传文件仅支持xls/xlsx,且不超过1MB</div>
      </el-upload>
    </el-dialog>


-----js方法把文件发往后台
//上传excel文件
const uploadRef = ref({});
function submitUpload() {
  uploadRef.value.submit();
}

-----引入token
import { getToken } from "@/utils/auth";

// 上传成功回调
function handleUploadSuccess(res, file) {
  if (res.code === 200) {
    //提示信息
    proxy.$modal.msgSuccess("提交成功");
    //关闭对话框
    importOpen.value = false;
    //重新查询页面
    getList();
  } else {
    proxy.$modal.msgError(res.msg);
  }
  //清空文件列表
  uploadRef.value.clearFiles();
  //关闭loading页面
  proxy.$modal.closeLoading();
}

// 上传失败
function handleUploadError() {
  proxy.$modal.msgError("提交失败");
  //清空文件列表
  uploadRef.value.clearFiles();
  //关闭loading页面
  proxy.$modal.closeLoading();
}

// 上传文件校验
const props = defineProps({
  modelValue: [String, Object, Array],
  // 大小限制(MB)
  fileSize: {
    type: Number,
    default: 1,
  },
  // 文件类型, 例如['png', 'jpg', 'jpeg']
  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(`上传文件大小不能超过 ${props.fileSize} MB!`);
      return false;
    }
  }
  proxy.$modal.loading("正在上传,请稍候...");
  //number.value++;
}

-----获取请求后端地址
const baseUrl = import.meta.env.VITE_APP_BASE_API;
// 上传地址
const uploadExcelUrl = ref(import.meta.env.VITE_APP_BASE_API + "/manage/sku/upload"); // 上传的图片服务器地址
// token获取
const headers = ref({ Authorization: "Bearer " + getToken() });

后端也需要修改

找到controller方法,dkd-manage/src/main/java/com/dkd/manage/controller/SkuController.java

/**
 * 批量导入商品信息
 */
@PreAuthorize("@ss.hasPermi('manage:sku:add')")
@Log(title = "商品管理", businessType = BusinessType.IMPORT)
@PostMapping("/upload")
public AjaxResult importExcelData(MultipartFile file) throws Exception{
    ExcelUtil<Sku> util = new ExcelUtil<Sku>(Sku.class);
    List<Sku> skuList = util.importExcel(file.getInputStream());
    for (Sku sku : skuList) {
        logger.info("-----------------------");
        logger.info("sku:"+sku.toString());
        skuService.insertSku(sku);
    }
    return success("插入成功"+skuList.size()+"条数据");
}

9.3.2.5、集成easyExcel

查看手册

https://doc.ruoyi.vip/ruoyi-vue/document/cjjc.html#%E9%9B%86%E6%88%90easyexcel%E5%AE%9E%E7%8E%B0excel%E8%A1%A8%E6%A0%BC%E5%A2%9E%E5%BC%BA

先引入依赖,找到dkd-common/pom.xml

<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>easyexcel</artifactId>
    <version>4.0.1</version>
</dependency>

添加到工具类dkd-common/src/main/java/com/dkd/common/utils/poi/ExcelUtil.java

/**
 * 对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());
	}
}

需要在Sku实体类上打标签。dkd-manage/src/main/java/com/dkd/manage/domain/Sku.java

dkd-manage/pom.xml还得把依赖添加到这,否则不知道为啥读不到

/**
 * 商品管理对象 tb_sku
 * 
 * @author krizen
 * @date 2024-12-04
 */
@ExcelIgnoreUnannotated // 忽略没有注解的属性
@ColumnWidth(16) // 设置列宽
@HeadRowHeight(20) // 设置表头高度
@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;

再去修改调用dkd-manage/src/main/java/com/dkd/manage/controller/SkuController.java

/**
 * 导出商品管理列表
 */
@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.exportExcel(response, list, "商品管理数据");
    //util.exportEasyExcel(response, list, "商品管理数据");
}

/**
 * 批量导入商品信息
 */
@PreAuthorize("@ss.hasPermi('manage:sku:add')")
@Log(title = "商品管理", businessType = BusinessType.IMPORT)
@PostMapping("/upload")
public AjaxResult importExcelData(MultipartFile file) throws Exception{
    ExcelUtil<Sku> util = new ExcelUtil<Sku>(Sku.class);
    List<Sku> skuList = util.importExcel(file.getInputStream());
    //List<Sku> skuList = util.importEasyExcel(file.getInputStream());
    for (Sku sku : skuList) {
        logger.info("-----------------------");
        logger.info("sku:"+sku.toString());
        skuService.insertSku(sku);
    }
    return success("插入成功"+skuList.size()+"条数据");
}

一开始的时候idea找不到依赖,后来重启一下idea就行了,抽象。

9.3.2.6、货道管理商品

货道关联商品是由关联字段关联的。

部分前端直接拷贝,就不写了。

src\api\manage\channel.js 这里的js只包含新增的,要手动拷贝进去

src\views\manage\vm\index.scss

src\views\manage\vm\components

把indev.vue的拷贝进去

<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 -->



// ********************货道********************
// 货道组件
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********************


<style lang="scss" scoped src="./index.scss"></style>

开始后端

需要开发一个接口,传入设备内码,返回货道和关联商品的集合。

先写mapper:dkd-manage/src/main/java/com/dkd/manage/mapper/ChannelMapper.java

    /**
     * 根据售货机编号查询货道信息
     * @param innerCode
     * @return
     */
    public List<ChannelVo> selectChannelVoByInnerCode(String innerCode);

再写mapper的xml

手动配置封装    
<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="selectChannelVoByInnerCode" resultMap="ChannelVoResult">
        <include refid="selectChannelVo"/>
        where inner_code = #{innerCode}
    </select>

再写service层

dkd-manage/src/main/java/com/dkd/manage/service/IChannelService.java

/**
 * 根据售货机编号查询货道信息
 * @param innerCode
 * @return
 */
public List<ChannelVo> selectChannelVoByInnerCode(String innerCode);

写实现dkd-manage/src/main/java/com/dkd/manage/service/impl/ChannelServiceImpl.java

@Override
public List<ChannelVo> selectChannelVoByInnerCode(String innerCode) {
    return channelMapper.selectChannelVoByInnerCode(innerCode);
}

conreoller层就调用返回即可dkd-manage/src/main/java/com/dkd/manage/controller/ChannelController.java

/**
 * 根据售货机软编号查询货道信息
 */
@PreAuthorize("@ss.hasPermi('manage:channel:list')")
@GetMapping("/list/{innerCode}")
public AjaxResult listByInnerCode(@PathVariable String innerCode) {
    List<ChannelVo> channelVoList = channelService.selectChannelVoByInnerCode(innerCode);
    return success(channelVoList);
}

接着就是更新货道商品接口

需要定义一个dto对象来接受前端传过来的数据

先写mapper,dkd-manage/src/main/java/com/dkd/manage/mapper/ChannelMapper.java

/**
 * 根据售货机编号和货道编号查询货道信息
 * @param innerCode
 * @param channelCode
 * @return 售货机货道信息
 */
@Select("select * from dkd_channel where inner_code=#{innerCode} and channel_code=#{channelCode}")
Channel selectChannelByInnerCodeAndChannelCode(@Param("innerCode") String innerCode,@Param("channelCode") String channelCode);

/**
 * 批量更新货道信息
 * @param channelList
 * @return
 */
int batchUpdateChannel(List<Channel> channelList);

mapper的xml,dkd-manage/src/main/resources/mapper/manage/ChannelMapper.xml

<update id="batchUpdateChannel" parameterType="java.util.List">
    <foreach collection="list" item="channel" separator=";">
        update tb_channel
        <trim prefix="SET" suffixOverrides=",">
            <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>
        </trim>
        where id = #{channel.id}
    </foreach>
</update>

service层dkd-manage/src/main/java/com/dkd/manage/service/IChannelService.java

/**
 * 设置售货机货道信息
 * @param channelConfigDto
 * @return
 */
int setChannelConfig(ChannelConfigDto channelConfigDto);

service的实现dkd-manage/src/main/java/com/dkd/manage/service/impl/ChannelServiceImpl.java

@Override
public int setChannelConfig(ChannelConfigDto channelConfigDto) {
    // 1、将dto转为po对象
    List<Channel> channelList = channelConfigDto.getChannelSkuDtoList().stream().map(channelSkuDto -> {
        Channel channel = channelMapper.selectChannelByInnerCodeAndChannelCode(channelSkuDto.getInnerCode(), channelSkuDto.getChannelCode());
        if (channel != null) {
            channel.setSkuId(channelSkuDto.getSkuId());
            channel.setUpdateTime(DateUtils.getNowDate());
        }
        return channel;
    }).collect(Collectors.toList());
    // 2、完成修改
    return channelMapper.batchUpdateChannel(channelList);
}

controller层 dkd-manage/src/main/java/com/dkd/manage/controller/ChannelController.java

    /**
     * 设置货道配置
     */
    @PreAuthorize("@ss.hasPermi('manage:channel:edit')")
    @Log(title = "售货机货道", businessType = BusinessType.UPDATE)
    @PutMapping("/config")
    public AjaxResult setChannelConfig(@RequestBody ChannelConfigDto channelConfigDto) {
//        System.out.println("-------------------");
//        System.out.println("channelConfigDto:"+channelConfigDto.toString());
        return toAjax(channelService.setChannelConfig(channelConfigDto));
    }

10、工单管理

需求分析:

库表设计

10.1、基础框架搭建

目标状态:

10.1.1、目录配置

10.1.2、数据字典创建

10.1.3、代码生成

先导入

task表:

有现成的代码,不需要修改,只要把两个字典配置上就行。

task_details

task_type

job

生成代码后,只把main的拷贝进去

前端代码把资料里的拷贝进去

10.1.4、配置代码

对拷贝进去的前端代码创建路由

由于工单表中用了desc作为字段,但是这个字段是数据库的保留字段,所以在mapper的使用中需要对字段加上反引号。

    <sql id="selectTaskVo">
        select task_id, task_code, task_status, create_type, inner_code, user_id, user_name, region_id, `desc`, product_type_id, assignor_id, addr, create_time, update_time from tb_task
    </sql>

10.2、功能优化

10.2.1、工单查询优化

需要创建一个TaskVo,把TaskType包含进去。

先创建TaskVo,dkd-manage/src/main/java/com/dkd/manage/domain/vo/TaskVo.java

package com.dkd.manage.domain.vo;

import com.dkd.manage.domain.Task;
import com.dkd.manage.domain.TaskType;
import lombok.Data;

@Data
public class TaskVo extends Task {

    private TaskType taskType;
}

再去mapper里面创建方法,dkd-manage/src/main/java/com/dkd/manage/mapper/TaskMapper.java

    /***
     * 查询工单管理列表
     * @param task
     * @return List<TaskVo>
     */
    public List<TaskVo> selectTaskListVo(Task task);

配置mapper的xml

    <resultMap type="TaskVo" id="TaskVoResult">
        <result property="taskId"    column="task_id"    />
        <result property="taskCode"    column="task_code"    />
        <result property="taskStatus"    column="task_status"    />
        <result property="createType"    column="create_type"    />
        <result property="innerCode"    column="inner_code"    />
        <result property="userId"    column="user_id"    />
        <result property="userName"    column="user_name"    />
        <result property="regionId"    column="region_id"    />
        <result property="desc"    column="desc"    />
        <result property="productTypeId"    column="product_type_id"    />
        <result property="assignorId"    column="assignor_id"    />
        <result property="addr"    column="addr"    />
        <result property="createTime"    column="create_time"    />
        <result property="updateTime"    column="update_time"    />
        <association property="taskType" javaType="TaskType" column="product_type_id" select="com.dkd.manage.mapper.TaskTypeMapper.selectTaskTypeByTypeId" />
    </resultMap>


    <select id="selectTaskListVo" resultMap="TaskVoResult">
        <include refid="selectTaskVo"/>
        <where>
            <if test="taskCode != null  and taskCode != ''"> and task_code = #{taskCode}</if>
            <if test="taskStatus != null "> and task_status = #{taskStatus}</if>
            <if test="createType != null "> and create_type = #{createType}</if>
            <if test="innerCode != null  and innerCode != ''"> and inner_code = #{innerCode}</if>
            <if test="userId != null "> and user_id = #{userId}</if>
            <if test="userName != null  and userName != ''"> and user_name like concat('%', #{userName}, '%')</if>
            <if test="regionId != null "> and region_id = #{regionId}</if>
            <if test="desc != null  and desc != ''"> and `desc` = #{desc}</if>
            <if test="productTypeId != null "> and product_type_id = #{productTypeId}</if>
            <if test="assignorId != null "> and assignor_id = #{assignorId}</if>
            <if test="addr != null  and addr != ''"> and addr = #{addr}</if>
            <if test="params.isRepair != null and params.isRepair == 'true'">
                 and product_type_id in (1,3,4)
            </if>
            <if test="params.isRepair != null and params.isRepair == 'false'">
                 and product_type_id = 2
            </if>
        </where>
    </select>

创建service的接口,dkd-manage/src/main/java/com/dkd/manage/service/ITaskService.java

/***
 * 查询工单管理列表
 * @param task
 * @return List<TaskVo>
 */
public List<TaskVo> selectTaskListVo(Task task);

再去实现这个service接口dkd-manage/src/main/java/com/dkd/manage/service/impl/TaskServiceImpl.java

/***
 * 查询工单管理列表
 * @param task
 * @return List<TaskVo>
 */
@Override
public List<TaskVo> selectTaskListVo(Task task) {
    return taskMapper.selectTaskListVo(task);
}

最后去controller调用接口,接口信息:/manage/task/list GET

/**
 * 查询工单管理列表
 */
@PreAuthorize("@ss.hasPermi('manage:task:list')")
@GetMapping("/list")
public TableDataInfo list(Task task)
{
    startPage();
    //List<Task> list = taskService.selectTaskList(task);
    List<TaskVo> taskVos = taskService.selectTaskListVo(task);
    return getDataTable(taskVos);
}

10.2.2、获取人员

根据设备获取区域,根据区域获取人员。

先搞一个接口,实现的是根据内码去查询设备。

先写mapper,dkd-manage/src/main/java/com/dkd/manage/mapper/VendingMachineMapper.java

/**
 * 根据内部编号查询设备
 * @param innerCode
 * @return VendingMachine
 */
public VendingMachine selectVendingMachineByInnerCode(String innerCode);

再去写mapper的xml,dkd-manage/src/main/resources/mapper/manage/VendingMachineMapper.xml

<select id="selectVendingMachineByInnerCode" resultType="com.dkd.manage.domain.VendingMachine">
    <include refid="selectVendingMachineVo"/>
    <where>
        <if test="innerCode != null  and innerCode != ''"> inner_code = #{innerCode}</if>
    </where>
</select>

再去写service,dkd-manage/src/main/java/com/dkd/manage/service/IVendingMachineService.java

/**
 * 根据内部编号查询设备
 * @param innerCode
 * @return VendingMachine
 */
public VendingMachine selectVendingMachineByInnerCode(String innerCode);

然后再看前端调用的接口

去emp的controller去实现接口,先查询到售货机,在根据售货机的信息去查询人员列表。

/**
 * 根据售货机获取人员列表
 */
@PreAuthorize("@ss.hasPermi('manage:emp:list')")
@GetMapping("/businessList/{innerCode}")
public AjaxResult businessList(@PathVariable("innerCode") String innerCode) {
    // 1、根据内码获取售货机
    VendingMachine vendingMachine = vendingMachineService.selectVendingMachineByInnerCode(innerCode);
    if (vendingMachine == null) {
        return AjaxResult.error("售货机不存在");
    }
    // 2、根据售货机获取人员列表
    Emp emp = new Emp();
    emp.setRegionId(vendingMachine.getRegionId());
    emp.setRoleCode(DkdContants.ROLE_CODE_OPERATOR);
    emp.setStatus(DkdContants.EMP_STATUS_NORMAL);
    List<Emp> list = empService.selectEmpList(emp);
    return success(list);
}

获取运维人员也是一样的,改个参数就行了。

/**
 * 根据售货机获取人员列表
 */
@PreAuthorize("@ss.hasPermi('manage:emp:list')")
@GetMapping("/operationList/{innerCode}")
public AjaxResult operationList(@PathVariable("innerCode") String innerCode) {
    // 1、根据内码获取售货机
    VendingMachine vendingMachine = vendingMachineService.selectVendingMachineByInnerCode(innerCode);
    if (vendingMachine == null) {
        return AjaxResult.error("售货机不存在");
    }
    // 2、根据售货机获取人员列表
    Emp emp = new Emp();
    emp.setRegionId(vendingMachine.getRegionId());
    emp.setRoleCode(DkdContants.ROLE_CODE_OPERATOR);
    emp.setStatus(DkdContants.EMP_STATUS_NORMAL);
    List<Emp> list = empService.selectEmpList(emp);
    return success(list);
}

10.2.3、创建工单

相当于是要把task表和task_details整合成一个vo对象。

先创建一个TaskDetailsDto,dkd-manage/src/main/java/com/dkd/manage/domain/dto/TaskDetailsDto.java

package com.dkd.manage.domain.dto;

import lombok.Data;

@Data
public class TaskDetailsDto {
    private String channelCode; //货道编号
    private Long expectCapacity; //补货数量
    private Long skuld; //商品id
    private String skuName; //商品名称
    private String skulmage; //商品图片
}

再创建dkd-manage/src/main/java/com/dkd/manage/domain/dto/TaskDto.java

package com.dkd.manage.domain.dto;

import lombok.Data;

import java.util.List;

@Data
public class TaskDto {
    private Long createType;
    private String innerCode;
    private Long userId;
    private Long assignorId;
    private Long productTypeId;
    private String desc;
    private List<TaskDetailsDto> details;
}

先写工单详情的批量插入,先写mapper的xml

<insert id="insertTaskDetailsBatch" parameterType="java.util.List" useGeneratedKeys="true" keyProperty="detailsId">
    insert into tb_task_details
    <trim prefix="(" suffix=")" suffixOverrides=",">
        task_id,
        channel_code,
        expect_capacity,
        sku_id,
        sku_name,
        sku_image
    </trim>
    values
    <foreach collection="list" item="taskDetail" separator=",">
        (
            #{taskDetail.taskId},
            #{taskDetail.channelCode},
            #{taskDetail.expectCapacity},
            #{taskDetail.skuId},
            #{taskDetail.skuName},
            #{taskDetail.skuImage}
        )
    </foreach>
</insert>

再去补全mapper,dkd-manage/src/main/java/com/dkd/manage/mapper/TaskDetailsMapper.java

/***
 * 批量新增工单详情
 * @param taskDetails
 * @return List<TaskDetails>
 */
public int insertTaskDetailsBatch(List<TaskDetails> taskDetails);

再去写service的接口和实现。dkd-manage/src/main/java/com/dkd/manage/service/ITaskDetailsService.java

/***
 * 批量新增工单详情
 * @param taskDetails
 * @return List<TaskDetails>
 */
public int insertTaskDetailsBatch(List<TaskDetails> taskDetails);

service实现,dkd-manage/src/main/java/com/dkd/manage/service/impl/TaskDetailsServiceImpl.java

/***
 * 批量新增工单详情
 * @param taskDetails
 * @return
 */
@Override
public int insertTaskDetailsBatch(List<TaskDetails> taskDetails) {
    return taskDetailsMapper.insertTaskDetailsBatch(taskDetails);
}

然后开始写task工单的新增,先去taskservice写,dkd-manage/src/main/java/com/dkd/manage/service/ITaskService.java

要注意把前端传过来的dto封装到po给后台的时候,有些字段需要补全.

@Autowired
private IVendingMachineService vendingMachineService;

@Autowired
private IEmpService empService;

@Autowired
private RedisTemplate redisTemplate;

@Autowired
private ITaskDetailsService taskDetailsService;

   @Override
    @Transactional
    public int insertTaskDto(TaskDto taskDto) {
        // 1、校验设备存在
        VendingMachine vendingMachine = vendingMachineService.selectVendingMachineByInnerCode(taskDto.getInnerCode());
        if (vendingMachine == null) {
            throw new ServiceException("设备不存在");
        }
        // 2、校验设备状态和工单状态是否符合
        // 如果是投放工单,设备在运行中,抛出异常
        // 如果是补货工单,设备不在运行中,抛出异常
        // 如果是维修工单,设备不在运行中,抛出异常
        // 如果是撤销工单,设备不在运行中,抛出异常
        checkVmStatus(vendingMachine.getVmStatus(), taskDto.getProductTypeId());

        // 3、检查是否有未完成的同类型工单
        hastask(taskDto);

        // 4、校验员工是否存在
        Emp emp = empService.selectEmpById(taskDto.getUserId());
        if (emp == null) {
            throw new ServiceException("员工不存在");
        }

        // 5、校验员工区域是否符合
        if (!emp.getRegionId().equals(vendingMachine.getRegionId())) {
            throw new ServiceException("员工区域不符合");
        }

        // 6、保存工单
        Task task = BeanUtil.copyProperties(taskDto, Task.class); //先复制相同的属性
        //工单状态
        task.setTaskStatus(DkdContants.TASK_STATUS_CREATE);
        //执行人名称
        task.setUserName(emp.getUserName());
        //区域id
        task.setRegionId(vendingMachine.getRegionId());
        //地址
        task.setAddr(vendingMachine.getAddr());
        //创建时间
        task.setCreateTime(DateUtils.getNowDate());
        //工单编号:日期+每日自增
        //task.setTaskCode(DateUtils.dateTimeNow(DateUtils.YYYYMMDDHHMMSS) + "0001");
        task.setTaskCode(generateTaskCode());

        //7、判断是否是补货工单
        if (taskDto.getProductTypeId().equals(DkdContants.TASK_TYPE_SUPPLY)) {
            //工单详情
            List<TaskDetailsDto> taskDtoDetails = taskDto.getDetails();
            //判断是否为空
            if (taskDtoDetails == null || taskDtoDetails.isEmpty()) {
                throw new ServiceException("补货工单详情不能为空");
            } else {
                // 把详情复制到工单详情
                List<TaskDetails> collect = taskDtoDetails.stream().map(taskDetailsDto -> {
                    TaskDetails taskDetails = BeanUtil.copyProperties(taskDetailsDto, TaskDetails.class);
                    taskDetails.setTaskId(task.getTaskId());
                    return taskDetails;
                }).collect(Collectors.toList());

                //批量插入工单详情
                taskDetailsService.insertTaskDetailsBatch(collect);
            }
        }


        //保存工单
        int i = taskMapper.insertTask(task);

        return i;
    }

    private String generateTaskCode() {
        // 获取当前日期
        String dateStr = DateUtils.getDate().replaceAll("-","");
        //设置rediskey的名称
        String key = "dkd:task:code:" + dateStr;
        //判断key是否存在
        if (!redisTemplate.hasKey(key)) {
            //不存在,给key赋值0001
            redisTemplate.opsForValue().set(key,1, Duration.ofDays(1));
            //返回日期+0001
            return dateStr + "0001";
        }
        //存在,获取key的值,并自增1
        String code = redisTemplate.opsForValue().increment(key).toString();

        return dateStr+ StrUtil.padPre(code,4,'0');
    }

    /***
     * 检查是否有未完成的同类型工单
     * @param taskDto
     */
    private void hastask(TaskDto taskDto) {
        Task task = new Task();
        task.setInnerCode(taskDto.getInnerCode());
        task.setProductTypeId(taskDto.getProductTypeId());
        task.setTaskStatus(DkdContants.TASK_STATUS_PROGRESS);
        List<Task> tasks = taskMapper.selectTaskList(task);
        if (tasks != null || tasks.size()>0) {
            throw new ServiceException("有未完成的工单");
        }
    }

    public void checkVmStatus(Long vmStatus, Long taskType){
        // 2、校验设备状态和工单状态是否符合
        // 如果是投放工单,设备在运行中,抛出异常
        if (vmStatus.equals(DkdContants.VM_STATUS_RUNNING) && taskType.equals(DkdContants.TASK_TYPE_DEPLOY)) {
            throw new ServiceException("设备在运行中,不能进行投放工单");
        }
        // 如果是补货工单,设备不在运行中,抛出异常
        if (!vmStatus.equals(DkdContants.VM_STATUS_RUNNING) && taskType.equals(DkdContants.TASK_TYPE_SUPPLY)) {
            throw new ServiceException("设备不在运行中,不能进行补货工单");
        }
        // 如果是维修工单,设备不在运行中,抛出异常
        if (!vmStatus.equals(DkdContants.VM_STATUS_RUNNING) && taskType.equals(DkdContants.TASK_TYPE_REPAIR)) {
            throw new ServiceException("设备不在运行中,不能进行维修工单");
        }
        // 如果是撤销工单,设备不在运行中,抛出异常
        if (!vmStatus.equals(DkdContants.VM_STATUS_RUNNING) && taskType.equals(DkdContants.TASK_TYPE_REVOKE)) {
            throw new ServiceException("设备不在运行中,不能进行撤销工单");
        }
    }

改造一下controller,dkd-manage/src/main/java/com/dkd/manage/controller/TaskController.java

/**
 * 新增工单管理
 */
@PreAuthorize("@ss.hasPermi('manage:task:add')")
@Log(title = "工单管理", businessType = BusinessType.INSERT)
@PostMapping
public AjaxResult add(@RequestBody TaskDto taskDto)
{
    // 设置创建人
    taskDto.setAssignorId(getUserId());
    return toAjax(taskService.insertTaskDto(taskDto));
}

10.2.4、取消工单

先确定一下流程

接口信息:

路径:/manage/task/cancel

方法:PUT

参数:{taskId: 543, desc: "后台工作人员取消"}

找到taskController,dkd-manage/src/main/java/com/dkd/manage/controller/TaskController.java

/***
 * 取消工单
 * @param task
 * @return
 */
@PreAuthorize("@ss.hasPermi('manage:task:edit')")
@Log(title = "工单管理", businessType = BusinessType.UPDATE)
@PutMapping("/cancel")
public AjaxResult cancel(@RequestBody Task task)
{

    return toAjax(taskService.cancelTask(task));
}

再去写service和实现,dkd-manage/src/main/java/com/dkd/manage/service/ITaskService.java

/***
 * 取消工单管理
 * @param task
 */
public int cancelTask(Task task);

dkd-manage/src/main/java/com/dkd/manage/service/impl/TaskServiceImpl.java

/***
 * 取消工单
 * @param task
 */
@Override
public int cancelTask(Task task) {
    // 1、先获取任务单的状态
    Task task1 = taskMapper.selectTaskByTaskId(task.getTaskId());
    // 如果任务单的状态是取消,则抛出异常
    if (task1.getTaskStatus().equals(DkdContants.TASK_STATUS_CANCEL)) {
        throw new ServiceException("工单已取消");
    }
    //如果任务单的状态是已完成,则抛出异常
    if (task1.getTaskStatus().equals(DkdContants.TASK_STATUS_FINISH)) {
        throw new ServiceException("工单已完成");
    }

        task.setTaskStatus(DkdContants.TASK_STATUS_CANCEL);
        task.setUpdateTime(DateUtils.getNowDate());


    int i = taskMapper.updateTask(task);
    return i;
}

10.2.4、查询工单详情

接口描述:

路径:/manage/taskDetails/byTaskId/:taskId

方法:GET

参数:taskId

返回:详情数组

直接去详情的controller,dkd-manage/src/main/java/com/dkd/manage/controller/TaskDetailsController.java

/***
 * 根据工单id查询工单详情
 * @param taskId
 * @return
 */
@GetMapping("/byTaskId/{taskId}")
public AjaxResult getInfoByTaskId(@PathVariable("taskId") Long taskId)
{
    TaskDetails taskDetails = new TaskDetails();
    taskDetails.setTaskId(taskId);
    return success(taskDetailsService.selectTaskDetailsList(taskDetails));
}

11、整合knife4j用于接口文档

11.1、引入依赖

来到common的pom文件,dkd-common/pom.xml

<!-- knife4j   -->
<dependency>
    <groupId>com.github.xiaoymin</groupId>
    <artifactId>knife4j-spring-boot-starter</artifactId>
    <version>3.0.3</version>
</dependency>

找到前端页面的swagger配置,src\views\tool\swagger\index.vue

<template>
   <i-frame v-model:src="url"></i-frame>
</template>

<script setup>
import iFrame from '@/components/iFrame'

const url = ref(import.meta.env.VITE_APP_BASE_API + "/doc.html")
</script>

在方法上加上@Api,在接口上加上@ApiOperation

@Api(tags = "工单管理")
@RestController
@RequestMapping("/manage/task")
public class TaskController extends BaseController
{
    @Autowired
    private ITaskService taskService;

    /**
     * 查询工单管理列表
     */
    @ApiOperation(value = "查询工单管理列表")
    @PreAuthorize("@ss.hasPermi('manage:task:list')")
    @GetMapping("/list")
    public TableDataInfo list(Task task)
    {
        startPage();
        //List<Task> list = taskService.selectTaskList(task);
        List<TaskVo> taskVos = taskService.selectTaskListVo(task);
        return getDataTable(taskVos);
    }
......

全局配置类:dkd-admin/src/main/java/com/dkd/web/core/config/SwaggerConfig.java

可以修改接口文档的清单等