一. 需求说明
设备管理主要涉及到三个功能模块,业务流程如下:
-
新增设备类型: 允许管理员定义新的售货机型号,包括其规格和容量。
-
新增设备: 在新的设备类型定义后,系统应允许添加新的售货机实例,并将它们分配到特定的点位。
-
新增货道: 对于每个新添加的设备,系统应支持定义新的货道,后期用于关联相应的商品SKU。
graph TD A[登录系统] A --> B[新增设备类型] B --> C[新增设备] C --> D[新增货道]
对于设备和其他管理数据,下面是示意图:
-
关系字段:vm_type_id、node_id、vm_id
-
数据字典:vm_status(0未投放、1运营、3撤机)
-
冗余字段:addr、business_type、region_id、partner_id(简化查询接口、提高查询效率)
二. 生成基础代码
2.1 需求
使用若依代码生成器,生成设备类型、设备、货道前后端基础代码,并导入到项目中
2.2 步骤
2.2.1 创建目录菜单
2.2.2 添加数据字典
2.2.3 配置代码生成信息
导入表
设备类型代码配置
设备代码设置
售货机管道代码改造,售货机没有特有的页面原型,只在设备管理中有货道设置页面,所以需要修改字段信息,默认就行
2.2.4 下载代码并导入项目
执行sql
前后端代码
重启项目,查看增删改查有没有问题
这里的设备照片不显示很正常,因为我们的阿里云OSS没有设备照片😂修改完就可以显示照片了
三. 设备类型改造
参考页面原型,完成基础布局展示改造
Element Plus官网:一个 Vue 3 UI 框架 | Element Plus
实现
在vmType/index.vue视图组件中修改
<!-- 查询条件 -->
<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"/>行
<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 基础页面改造
4.1.1 需求
参考页面原型,完成基础布局展示改造
4.1.2 实现
-
刷新设备表数据,使设备的详细地址和点位地址对应好
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;
-
在vm/index.vue视图组件中修改
-
修改合作商和设备状态的展示方式
使用后端进行搜索查询返回的话会很麻烦,所以这里可以让后端查询所有的合作商和设备状态返回前端进行存储,使用v-for进行遍历,v-if进行判断,如果查询到的id等于当前行id的话,就拿到名称返回到页面进行展示
-
在vm/index.vue视图组件中修改
parseTime(form.lastSupplyTime, '{y}-{m}-{d} {h}:{i}:{s}') 可以修改日期展示格式
<!-- 查询条件 -->
<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-input v-model="form.vmTypeId" placeholder="请输入设备型号" /> -->
<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-input v-model="form.nodeId" placeholder="请输入点位Id" /> -->
<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>
4.2 新增设备改造
4.2.1 需求
新增设备时,补充设备表其他字段信息,还需要根据售货机类型创建所属货道
我们了解到在新增设备时,添加设备和货道表,还包含点位和设备类型的查询,共涉及到四张表的操作。
这个过程需要我们仔细处理每个字段,确保数据的一致性和完整性
4.2.2 实现
-
VendingMachineServiceImpl
@Autowired
private INodeService nodeService;
@Autowired
private IVmTypeService vmTypeService;
@Autowired
private IChannelService channelService;
/**
* 新增设备管理
*
* @param vendingMachine 设备管理
* @return 结果
*/
@Transactional
@Override
public int insertVendingMachine(VendingMachine vendingMachine) {
//1. 新增设备
//1-1 生成8位编号,补充货道编号
String innerCode = UUIDUtils.getUUID();
vendingMachine.setInnerCode(innerCode); // 售货机编号
//1-2 查询售货机类型表,补充设备容量
VmType vmType = vmTypeService.selectVmTypeById(vendingMachine.getVmTypeId());
vendingMachine.setChannelMaxCapacity(vmType.getChannelMaxCapacity());
//1-3 查询点位表,补充 区域、点位、合作商等信息
Node node = nodeService.selectNodeById(vendingMachine.getNodeId());
BeanUtil.copyProperties(node, vendingMachine, "id");
vendingMachine.setAddr(node.getAddress());
//1-4 设备状态
vendingMachine.setVmStatus(DkdContants.VM_STATUS_NODEPLOY);// 0-未投放(数据库有默认值,这个不写也不影响)
vendingMachine.setCreateTime(DateUtils.getNowDate());// 创建时间
vendingMachine.setUpdateTime(DateUtils.getNowDate());// 更新时间
//1-5 保存
int result = vendingMachineMapper.insertVendingMachine(vendingMachine);
//2. 新增货道
//2-1 声明货道集合
List<Channel> channelList = new ArrayList<>();
//2-2 双层for循环
for (int i = 1; i <= vmType.getVmRow(); i++) { // 外层行
for (int j = 1; j <= vmType.getVmCol(); j++) {// 内层列
//2-3 封装channel
Channel channel = new Channel();
channel.setChannelCode(i + "-" + j);// 货道编号
channel.setVmId(vendingMachine.getId());// 售货机id
channel.setInnerCode(vendingMachine.getInnerCode());// 售货机编号
channel.setMaxCapacity(vmType.getChannelMaxCapacity());// 货道最大容量
channel.setCreateTime(DateUtils.getNowDate());// 创建时间
channel.setUpdateTime(DateUtils.getNowDate());// 更新时间
channelList.add(channel);
}
}
//2-4 批量新增
channelService.batchInsertChannel(channelList);
return result;
}
-
ChannelMapper接口
/**
* 批量新增售货机货道
* @param channelList
* @return 结果
*/
public int batchInsertChannel(List<Channel> channelList);
ChannelMapper.xml
<insert id="batchInsertChannel" parameterType="java.util.List">
INSERT INTO tb_channel (
channel_code, vm_id, inner_code, max_capacity, current_capacity, last_supply_time, create_time, update_time
) VALUES
<foreach collection="list" item="channel" separator=",">
(
#{channel.channelCode},
#{channel.vmId},
#{channel.innerCode},
#{channel.maxCapacity},
#{channel.currentCapacity},
#{channel.lastSupplyTime},
#{channel.createTime},
#{channel.updateTime}
)
</foreach>
</insert>
IChannelService
/**
* 批量新增售货机货道
* @param channelList
* @return 结果
*/
public int batchInsertChannel(List<Channel> channelList);
IChannelServiceImpl实现类
/**
* 批量新增售货机货道
* @param channelList
* @return 结果
*/
@Override
public int batchInsertChannel(List<Channel> channelList) {
return channelMapper.batchInsertChannel(channelList);
}
4.3 修改设备改造
4.3.1 需求
修改设备时,根据点位同步更新冗余字段信息
根据前端提交的点位ID,后端需要查询点位表,来获取点位的详细信息,包括详细地址、商圈类型、区域ID和合作商ID,获取到点位信息后,我们需要更新设备表中的相关冗余字段。
4.3.2 实现
-
VendingMachineServiceImpl实现类
/**
* 修改设备管理
*
* @param vendingMachine 设备管理
* @return 结果
*/
@Override
public int updateVendingMachine(VendingMachine vendingMachine)
{
// 查询点位表,补充:区域、点位、合作商等信息
Node node = nodeService.selectNodeById(vendingMachine.getNodeId());
BeanUtil.copyProperties(node,vendingMachine,"id"); // 商圈类型、区域、合作商
vendingMachine.setAddr(node.getAddress()); // 设备地址
vendingMachine.setUpdateTime(DateUtils.getNowDate()); // 更新时间
return vendingMachineMapper.updateVendingMachine(vendingMachine);
}
五. 设备状态改造
5.1 需求
-
为设备状态管理功能创建前端页面,并在若依框架中定义相应的路由和菜单项
-
基于原型完成视图组件基础布局展示改造
5.2 实现
-
在前端manage视图新建vmStatus页面
-
添加二级菜单
-
改造视图组件
<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="left" 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="vmStatus">
<template #default="scope">
<!-- <span v-if="scope.row.runningStatus != null">
{{ JSON.parse(scope.row.runningStatus).status==true ? '正常' : '异常' }}
</span>
<span v-else>异常</span> -->
{{ scope.row.runningStatus != null?JSON.parse(scope.row.runningStatus).status==true ? '正常' : '异常': '异常'}}
</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-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 { loadAllParams } from '@/api/page';
import { listNode } from '@/api/manage/node';
import { listRegion } from "@/api/manage/region";
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,
channelMaxCapacity: 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;
});
}
// ------------------------------------
/* 查询设备类型列表 */
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();
//-----------------------------------------
// 取消按钮
function cancel() {
open.value = false;
reset();
}
// 表单重置
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 handleAdd() {
reset();
open.value = true;
title.value = "添加设备管理";
}
/** 修改按钮操作 */
function getVmInfo(row) {
reset();
const _id = row.id || ids.value
getVm(_id).then(response => {
form.value = response.data;
open.value = true;
title.value = "设备详情";
});
}
/** 提交按钮 */
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;
getList();
});
} else {
addVm(form.value).then(response => {
proxy.$modal.msgSuccess("新增成功");
open.value = false;
getList();
});
}
}
});
}
/** 删除按钮操作 */
function handleDelete(row) {
const _ids = row.id || ids.value;
proxy.$modal.confirm('是否确认删除设备管理编号为"' + _ids + '"的数据项?').then(function() {
return delVm(_ids);
}).then(() => {
getList();
proxy.$modal.msgSuccess("删除成功");
}).catch(() => {});
}
/** 导出按钮操作 */
function handleExport() {
proxy.download('manage/vm/export', {
...queryParams.value
}, `vm_${new Date().getTime()}.xlsx`)
}
getList();
</script>
效果如下✌
六. 点位查看详情
6.1 需求
在点位管理点击查看详情时,可以在对话框查看设备的信息
6.2 实现
-
在node/index.vue视图组件中修改
<el-button link type="primary" @click="getNodeInfo(scope.row)" v-hasPermi="['manage:vm:list']">查看详情</el-button>
<!-- 点位详情对话框 -->
<el-dialog title="点位详情" v-model="nodeOpen" 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">
<!-- parseTime 是一个函数,用于格式化时间戳 -->
<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";
const { proxy } = getCurrentInstance();
/* 引入设备状态数据字典 */
const { vm_status } = proxy.useDict('vm_status');
/* 查看详情 */
const nodeOpen = ref(false);
const vmList = ref([]);
function getNodeInfo(row) {
// 根据点位id,查询设备列表
loadAllParams.nodeId = row.id;
listVm(loadAllParams).then(response => {
vmList.value = response.rows;
nodeOpen.value = true;
});
}
</script>
注意:在查看详情定义了v-hasPermi,这里的权限对应的是想要实现功能的权限字符,如下图
效果如下✌