【若依RuoYi-Vue | 项目实战】帝可得后台管理系统(二)

文章目录


接着上回的【若依RuoYi-Vue | 项目实战】基于若依的帝可得后台管理系统(一),本次我们继续完成人员管理、设备管理、策略管理模块的开发。

页面原型参考:帝可得(智能货柜)页面原型

一、人员管理

1、需求说明

人员管理业务流程如下:

  1. 登录系统: 首先,后台管理人员需要登录到帝可得后台管理系统中。
  2. 新增工作人员: 登录系统后,管理人员可以新增工作人员,包括姓名、联系方式等信息。
  3. 关联角色: 确定此员工是运维人员还是运营人员,这将影响他们的职责和权限。
  4. 关联区域: 确定员工负责的区域范围,确保工作人员能够高效地完成区域内的设备安装、维修、商品补货等工作。

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

  • 关系字段:role_id、region_id
  • 数据字典:status(1启用、0停用)
  • 冗余字段:region_name、role_code、role_name

关于冗余字段的设计

  • 优点:减少关联查询,现在只需要单表查询,提升了查询速度,简化查询逻辑。在读写分离架构时,冗余字段可以降低对主表的依赖,增加了读写操作的灵活性。
  • 缺点:增加了数据维护的复杂性,占用了额外的存储空间,空间换时间。

2、生成基础代码

需求:使用若依代码生成器,生成人员列表前后端基础代码,并导入到项目中

(1)创建目录菜单

创建人员管理目录菜单

(2)添加数据字典

先创建员工状态的字典类型

再创建员工状态的字典数据

(3)配置代码生成信息

导入tb_emp、tb_role两张表

配置员工表(参考原型)

配置角色表(无原型无页面,只需要所生成的role.js和后端项目文件)

(4)下载代码并导入项目

选中二张表生成下载,解压ruoyi.zip得到前后端代码和动态菜单sql

注意:角色动态菜单sql和视图组件不需要导入

前后端代码导入

对于无页面原型的角色表,需要导入所生成的role.js和后端项目文件(仅提供前后端api支持),是因为员工表的前端请求页面,在新增和修改的表单里需要查询角色名称列表下拉框。

3、人员列表改造

(1)基础页面

  • 需求:参考页面原型,完成基础布局展示改造
  • 代码实现

在emp/index.vue视图组件中修改

html 复制代码
<!-- 搜索区域 -->
<el-form :model="queryParams" ref="queryRef" :inline="true" v-show="showSearch" label-width="68px">
    <el-form-item label="人员名称" prop="userName">
        <el-input v-model="queryParams.userName" placeholder="请输入人员名称" clearable @keyup.enter="handleQuery" />
    </el-form-item>
    <el-form-item>
        <el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
        <el-button icon="Refresh" @click="resetQuery">重置</el-button>
    </el-form-item>
</el-form>

<!-- 人员列表 -->
<el-table v-loading="loading" :data="empList" @selection-change="handleSelectionChange">
    <el-table-column type="selection" width="55" align="center" />
    <el-table-column label="序号" type="index" width="80" align="center" prop="id" />
    <el-table-column label="人员名称" align="center" prop="userName" />
    <el-table-column label="归属区域" align="center" prop="regionName" />
    <el-table-column label="角色" align="center" prop="roleName" />
    <el-table-column label="联系电话" align="center" prop="mobile" />
    <el-table-column label="操作" align="center" class-name="small-padding fixed-width">
        <template #default="scope">
			<el-button link type="primary"  @click="handleUpdate(scope.row)" v-hasPermi="['manage:emp:edit']">修改</el-button>
			<el-button link type="primary"  @click="handleDelete(scope.row)" v-hasPermi="['manage:emp:remove']">删除</el-button>
        </template>
    </el-table-column>
</el-table>

<!-- 添加或修改人员列表对话框 -->
<el-dialog :title="title" v-model="open" width="500px" append-to-body>
  <el-form ref="empRef" :model="form" :rules="rules" label-width="80px">
    <el-form-item label="员工名称" prop="userName">
      <el-input v-model="form.userName" placeholder="请输入员工名称" />
    </el-form-item>
    <el-form-item label="角色" prop="roleId">
      <!-- <el-input v-model="form.roleId" placeholder="请输入角色id" /> -->
      <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>
    <el-form-item label="联系电话" prop="mobile">
      <el-input v-model="form.mobile" placeholder="请输入联系电话" />
    </el-form-item>
    <el-form-item label="创建时间" prop="createTime" v-if="form.id!=null">
      {{form.createTime }}
    </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-select>
    </el-form-item>
    <el-form-item label="员工头像" prop="image">
      <image-upload v-model="form.image" />
    </el-form-item>
    <el-form-item label="是否启用" prop="status">
      <el-radio-group v-model="form.status">
        <el-radio v-for="dict in emp_status" :key="dict.value"
          :label="parseInt(dict.value)">{{ dict.label }}</el-radio>
      </el-radio-group>
    </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>

<script>
    import { listRegion } from "@/api/manage/region";
    import { listRole } from "@/api/manage/role";
    import { loadAllParams } from "@/api/page";

    // 查询角色列表
    const roleList = ref([]);
    function getRoleList() {
        listRole(loadAllParams).then(response => {
            roleList.value = response.rows;
        });
    }
    // 查询区域列表
    const regionList = ref([]);
    function getRegionList() {
        listRegion(loadAllParams).then(response => {
            regionList.value = response.rows;
        });
    }
    getRegionList();
    getRoleList();
</script>

因为前端新增和修改的表单提交的是角色id和区域id,还需要后端通过这两个id查询数据库相关字段,在EmpServiceImpl中新增和修改时,补充区域名称和角色信息给员工表的冗余字段。

java 复制代码
@Autowired
private RegionMapper regionMapper;

@Autowired
private RoleMapper roleMapper;

/**
 * 新增人员列表
 *
 * @param emp 人员列表
 * @return 结果
 */
@Override
public int insertEmp(Emp emp)
{
    // 补充区域名称
    emp.setRegionName(regionMapper.selectRegionById(emp.getRegionId()).getRegionName());
    // 补充角色信息
    Role role = roleMapper.selectRoleByRoleId(emp.getRoleId());
    emp.setRoleName(role.getRoleName());
    emp.setRoleCode(role.getRoleCode());
    emp.setCreateTime(DateUtils.getNowDate());
    return empMapper.insertEmp(emp);
}

/**
 * 修改人员列表
 *
 * @param emp 人员列表
 * @return 结果
 */
@Override
public int updateEmp(Emp emp)
{
    // 补充区域名称
    emp.setRegionName(regionMapper.selectRegionById(emp.getRegionId()).getRegionName());
    // 补充角色信息
    Role role = roleMapper.selectRoleByRoleId(emp.getRoleId());
    emp.setRoleName(role.getRoleName());
    emp.setRoleCode(role.getRoleCode());
    emp.setUpdateTime(DateUtils.getNowDate());
    return empMapper.updateEmp(emp);
}
  • 测试新增和修改

(2)同步存储

  • 问题演示:

当我们在修改区域管理中修改某个区域的名称时,区域表可以单表更新。

但是员工表中的区域显示没有发生改变,并没有同步更新。

  • 此问题的解决方案:

同步存储: 在员工表中有区域名称的冗余字段,在更新区域表的同时,同步更新员工表中区域名称。

  • 优点:由于是单表查询操作,查询列表效率最高。
  • 缺点:需要在区域修改时修改员工表中的数据,有额外的开销,数据也可能不一致。
  • 这种同步存储适合读多写少的业务场景,不适合修改频繁的场景。
  • SQL
sql 复制代码
-- 根据区域id修改区域名称
update tb_emp set region_name='北京市奥体中心' where region_id=4;
  • EmpMapper
sql 复制代码
/**
 * 根据区域id修改员工表的区域名称(同步更新)
 * @param regionName
 * @param regionId
 * @return
 */
@Update("update tb_emp set region_name = #{regionName} where region_id = #{regionId}")
int updateEmpByRegionId(@Param("regionName") String regionName, @Param("regionId") Long regionId);
  • RegionServiceImpl
java 复制代码
@Autowired
private EmpMapper empMapper;

/**
 * 修改区域管理
 *
 * @param region 区域管理
 * @return 结果
 */
@Transactional(rollbackFor = Exception.class)
@Override
public int updateRegion(Region region)
{
    // 先更新区域信息
    region.setUpdateTime(DateUtils.getNowDate());
    int result = regionMapper.updateRegion(region);

    // 同步更新员工表区域名称
    empMapper.updateByRegionId(region.getRegionName(),region.getId());
    return result;
}
  • 保持同步更新测试

4、文件存储

(1)本地存储

问题分析说明:

在若依框架目前的实现中,默认是把图片存储到了服务器本地的目录,通过服务进行访问。这样做存储的是比较省事,但是缺点也有很多:

  • 硬件与网络要求:服务器通常需要高性能的硬件和稳定的网络环境,以保证文件传输的效率和稳定性。这可能会增加硬件和网络资源的成本和维护难度。
  • 管理难度:服务器目录需要管理员进行配置和管理,包括权限设置、备份策略等。如果管理不善或配置不当,可能会引发一些安全问题和性能问题。
  • 性能瓶颈:如果服务器处理能力不足或网络带宽不够,可能会导致性能瓶颈,影响文件上传、下载和访问的速度。
  • 单点故障风险:服务器故障可能导致所有存储在其上的文件无法访问,尽管可以通过备份和冗余措施来降低这种风险,但单点故障的风险仍然存在。
  • 本地存储:无法直接访问、磁盘空间限制、磁盘损坏等。

为了解决上述问题,通常有两种解决方案:

  • 自己搭建存储服务器,如:fastDFS 、MinIO
  • 使用现成的云服务,如:阿里云,腾讯云,华为云

(2)阿里云OSS

  • 介绍

阿里云对象存储OSS(Object Storage Service),是一款海量、安全、低成本、高可靠的云存储服务。使用OSS,您可以通过网络随时存储和调用包括文本、图片、音频和视频等在内的各种文件。

在我们使用了阿里云OSS对象存储服务之后,我们的项目当中如果涉及到文件上传这样的业务,在前端进行文件上传并请求到服务端时,在服务器本地磁盘当中就不需要再来存储文件了。我们直接将接收到的文件上传到oss,由oss帮我们存储和管理,同时阿里云的oss存储服务还保障了我们所存储内容的安全可靠。

第三方服务使用的通用思路,我们做一个简单介绍之后,接下来我们就来介绍一下我们当前要使用的阿里云oss对象存储服务具体的使用步骤。

Bucket:存储空间是用户用于存储对象(Object,就是文件)的容器,所有的对象都必须隶属于某个存储空间。

SDK:Software Development Kit 的缩写,软件开发工具包,包括辅助软件开发的依赖(jar包)、代码示例等,都可以叫做SDK。

简单说,sdk中包含了我们使用第三方云服务时所需要的依赖,以及一些示例代码。我们可以参照sdk所提供的示例代码就可以完成入门程序。

阿里云OSS具体使用教程请参考:【服务集成】最新版 | 阿里云OSS对象存储服务使用教程(包含OSS工具类优化、自定义阿里云OSS服务starter)

(3)x-file-storage

  • 介绍

官方地址:https://x-file-storage.xuyanwu.cn/#/

一行代码将文件存储到本地、FTP、SFTP、WebDAV、阿里云 OSS、华为云 OBS、七牛云 Kodo、腾讯云 COS、百度云 BOS、又拍云 USS、MinIO、 Amazon S3、GoogleCloud Storage、FastDFS、 Azure Blob Storage、Cloudflare R2、金山云 KS3、美团云 MSS、京东云 OSS、天翼云 OOS、移动 云EOS、沃云 OSS、 网易数帆 NOS、Ucloud US3、青云 QingStor、平安云 OBS、首云 OSS、IBM COS、其它兼容 S3 协议的存储平台。

  • 集成
  1. 在dkd-common模块的pom.xml中引入依赖
xml 复制代码
<!-- x-file-storage 统一文件上传管理器 -->
<dependency>
    <groupId>org.dromara.x-file-storage</groupId>
    <artifactId>x-file-storage-spring</artifactId>
    <version>2.2.1</version>
</dependency>
<!-- 阿里云OSS -->
<dependency>
    <groupId>com.aliyun.oss</groupId>
    <artifactId>aliyun-sdk-oss</artifactId>
    <version>3.17.4</version>
</dependency>
<!-- Java 9及以上的版本还需引入以下三个依赖 -->
<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>
  1. 在dkd-admin的 application.yml 配置文件中先添加以下基础配置,再添加对应平台的配置
yaml 复制代码
# 文件上传
dromara:
  x-file-storage: # 文件存储配置
    default-platform: aliyun-oss-1 # 默认使用的存储平台
    thumbnail-suffix: ".min.jpg" # 缩略图后缀,例如【.min.jpg】【.png】
    # 对应平台的配置写在这里,注意缩进要对齐
    aliyun-oss: # 阿里云OSS配置
      - platform: aliyun-oss-1 # 存储平台标识
        enable-storage: true  # 启用存储
        access-key: ??
        secret-key: ??
        end-point: oss-cn-beijing.aliyuncs.com # 不需要带https://
        bucket-name: oss-bucket-web
        domain: https://oss-bucket-web.oss-cn-beijing.aliyuncs.com/ # 访问域名,注意"/"结尾,例如:https://abc.oss-cn-shanghai.aliyuncs.com/
        base-path: dkd/dkd-images/ # 基础路径
  1. 在dkd-admin的启动类上加上 @EnableFileStorage 注解
java 复制代码
@EnableFileStorage
@SpringBootApplication(exclude = { DataSourceAutoConfiguration.class })
public class DkdApplication {
    public static void main(String[] args) {
        // System.setProperty("spring.devtools.restart.enabled", "false");
        SpringApplication.run(DkdApplication.class, args);
        System.out.println("(♥◠‿◠)ノ゙  帝可得启动成功   ლ(´ڡ`ლ)゙");
    }
}
  1. 修改若依默认上传图片代码

若依前端上传图片发送的请求路径为:http://localhost/dev-api/common/upload,路由转发到后端就是http://localhost:8080/common/upload

找到 ruoyi-admin 模块中的com.ruoyi.web.controller.common.CommonController类,修改单个文件上传的方法,将本地文件上传的代码改为由x-file-storage管理的阿里云OSS文件上传:

java 复制代码
@Autowired
private FileStorageService fileStorageService;// 注入实列

/**
  * 通用上传请求(单个)
*/
@PostMapping("/upload")
public AjaxResult uploadFile(MultipartFile file) throws Exception {

    try {
        // 指定oss保存文件路径 dkd/dkd-images/2024/06/19/文件名
        String relativePath = LocalDate.now().format(DateTimeFormatter.ofPattern("yyyy/MM/dd")) + "/";
        // 上传图片,成功返回文件信息
        FileInfo fileInfo = fileStorageService.of(file).setPath(relativePath).upload();
        // 设置返回结果
        AjaxResult ajax = AjaxResult.success();
        ajax.put("url", fileInfo.getUrl());
        ajax.put("fileName", fileInfo.getUrl());    // 这里的值要改为ur1,前端访问的地址,需要文件的地址,而不是文件名称
        ajax.put("newFileName", FileUtils.getName(fileInfo.getUrl()));
        ajax.put("originalFilename", file.getOriginalFilename());
        return ajax;
    } catch (Exception e) {
        return AjaxResult.error(e.getMessage());
    }
}

这里说明一下fileName,若依原本上传图片时会为fileName前面拼接http://localhost/dev-api/profile/upload/。

针对前端图片回显问题,我们之前在前端代码中增加了对http的判断,如果响应的fileName中包含http的话,就不做拼接,直接返回图片路径URL。

  1. 联调测试,重启后台程序,前端上传图片操作,F12抓包工具查看效果:

成功将项目中图片上传从本地存储改为了阿里云OSS文件存储。


二、设备管理

1、需求说明

业务场景:管理员在系统录入设备信息后,员工将负责设备(智能售货机)的投放和商品补货工作。

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

  1. 新增设备类型: 允许管理员定义新的售货机型号,包括其规格和容量。
  2. 新增设备: 在新的设备类型定义后,系统应允许添加新的售货机实例,并将它们分配到特定的点位。
  3. 新增货道: 对于每个新添加的设备,系统应支持定义新的货道,后期用于关联相应的商品SKU。

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

  • 关系字段:vm_type_id、node_id、vm_id
  • 数据字典:vm_status(0未投放、1运营、3撤机)
  • 冗余字段:addr、business_type、region_id、partner_id(简化查询接口、提高查询效率)

2、生成基础代码

  • 需求:使用若依代码生成器,生成设备类型、设备、货道前后端基础代码,并导入到项目中:

(1)创建目录菜单

创建设备管理目录菜单

(2)添加数据字典

先创建 设备状态 的字典类型

再创建 设备状态的字典数据

(3)配置代码生成信息

导入三张表

  • 配置设备类型表(参考原型)
  • 配置设备表(参考原型)
  • 配置货道表(无原型)


(4)下载代码并导入项目

选中三张表生成下载

注意:货道动态菜单sql和前端不需要导入

调整动态菜单的显示顺序(设备状态模块稍后完成)

前端代码导入(由于货道表没有页面原型,因此我们只需要导入它的后端代码,实现业务逻辑的查询即可)

后端代码导入

3、设备类型改造

(1)基础页面

  • 需求:参考页面原型,完成基础布局展示改造

设备类型管理列表

新增和修改共用一个表单框

  • 代码实现

在vmType/index.vue视图组件中修改

html 复制代码
<!-- 查询条件 -->
<el-form :model="queryParams" ref="queryRef" :inline="true" v-show="showSearch" label-width="68px">
    <el-form-item label="型号名称" prop="name">
        <el-input  v-model="queryParams.name" placeholder="请输入型号名称" clearable  @keyup.enter="handleQuery"/>
    </el-form-item>
    <el-form-item>
        <el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
        <el-button icon="Refresh" @click="resetQuery">重置</el-button>
    </el-form-item>
</el-form>

<!-- 列表展示 -->
<el-table v-loading="loading" :data="vmTypeList" @selection-change="handleSelectionChange">
    <el-table-column type="selection" width="55" align="center" />
    <el-table-column label="型号名称" align="center" prop="name" />
    <el-table-column label="型号编码" align="center" prop="model" />
    <el-table-column label="设备图片" align="center" prop="image" width="100">
        <template #default="scope">
<image-preview :src="scope.row.image" :width="50" :height="50"/>
        </template>
    </el-table-column>
    <el-table-column label="货道行" align="center" prop="vmRow" />
    <el-table-column label="货道列" align="center" prop="vmCol" />
    <el-table-column label="设备容量" align="center" prop="channelMaxCapacity" />
    <el-table-column label="操作" align="center" class-name="small-padding fixed-width">
        <template #default="scope">
		<el-button link type="primary" @click="handleUpdate(scope.row)" v-hasPermi="['manage:vmType:edit']">修改</el-button>
		<el-button link type="primary" @click="handleDelete(scope.row)" v-hasPermi="['manage:vmType:remove']">删除</el-button>
        </template>
    </el-table-column>
</el-table>

<!-- 添加或修改设备类型管理对话框 -->
<el-dialog :title="title" v-model="open" width="500px" append-to-body>
    <el-form ref="vmTypeRef" :model="form" :rules="rules" label-width="80px">
        <el-form-item label="型号名称" prop="name">
            <el-input v-model="form.name" placeholder="请输入型号名称" />
        </el-form-item>
        <el-form-item label="型号编码" prop="model">
            <el-input v-model="form.model" placeholder="请输入型号编码" />
        </el-form-item>
        <el-form-item label="货道数" prop="vmRow">
            <el-input-number v-model="form.vmRow" placeholder="请输入"  :min="1" :max="10"/>行&nbsp;&nbsp;
            <el-input-number v-model="form.vmCol" placeholder="请输入"  :min="1" :max="10"/>列
        </el-form-item>
        <el-form-item label="货道容量" prop="channelMaxCapacity">
            <el-input-number v-model="form.channelMaxCapacity" placeholder="请输入"  :min="1" :max="10"/>个
        </el-form-item>
        <el-form-item label="设备图片" prop="image">
            <image-upload v-model="form.image"/>
        </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>
  • 测试和修改新增设备类型
  • 设备类型管理列表

4、设备管理改造

(1)基础页面

  • 需求:参考页面原型,完成基础布局展示改造

设备管理列表

新增设备

设备修改

货道设置(这个页面等后期写完商品管理模块再完善)

  • 代码实现

将设备表中的测试数据更新,保证和点位表中的详细地址是一致的。

sql 复制代码
update tb_vending_machine set partner_id=2 where id=80;
update tb_vending_machine set addr=(select address from tb_node where id = 1) where node_id=1;
update tb_vending_machine set addr=(select address from tb_node where id = 2) where node_id=2;
  • 优化查询方案

通过页面原型我们可以观察到,列表界面需要查询设备型号(设备类型表tb_vending_machine)、合作商(合作商表tb_partner),新增和修改界面需要回显点位名称(点位表tb_node)、所属区域(区域表tb_region)。如果要保证数据实时性,都需要进行多表查询。

而在之前我们针对多表查询这种业务使用的是MyBatis嵌套查询(将多表查询中的联合查询语句拆成单个表的查询)设置数据库冗余字段(空间换时间的单表查询,需要考虑数据同步存储)两种方案进行查询优化,降低数据库的访问压力。

这里提供第三种简化多表查询的解决方案,就是前端模拟数据字典的套路。因为数据量不大,我们可以在前端预加载设备类型表、点位表、区域表、合作商表,将所有数据存储到各表的List。由于设备表中设有外键vm_type_id和node_id,还存在冗余字段region_id、partner_id,可以配合使用
遍历前端预加载好的List 和 判断各表List的id和外键id是否相同。

在数据列表中使用template模板插槽,从scope作用域获取行对象row的外键id;

在新增或修改的表单中通过form.属性名,获取外键id。

通过预加载这样的方式,我们就能将多表的关联查询,优化成单表查询,减少了笛卡尔积,提高了查询效率。

在vm/index.vue视图组件中修改

html 复制代码
<!-- 查询条件 -->
<el-form :model="queryParams" ref="queryRef" :inline="true" v-show="showSearch" label-width="68px">
    <el-form-item label="设备编号" prop="innerCode">
        <el-input v-model="queryParams.innerCode" placeholder="请输入设备编号" clearable @keyup.enter="handleQuery" />
    </el-form-item>
    <el-form-item>
        <el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
        <el-button icon="Refresh" @click="resetQuery">重置</el-button>
    </el-form-item>
</el-form>

<!-- 列表展示 -->
<el-table v-loading="loading" :data="vmList" @selection-change="handleSelectionChange">
  <el-table-column type="selection" width="55" align="center" />
  <el-table-column label="设备编号" align="center" prop="innerCode" />
  <el-table-column label="设备型号" align="center" prop="vmTypeId" >
    <template #default="scope">
      <div v-for="item in vmTypeList" :key="item.id">
        <span v-if="item.id == scope.row.vmTypeId">{{ item.name }}</span>
      </div>
    </template>
  </el-table-column>
  <el-table-column label="详细地址" align="center" prop="addr" />
  <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="vmStatus">
    <template #default="scope">
      <dict-tag :options="vm_status" :value="scope.row.vmStatus" />
    </template>
  </el-table-column>
  <el-table-column label="操作" align="center" class-name="small-padding fixed-width">
    <template #default="scope">
      <el-button link type="primary" @click="handleUpdate(scope.row)" v-hasPermi="['manage:vm:edit']">修改</el-button>
    </template>
  </el-table-column>
</el-table>

<!-- 添加或修改设备管理对话框 -->
<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="设备编号">
      <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}:{i}:{s}') }}</span>
    </el-form-item>
    <el-form-item label="设备类型" v-if="form.innerCode != null">
      <div v-for="item in vmTypeList" :key="item.id">
        <span v-if="form.vmTypeId == item.id">{{ item.name }}</span>
      </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="vmTypeId" v-if="form.innerCode == null">
      <el-select v-model="form.vmTypeId" placeholder="请选择设备型号" style="width: 100%">
        <el-option v-for="item in vmTypeList" :key="item.id" :label="item.name" :value="item.id" />
      </el-select>
    </el-form-item>
    <el-form-item label="选择点位" prop="nodeId">
      <el-select v-model="form.nodeId" placeholder="请选择点位" style="width: 100%">
        <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="合作商" v-if="form.innerCode != null">
      <div v-for="item in partnerList" :key="item.id">
        <span v-if="form.partnerId == item.id">{{ item.partnerName }}</span>
      </div>
    </el-form-item>
    <el-form-item label="所属区域" v-if="form.innerCode != null">
      <div v-for="item in regionList" :key="item.id">
        <span v-if="form.regionId == item.id">{{ item.regionName }}</span>
      </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>

<script setup name="Vm">
import { listVmType } from "@/api/manage/vmType";
import { listPartner } from "@/api/manage/partner";
import { loadAllParams } from '@/api/page';
import { listNode } from '@/api/manage/node';
import { listRegion } from "@/api/manage/region";
    
/* 查询设备类型列表 */
const vmTypeList = ref([]);
function getVmTypeList() {
  listVmType(loadAllParams).then((response) => {
    vmTypeList.value = response.rows;
  });
}
/* 查询合作商列表 */
const partnerList = ref([]);
function getPartnerList() {
  listPartner(loadAllParams).then((response) => {
    partnerList.value = response.rows;
  });
}
/* 查询点位列表 */
const nodeList = ref([]);
function getNodeList() {
  listNode(loadAllParams).then((response) => {
    nodeList.value = response.rows;
  });
}
/* 查询区域列表 */
const regionList = ref([]);
function getRegionList() {
  listRegion(loadAllParams).then((response) => {
    regionList.value = response.rows;
  });
}
getRegionList();
getPartnerList();
getNodeList();
getVmTypeList();
</script>

(2)新增设备

  • 需求:新增设备时,补充设备表其他字段信息,还需要根据售货机类型创建所属货道

我们了解到在新增设备时,添加设备和货道表,还包含点位和设备类型的查询,共涉及到四张表的操作。

这个过程需要我们仔细处理每个字段,对于新增时没有的属性字段,需要补充进去,确保数据的一致性和完整性。

  • VendingMachineServiceImpl
java 复制代码
@Autowired
private IVmTypeService vmTypeService;
@Autowired
private INodeService nodeService;
@Autowired
private IChannelService channelService;

/**
 * 新增设备管理
 * 
 * @param vendingMachine 设备管理
 * @return 结果
 */
@Transactional
@Override
public int insertVendingMachine(VendingMachine vendingMachine)
{
    // 新增设备
    // 生成8位的唯一标识,补充货道编号
    String innerCode = UUIDUtils.getUUID();
    vendingMachine.setInnerCode(innerCode);
    // 查询售货机类型表,补充设备容量
    VmType vmType = vmTypeService.selectVmTypeById(vendingMachine.getVmTypeId());
    vendingMachine.setChannelMaxCapacity(vmType.getChannelMaxCapacity());
    // 查询点位表,补充区域、点位、合作商等信息
    Node node = nodeService.selectNodeById(vendingMachine.getNodeId());
    // 将相同属性名称的属性值拷贝到目标对象中 copyProperties(源bean, 目的bean, 拷贝忽略字段)
    BeanUtil.copyProperties(node, vendingMachine, "id", "createTime", "updateTime");   // 拷贝商圈类型、区域id、合作商id
    vendingMachine.setAddr(node.getAddress());  // 设备详细地址
    // 设备状态
    vendingMachine.setVmStatus(DkdContants.VM_STATUS_NODEPLOY); // 0-未投放
    vendingMachine.setCreateTime(DateUtils.getNowDate());
    vendingMachine.setUpdateTime(DateUtils.getNowDate());
    int result = vendingMachineMapper.insertVendingMachine(vendingMachine);
    // 新增货道
    List<Channel> channelList = new ArrayList<>();
    for (int i = 1; i <= vmType.getVmRow(); i++) {
        for (int j = 1; j <= vmType.getVmCol(); j++) {
            Channel channel = new Channel();
            channel.setChannelCode(i + "-" + j);    // 货道编号
            channel.setVmId(vendingMachine.getId());  // 售货机id
            channel.setInnerCode(vendingMachine.getInnerCode());    // 售货机编号
            channel.setMaxCapacity(vendingMachine.getChannelMaxCapacity()); // 货道最大容量
            channel.setCreateTime(DateUtils.getNowDate());
            channel.setUpdateTime(DateUtils.getNowDate());
            channelList.add(channel);
        }
    }
    channelService.batchInsertChannels(channelList);    // 批量保存
    return result;
}
  • ChannelMapper接口和xml
xml 复制代码
/**
 * 批量新增售货机货道
 * @param channelList
 * @return 结果
 */
public int batchInsertChannel(List<Channel> channelList);

<insert id="batchInsertChannel" parameterType="java.util.List">
    INSERT INTO tb_channel (
    channel_code, vm_id, inner_code, max_capacity, last_supply_time, create_time, update_time
    ) VALUES
    <foreach collection="list" item="channel" separator=",">
        (
        #{channel.channelCode},
        #{channel.vmId},
        #{channel.innerCode},
        #{channel.maxCapacity},
        #{channel.lastSupplyTime},
        #{channel.createTime},
        #{channel.updateTime}
        )
    </foreach>
</insert>
  • IChannelService和ChannelServiceImpl
java 复制代码
/**
 * 批量新增售货机货道
 * @param channelList
 * @return 结果
 */
public int batchInsertChannel(List<Channel> channelList);

/**
 * 批量新增售货机货道
 * @param channelList
 * @return 结果
 */
@Override
public int batchInsertChannel(List<Channel> channelList) {
    return channelMapper.batchInsertChannel(channelList);
}
  • 测试新增设备功能

因为饮料机设备类型的货道数是5行6列,所以货道表中同步插入30条货道数据。

(3)修改设备

  • 需求:修改设备时,根据点位同步更新冗余字段信息

根据前端提交的点位ID,后端需要查询点位表,来获取点位的详细信息,包括详细地址、商圈类型、区域ID和合作商ID,获取到点位信息后,我们需要更新设备表中的相关冗余字段。

  • VendingMachineServiceImpl
java 复制代码
/**
 * 修改设备管理
 * 
 * @param vendingMachine 设备管理
 * @return 结果
 */
@Override
public int updateVendingMachine(VendingMachine vendingMachine)
{
    // 查询点位表,补充:区域、点位、合作商等信息
    Node node = nodeService.selectNodeById(vendingMachine.getNodeId());
    BeanUtil.copyProperties(node, vendingMachine, "id", "createTime");    // 商圈类型、区域、合作商
    vendingMachine.setAddr(node.getAddress());  // 设备地址
    vendingMachine.setUpdateTime(DateUtils.getNowDate());
    return vendingMachineMapper.updateVendingMachine(vendingMachine);
}

(4)设备状态改造

  • 页面原型

设备状态列表

查看设备详情

因为设备状态并没有单独创建一张表,只是作为设备表中的一个字段vm_status,所以没有让若依生成设备状态的前端代码。因此我们来学习一下在若依中如何自己定制化开发一个前端页面,并配置响应的菜单路径映射、vue路由等信息。

另外,对于查看设备详情界面展示的信息,都是需要智能售货机投入运营后产生的订单和工单统计的(后期工单管理写完后再进行完善,这里先搭一个基本框架),包括设备状态列表界面中的设备运行状态(running_status),是售货机自己通过物联网MQTT协议进行自我更新上传信息到数据库。

  • 创建视图组件

创建vmStatus/index.vue视图组件

  • 创建二级菜单
  • 上级菜单:设备管理

  • 菜单名称:设备状态

  • 路由地址:显示在url上的名称(http://localhost/vm/vmStatus),需要和视图组件产生一个映射关系。

  • 组件路径:设置前端组件的视图页面,也就是manage/vmStatus/index,默认映射views目录不用写,.vue后缀也不用填写。设置完后 路由地址 和 组件路径 就会产生映射关系,实现页面的跳转了。

  • 权限字符:查询的是设备列表,参考查询设备管理列表的权限字符 manage:vm:list

  • 改造视图组件

智能售货机运营后自我上传的设备运行状态running_status,存储到数据库是json格式,我们需要用status进行判断。

我们在前端列表展示这个字段需要使用JSON.parse()方法将字符串转为json对象,前端判断如下:

html 复制代码
<el-table-column label="设备状态" align="center" prop="runningStatus">
  <template #default="scope">
    <span v-if="scope.row.runningStatus != null">
      {{ JSON.parse(scope.row.runningStatus).status ? "正常" : "异常" }}
    </span>
    <span v-else>异常</span>
  </template>
</el-table-column>

完整代码:

html 复制代码
<template>
  <div class="app-container">
    <el-form :model="queryParams" ref="queryRef" :inline="true" v-show="showSearch" label-width="68px">
      <el-form-item label="设备编号" prop="innerCode">
        <el-input
          v-model="queryParams.innerCode"
          placeholder="请输入设备编号"
          clearable
          @keyup.enter="handleQuery"
        />
      </el-form-item>
      <el-form-item>
        <el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
        <el-button icon="Refresh" @click="resetQuery">重置</el-button>
      </el-form-item>
    </el-form>

    <el-table v-loading="loading" :data="vmList" @selection-change="handleSelectionChange">
      <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="vmTypeId">
        <template #default="scope">
          <div v-for="item in vmTypeList" :key="item.id">
            <span v-if="item.id == scope.row.vmTypeId">{{ item.name }}</span>
          </div>
        </template>
      </el-table-column>
      <el-table-column label="详细地址" align="center" prop="addr" show-overflow-tooltip="true"/>
      <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="runningStatus">
        <template #default="scope">
          <!-- <span v-if="scope.row.runningStatus != null">
            {{ JSON.parse(scope.row.runningStatus).status ? "正常" : "异常" }}
          </span>
          <span v-else>异常</span> -->
          {{ scope.row.runningStatus != null ? JSON.parse(scope.row.runningStatus).status ? "正常" : "异常" : "异常" }}
        </template>
      </el-table-column>
      <el-table-column label="操作" align="center" class-name="small-padding fixed-width">
        <template #default="scope">
          <el-button link type="primary" @click="getVmInfo(scope.row)" v-hasPermi="['manage:vm:query']">查看详情</el-button>
        </template>
      </el-table-column>
    </el-table>
    
    <pagination
      v-show="total>0"
      :total="total"
      v-model:page="queryParams.pageNum"
      v-model:limit="queryParams.pageSize"
      @pagination="getList"
    />

    <!-- 添加或修改设备管理对话框 -->
    <el-dialog :title="title" v-model="open" width="500px" append-to-body>
      <el-row>
        销售量:{{ 100 }}&nbsp;&nbsp;
        销售额:{{ 500 }}&nbsp;&nbsp;
        补货次数:{{ 10 }}次&nbsp;&nbsp;
        维修次数:{{ 3 }}次
      </el-row>
      <br/>
      <el-row>
        商品销量(月)
      </el-row>
    </el-dialog>
  </div>
</template>

<script setup name="Vm">
import { listVm, getVm, delVm, addVm, updateVm } from "@/api/manage/vm";
import { listVmType } from "@/api/manage/vmType";
import { listPartner } from "@/api/manage/partner";
import { listNode } from "@/api/manage/node";
import { listRegion } from '@/api/manage/region';
import { loadAllParams } from "@/api/page";

const { proxy } = getCurrentInstance();
const { vm_status } = proxy.useDict('vm_status');

const vmList = ref([]);
const open = ref(false);
const loading = ref(true);
const showSearch = ref(true);
const ids = ref([]);
const single = ref(true);
const multiple = ref(true);
const total = ref(0);
const title = ref("");

const data = reactive({
  form: {},
  queryParams: {
    pageNum: 1,
    pageSize: 10,
    innerCode: null,
    nodeId: null,
    businessType: null,
    regionId: null,
    partnerId: null,
    vmTypeId: null,
    vmStatus: null,
    runningStatus: null,
    policyId: null,
  },
  rules: {
    nodeId: [
      { required: true, message: "点位Id不能为空", trigger: "blur" }
    ],
    vmTypeId: [
      { required: true, message: "设备型号不能为空", trigger: "blur" }
    ],
  }
});

const { queryParams, form, rules } = toRefs(data);

/** 查询设备管理列表 */
function getList() {
  loading.value = true;
  listVm(queryParams.value).then(response => {
    vmList.value = response.rows;
    total.value = response.total;
    loading.value = false;
  });
}

// 表单重置
function reset() {
  form.value = {
    id: null,
    innerCode: null,
    channelMaxCapacity: null,
    nodeId: null,
    addr: null,
    lastSupplyTime: null,
    businessType: null,
    regionId: null,
    partnerId: null,
    vmTypeId: null,
    vmStatus: null,
    runningStatus: null,
    longitudes: null,
    latitude: null,
    clientId: null,
    policyId: null,
    createTime: null,
    updateTime: null
  };
  proxy.resetForm("vmRef");
}

/** 搜索按钮操作 */
function handleQuery() {
  queryParams.value.pageNum = 1;
  getList();
}

/** 重置按钮操作 */
function resetQuery() {
  proxy.resetForm("queryRef");
  handleQuery();
}

// 多选框选中数据
function handleSelectionChange(selection) {
  ids.value = selection.map(item => item.id);
  single.value = selection.length != 1;
  multiple.value = !selection.length;
}

/** 查看设备详情 */
function getVmInfo(row) {
  reset();
  const _id = row.id || ids.value
  getVm(_id).then(response => {
    form.value = response.data;
    open.value = true;
    title.value = "设备详情";
  });
}

/* 查询设备类型列表 */
const vmTypeList = ref([]);
function getVmTypeList() {
  listVmType(loadAllParams).then(response => {
    vmTypeList.value = response.rows;
  });
}

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

/* 查询点位列表 */
const nodeList = ref([]);
function getNodeList() {
  listNode(loadAllParams).then(response => {
    nodeList.value = response.rows;
  });
}

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

getVmTypeList();
getPartnerList();
getNodeList();
getRegionList();
getList();
</script>
  • 测试页面效果

(5)点位查看详情

  • 需求:点位管理页面中点击查看详情,需要显示当前点位下所有设备信息
  • 实现思路:在点位管理的页面中创建查看详情按钮,引入设备的api和设备的数据字典,查询所有设备信息,请求参数中多携带一个nodeId,通过后端动态SQL进行查询。

在node/index.vue视图组件中修改

html 复制代码
<el-button link type="primary" @click="getNodeInfo(scope.row)" v-hasPermi="['manage:vm:list']">查看详情</el-button>

<!-- 点位详情对话框 -->
<el-dialog title="点位详情" v-model="nodeInfoOpen" width="600px" append-to-body>
  <el-table :data="vmList">
    <el-table-column label="序号" type="index" width="80" align="center" prop="id" />
    <el-table-column label="设备编号" align="center" prop="innerCode" />
    <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" width="180">
      <template #default="scope">
        <span>{{ parseTime(scope.row.lastSupplyTime, '{y}-{m}-{d} {h}:{i}:{s}') }}</span>
      </template>
    </el-table-column>
  </el-table>
</el-dialog>


<script setup name="Node">
import { listVm } from "@/api/manage/vm";
import { loadAllParams } from "@/api/page";
/* 引入设备状态数据字典 */
const { vm_status } = proxy.useDict('vm_status');
    
/* 查看点位详情 */
const nodeInfoOpen = ref(false);
const vmList = ref([]);
function getNodeInfo(row) {
  // 根据点位id,查询设备列表
  loadAllParams.nodeId = row.id;  // 在查询条件loadAllParams中增加一个nodeId参数
  listVm(loadAllParams).then(response => {
    vmList.value = response.rows;
    nodeInfoOpen.value = true;
  });
}
</script>

三、策略管理

1、需求说明

业务场景:管理员在系统中可以对每一台设备设置一个固定折扣,用于营销作用。

策略管理主要涉及到二个功能模块,业务流程如下:

  1. 新增策略:允许管理员定义新的策略,包括策略的具体内容和参数(如折扣率)。
  2. 策略分配:将策略分配给一个或多个售货机。

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

  • 关系字段:policy_id

2、生成基础代码

  • 需求:使用若依代码生成器,生成策略管理前后端基础代码,并导入到项目中
  • 步骤

(1)创建目录菜单

创建策略管理目录菜单

(2)配置代码生成信息

导入并配置策略表(参考原型)

(3)下载代码并导入项目

选中策略表生成下载,解压 ruoyi.zip 得到前后端代码和动态菜单sql,完成sql脚本执行和前后端代码导入。导入过程和之前同理。

3、策略管理改造

(1)基础页面

  • 需求:参考页面原型,完成基础布局展示改造。

策略管理列表

新增策略(和修改策略共用一个表单)

  • 代码实现

在policy/index.vue视图组件中修改

html 复制代码
<!-- 列表展示 -->
<el-table v-loading="loading" :data="policyList" @selection-change="handleSelectionChange">
  <el-table-column type="selection" width="55" align="center" />
  <el-table-column label="序号" type="index" width="50" align="center" prop="policyId" />
  <el-table-column label="策略名称" align="center" prop="policyName" />
  <el-table-column label="策略方案" align="center" prop="discount" />
  <el-table-column label="创建时间" align="center" prop="createTime" width="180">
    <template #default="scope">
      <span>{{ parseTime(scope.row.createTime, '{y}-{m}-{d} {h}:{i}:{s}') }}</span>
    </template>
  </el-table-column>
  <el-table-column label="操作" align="center" class-name="small-padding fixed-width">
    <template #default="scope">
      <el-button link type="primary"  @click="handleUpdate(scope.row)" v-hasPermi="['manage:policy:edit']">修改</el-button>
      <el-button link type="primary"  @click="handleDelete(scope.row)" v-hasPermi="['manage:policy:remove']">删除</el-button>
    </template>
  </el-table-column>
</el-table>


<!-- 添加或修改策略管理对话框 -->
<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">
     <el-input-number :min="0" :max="100" :step="5" :precision="0" controls-position="right" v-model="form.discount" placeholder="请输入策略方案" /> &nbsp;%
    </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>
  • 测试新增和修改策略

(2)查看详情

需求:点击查看详情,展示策略名称和该策略下包含的设备列表。

  • 代码实现

在policy/index.vue视图组件中修改

html 复制代码
<el-button link type="primary"  @click="getPolicyInfo(scope.row)" v-hasPermi="['manage:vm:list']">查看详情</el-button>

<!-- 策略详情对话框 -->
<el-dialog v-model="policyOpen" title="策略详情" width="500px">
  <el-form-item label="策略名称" prop="policyName">
    <el-input v-model="form.policyName" placeholder="请输入策略名称" disabled />
  </el-form-item>
  <label>包含设备:</label>
  <el-table :data="vmList">
    <el-table-column label="序号" type="index" width="80" align="center" prop="id" />
    <el-table-column label="点位地址" align="left" prop="addr" show-overflow-tooltip />
    <el-table-column label="设备编号" align="center" prop="innerCode" />
  </el-table>
</el-dialog>

<script setup name="Policy">
import { listVm } from "@/api/manage/vm";
import { loadAllParams } from "@/api/page";
    
/* 查看策略详情 */
const policyOpen = ref(false);
const vmList = ref([]);
function getPolicyInfo(row) {
  // 1. 获取策略信息
  form.value = row;
  // 2. 根据策略id,查询设备列表
  loadAllParams.policyId = row.policyId;
  listVm(loadAllParams).then(response => {
    vmList.value = response.rows;
    policyOpen.value = true;
  });
}
</script>

4、设备策略分配

  • 需求:在设备管理页面中点击策略,对设备设置一个固定折扣,用于营销作用。

设备管理列表(页面中新增一个策略按钮)

策略管理

  • 代码实现

在vm/index.vue视图组件中修改

html 复制代码
<el-button link type="primary" @click="handleUpdatePolicy(scope.row)" v-hasPermi="['manage:vm:edit']">策略</el-button>

<!-- 策略管理对话框 -->
<el-dialog title="策略管理" v-model="policyOpen" width="500px" append-to-body>
  <el-form ref="vmRef" :model="form" label-width="80px">
    <el-form-item label="策略" prop="policyId">
      <el-select v-model="form.policyId" placeholder="请选择策略">
        <el-option v-for="item in policyList" :key="item.policyId" :label="item.policyName"
          :value="item.policyId"></el-option>
      </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>

<script setup name="Vm">
import { listPolicy } from '@/api/manage/policy';
    
// 取消按钮
function cancel() {
  open.value = false;
  policyOpen.value = false; // 关闭策略管理对话框
  reset();
}
    
/** 提交按钮 */
function submitForm() {
  proxy.$refs["vmRef"].validate(valid => {
    if (valid) {
      if (form.value.id != null) {
        updateVm(form.value).then(response => {
          proxy.$modal.msgSuccess("修改成功");
          open.value = false;
          policyOpen.value = false; // 关闭策略管理对话框
          getList();
        });
      } else {
        addVm(form.value).then(response => {
          proxy.$modal.msgSuccess("新增成功");
          open.value = false;
          getList();
        });
      }
    }
  });
}
    
/* 设备策略分配 */
const policyList = ref([]);
const policyOpen = ref(false);
function handleUpdatePolicy(row) {
  // 为表单提供设备id和策略id(减少一次后端查询开销)
  form.value.id = row.id; // 设备id作为更新条件
  form.value.policyId = row.policyId; // 策略id作为提交参数
  // 查询策略列表
  listPolicy(loadAllParams).then((response) => {
    policyList.value = response.rows;
    policyOpen.value = true;
  });
}
</script>
  • 修改VendingMachineServiceImpl的更新设备方法,加入更新策略的特判。
java 复制代码
/**
 * 修改设备管理
 *
 * @param vendingMachine 设备管理
 * @return 结果
 */
@Override
public int updateVendingMachine(VendingMachine vendingMachine) {
    // 仅当修改设备信息时做点位的更新,策略管理修改只提交设备id和策略id,不包含点位id,只需更新基本信息。
    if (vendingMachine.getNodeId() != null) {
        // 查询点位表,补充:区域、点位、合作商等信息
        Node node = nodeService.selectNodeById(vendingMachine.getNodeId());
        BeanUtil.copyProperties(node, vendingMachine, "id", "createTime");    // 商圈类型、区域、合作商
        vendingMachine.setAddr(node.getAddress());  // 设备地址
    }
    vendingMachine.setUpdateTime(DateUtils.getNowDate());
    return vendingMachineMapper.updateVendingMachine(vendingMachine);
}

到此,我们基于若依完成了帝可得的人员管理、设备管理、策略管理模块的功能开发与测试,并使用阿里云OSS服务配合x-file-storage改造了若依原有的文件上传功能。

下期我们将完成商品管理、工单管理、订单管理模块的开发,并使用运营管理App测试投放工单和补货工单,在设备屏幕端完成下单、支付出货流程。


相关推荐
2601_958352903 小时前
A-59U 仅 37mm 却集成 USB + 双波束?
前端·回声消除·语音模块·降噪处理·降噪消回音
智慧物业老杨5 小时前
物业数字化落地思考:真正的转型,是底层数据秩序的重构
java·大数据·人工智能·微服务·系统架构
上进小菜猪8 小时前
把 KES 集群交给 Kubernetes 的九十天:一个 DBA 的试用实录
后端
星空8 小时前
金蝶苍穹build.gradle配置
java·build.gradle
两点王爷9 小时前
PostgreSQL 常用 SQL 语句与 GIS 相关函数详解
数据库·后端
泯泷9 小时前
那段文字是谁删的?Yjs 14 正式版之前,一套删除归属方案的实现与边界
前端·javascript·算法
考虑考虑9 小时前
Springboot环境变量占位符语法
spring boot·后端·spring
步行cgn11 小时前
Spring 基于 XML 的自动装配:byName 详解
java·后端·spring
野槐11 小时前
前端项目优化(有了我,会发生...)
前端
大帅点兵12 小时前
Apache Ant 1.9.9 完整讲解
java