企业设备运行管理平台——项目实战
该项目为团队开发的基础项目,在此重点围绕设备台账信息管理、设备维修、故障预警等模块实现展开自己的项目分享以及整个功能的业务逻辑分析。
一、系统需求介绍
1.设备台账管理
实现对车间设备的数字化台帐管理功能,包括车间名称、设备类型(生产设备/其他设备)、设备编号、设备名称、设备型号、制造厂、生产日期、使用开始日期、维保到期、备注、传感器ID。新增、修改、查询、删除、下载模板、上传数据、导出数据功能。
2.设备保养管理
2.1 保养计划管理
实现对企业设备保养计划的管理。
1)选择设备,选择按期保养方式,设置该设备的保养周期X天,在保养到期前Y天提醒用户。X要大于Y,保养到期后,维护保养记录后,重新按照保养周期执行,未维护保养记录,一直提醒保养。注意:一个设备只能一种保养方式。
2)选择设备,选择按设备使用时长保养,使用时长达到X小时,就提示保养,使用时长计算方法:红灯+绿灯+黄灯时长总和。使用时长达到X小时后,维护保养记录后,重新计算时长执行预警;未维护保养记录,一直提醒保养。注意:一个设备只能一种保养方式。
2.2 保养预警管理
根据预警设置,显示保养到期预警的设备列表。列表信息:车间名称、设备编号、设备名称、设备型号、保养到期日期、预警时间、保养状态。提供添加保养记录快捷按钮,将待保养设备信息带过去。
2.3 维保到期管理
设备维保到期提醒列表
2.4 保养记录
添加保养预警的设备,维护保养日期、保养人。一个保养预警只能添加一个保养记录。维护好保养记录,更改养预警列表的状态,解除大屏预警提醒。
3.设备维修管理
3.1 设备维修申请单管理
可针对待维修设备故障,进行申请设备维修。
3.2 设备维修登记管理
登记每次设备检修情况(设备故障码、故障表现、维修方案、维修时间、维修人、联系方式)。
3.3 设备故障库管理
综合设备维修信息,进行数据汇总分析,通过关键字,搜索指定故障信息;
4.设备故障管理
4.1 红灯预警列表
所有设备红灯预警设备编号、设备名称、设备型号、红灯开始时间、红灯结束时间、红灯时长。时长显示X天X时X分X秒 ,当X为0时不用显示。
5.设备运行数据管理
5.1 设备运行状态管理
针对所有设备进行运行状态的管理,所有设备的编号、名称、红灯、绿灯、黄灯设备灯状态时长统计列表。
5.2 使用效率管理
展示所有设备使用平均,使用效率计算方法:绿灯使用时间/(绿灯+黄灯+红灯时间)列表包含:日期、设备编号、设备名称、当天使用效率、累计使用效率。
5.3 故障报警管理
红灯状态代表设备故障,所有设备故障时长统计,平均解决时长。列表包含:设备编号、设备名称、故障时长、平均解决时长,时长显示X天X时X分X秒 ,当X为0时不用显示。故障解决时长定义:红灯亮起到结束时间。
5.4 产量列表管理
历史每天设备产量统计列表。绿灯有规律的一亮一灭代表生产一个产品。列表:日期、设备编号、设备名称、当日产量、累计产量。
6.关注数据管理
用户账号关注的设备列表。用户账号、关注的设备编号、设备名称。新增关注、取消关注功能。
7.系统管理
7.1 账号管理
账号的新增、修改、启用、禁用功能设置。
7.2 角色管理
角色的增删改查功能。
7.3 部门管理
部门的增删改查功能。
二、系统开发的配置
1.开发环境版本:JDK 8
2.开发计算机系统:windows
3.开发工具:后端----------IDEA
前端----------VScode
数据库-------MySQL8.0
4.版本控制工具:git
5.框架使用:前端-----------vue+element ui+router等
后端-----------springboot+mybatis-plus+springmvc等
三、个人实现模块介绍
主要完成三个模块的功能实现:
设备台账管理 :台账包含了设备的全部基本信息,实现新增、查询、删除和修改功能。
设备故障预警管理 :记录亮红灯时产生的设备信息,故障预警由设备运行状态模块提供。故障预警模块主要实现多表分页查询以及维修功能。
维修记录管理 :故障预警列表里的设备进行维修后生成维修记录。该模块主要实现多表分页查询以及修改、删除功能。
用户关注管理 :不同的用户在登陆平台后可以对设备进行关注和取消关注,已经关注的设备不可以重复关注。
四、项目部分模块架构
1、数据库模块
需要四张表:设备台账信息表、设备故障预警表、设备维修记录表、用户关注表
设备台账信息表设计如下

故障预警表设计

维修记录表设计

用户关注表设计

2、后端模块


3、前端模块
前端涉及到七个页面:设备台账信息管理页面、故障预警管理页面、维修记录管理页面、添加和修改设备页面、维修页面、修改维修记录页面、用户关注列表页面

五、具体实现
通过.yml配置文件配置数据库连接信息

1、设备台账信息管理模块
设备信息列表效果展示

创建设备台账信息表的映射实体类DopDeviceEntity
创建数据层接口DopDeviceDao
创建xml文件DopDeviceDao
创建业务逻辑接口DopDeviceService
创建业务逻辑实现类DopDeviceImpl
创建前端页面展示数据封装类DopDeviceVo
创建控制类DopDeviceController
1.1 设备信息列表展示
使用DopDeviceVo目的是封装多表查询数据,方便展示在前端页面

设备信息表中的车间名称workshop字段使用了数据字典,对应数据字典中value字段


在DopDeviceDao.xml文件中编写多表查询sql语句,返回结果集类型为DopDeviceVo
多表连接时通过workshop字段和value字段相等建立两个表的连接
<select id="getAllDevice" resultType="com.wedu.modules.dop.entity.vo.DopDeviceVo">
SELECT dop.`id`,sys.`name` workshop,dop.`number`,dop.`type`,dop.`name`,dop.`model`,dop.`factory`,dop.`create_time`,dop.`update_time`
FROM dop_device dop JOIN sys_dict sys ON dop.`workshop`=sys.`value`
${ew.customSqlSegment}
</select>
AI写代码
在DopDeviceDao中写相应的方法,IPage 需要在dao层传入IPage的实现类Page对象,传入条件构造器封装好的查询条件
**${ew.customSqlSegment}自动将条件构造器中的条件转换拼接在sql语句后面**
@Mapper
public interface DopDeviceDao extends BaseMapper<DopDeviceEntity> {
//查所有
IPage<DopDeviceVo> getAllDevice(IPage<DopDeviceVo> page, @Param(Constants.WRAPPER) Wrapper<DopDeviceVo> queryWrapper);
}
AI写代码
在业务层接口DopDeviceService中编写方法,返回一个分页的实体类
PageUtils封装了查询数据的结果集,以及数据的总条数、总页数、每页显示条数、当前处于第几页等信息,响应时直接返回PageUtils的一个对象。
public interface DopDeviceService extends IService<DopDeviceEntity> {
//
PageUtils queryPage(Map<String, Object> params);
}
AI写代码
业务层实现类实现方法
IPage判断是否是查询操作,然后对自定义多表查询方法的结果集进行分页
自定义查询方法传入IPage的实现类Page对象和通过QueryWrapper封装的查询条件
这里通过code=workshop和type=2可以在数据字典表中查询到所有车间数据的字典项集合
相当于前端显示的时候,设备的车间名称直接拿的是数据字典中的name字段
@Service("sysDeviceService")
public class DopDeviceServiceImpl extends ServiceImpl<DopDeviceDao, DopDeviceEntity> implements DopDeviceService {
@Autowired
DopDeviceDao dopDeviceDao;
@Override
public PageUtils queryPage(Map<String, Object> params) {
String devicename= (String)params.get("name");
String factory= (String)params.get("factory");
IPage<DopDeviceVo> page = dopDeviceDao.getAllDevice(
new Query<DopDeviceVo>().getPage(params),
new QueryWrapper<DopDeviceVo>()
.like(StringUtils.isNotBlank(devicename), "dop.`name`", devicename)
.like(StringUtils.isNotBlank(factory),"dop.`factory`",factory)
.eq("dop.`delete_type`",0)
.eq("sys.`code`","workshop")
.eq("sys.`type`",2)
);
return new PageUtils(page);
}
AI写代码
DopDeviceController中接收前端页面发送的请求,调用业务层实现类的queryPage方法,响应结果
@RestController//@ResponseBody+@Controller
@RequestMapping("/dop/devices")
public class DopDeviceController extends AbstractController {
@Autowired
private DopDeviceService dopDeviceService;
@GetMapping("/list")
public R list(@RequestParam Map<String, Object> params){
PageUtils page= dopDeviceService.queryPage(params);
return R.ok().put("page", page);
}
}
AI写代码
前端代码部分展示
export default{
data(){
return{
deviceForm:{name:'',factory:''},//搜索框form表单数据
dataList:[],//表格数据
pageIndex: 1,//初始页面显示第一页
pageSize: 10,//每页显示的数据条数
totalPage: 0,//总页数
dataListLoading: false,//数据加载状态
dataListSelections: [],//复选框的数据
addOrUpdateVisible: false//为false时弹窗关闭
}
},
//引入外部组件
components: {
AddOrUpdate
},
created(){//钩子函数,页面加载时触发
this.getDataList()
},
methods:{
search() {
this.pageIndex = 1
this.getDataList()
},
// 获取数据列表
getDataList () {
this.dataListLoading = true
this.$http({
url: this.$http.adornUrl('/dop/devices/list'),
method: 'get',
//params请求参数
params: this.$http.adornParams({
'page': this.pageIndex,
'limit': this.pageSize,
'name': this.deviceForm.name,
'factory':this.deviceForm.factory
})
}).then(({data}) => {//.then((data)=>{ })里的data是指接口成功返回的数据,包含请求头,请求体,等信息
if (data && data.code === 0) {
this.dataList = data.page.list
this.totalPage = data.page.totalCount
} else {
this.dataList = []
this.totalPage = 0
}
this.dataListLoading = false
})
}
AI写代码
1.2 添加和修改功能
前端设计添加和修改使用同一个Dialog弹框
点击新增时判断有没有id传入,没有则代表是新增弹框,只涉及form表单提交
点击修改时判断有没有id传入,有则代表是修改弹框,涉及form表单提交和数据的回显
为了保证保养预警模块的功能闭环,在添加设备时提供了第一次的保养方式和时间

methods: {
//此id为device.vue中修改按钮获取到的当前行id
init (id) {
this.dataForm.id = id || 0
this.visible = true
this.$nextTick(() => {
this.$refs['dataForm'].resetFields()
if (this.dataForm.id) {
this.$http({
url:this.$http.adornUrl('/sys/dict/childList'),
method:'get',
params:this.$http.adornParams({
'code':'workshop'
})
}).then(({data})=>{
console.log(data)
if (data&&data.code===0) {
this.options=data.page.list
}else{
this.options=[]
}
})
this.$http({
url: this.$http.adornUrl(`/dop/devices/info/${this.dataForm.id}`),
method: 'get',
params: this.$http.adornParams()
}).then(({data}) => {
if (data && data.code === 0) {
this.dataForm.workshop = data.info.workshop + ""
this.dataForm.number = data.info.number
this.dataForm.type = data.info.type
this.dataForm.name = data.info.name
this.dataForm.model = data.info.model
this.dataForm.factory=data.info.factory
this.dataForm.startUsetime = data.info.startUsetime
this.dataForm.upkeep = data.info.upkeep
this.dataForm.upkeepExpire =data.info.upkeepExpire
this.dataForm.presetTime=data.info.presetTime
this.dataForm.remindTime=data.info.remindTime
}
})
}
})
},
// 表单提交
dataFormSubmit () {
//validate : 对整个表单进行校验的方法 valid : 每个必填表单项都提交为true,否则为false
this.$refs['dataForm'].validate((valid) => {
if (valid) {
this.$http({
url: this.$http.adornUrl(`/dop/devices/${!this.dataForm.id ? 'add' : 'update'}`),
method: 'post',
data: this.$http.adornData({
'id': this.dataForm.id || undefined,
'workshop': this.dataForm.workshop,
'number': this.dataForm.number,
'type': this.dataForm.type,
'name': this.dataForm.name,
'model': this.dataForm.model,
'factory': this.dataForm.factory,
'startUsetime': this.dataForm.startUsetime,
'upkeep': this.dataForm.upkeep
})
}).then(({data}) => {
if (data && data.code === 0) {
this.$message({
message: '操作成功',
type: 'success',
duration: 1500,
onClose: () => {
this.visible = false
this.$emit('refreshDataList')
}
})
} else {
this.$message.error(data.msg)
}
})
}
})
}
AI写代码
后端接收请求,直接调用mybatis-plus提供的新增和修改方法即可,响应结果
@PostMapping("/add")
public R add(@RequestBody DopDeviceEntity sysDeviceEntity){
sysDeviceEntity.setCreateBy(getUserId());
sysDeviceEntity.setCreateTime(new Date());
dopDeviceService.addDevice(sysDeviceEntity);
return R.ok();
}
@PostMapping("/update")
public R update(@RequestBody DopDeviceEntity sysDeviceEntity){
sysDeviceEntity.setUpdateBy(getUserId());
sysDeviceEntity.setUpdateTime(new Date());
dopDeviceService.updateDevice(sysDeviceEntity);
return R.ok();
}
AI写代码
1.3 删除功能
提供单个删除和批量删除,点击删除和批量删除弹出确定提示框
在设备信息列表第一列设置复选框,定义复选框数组变量和方法
点击【批量删除】触发多选的方法,将已选的复选框id给到复选框数组
// 删除
deleteHandle (id) {
var ids = id ? [id] : this.dataListSelections.map(item => {
return item.id
})
// 弹出框的两个按钮都能分别请求接口
this.$confirm(`确定对设备id=${ids.join(',')}进行${id ? '删除' : '批量删除'}操作?`, '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
this.$http({
url: this.$http.adornUrl('/dop/devices/delete'),
method: 'post',
data: this.$http.adornData(ids, false)
}).then(({data}) => {
if (data && data.code === 0) {
this.$message({
message: '操作成功',
type: 'success',
duration: 1500,
onClose: () => {
this.getDataList()
}
})
} else {
this.$message.error(data.msg)
}
})
}).catch(() => {})
}
AI写代码
后端接收请求,获取到设备id的数组,根据id遍历对设备进行逻辑删除,通过mybatis-plus提供的方法可以实现
//逻辑删除 0未删除 1已删除
@PostMapping("/delete")
public R delete(@RequestBody Long[] deviceIds){
dopDeviceService.logicDelete(deviceIds);
return R.ok();
}
AI写代码
2、设备故障预警管理模块
故障预警的产生是由设备运行状态模块提供的,因此不提供添加按钮,另外不提供修改和删除功能,为了形成功能闭环,在修改按钮里实现了四个逻辑。
首先设备运行状态表里status为2并且type=0的设备信息才会记录在这
status=2时代表红灯状态,type=0时代表设备的当前状态
也就是说故障预警里面记录的是当前亮红灯的设备

2.1 多表分页查询
这里的逻辑其实和设备信息列表一样
设计了一个DopWarnVo类,用于封装从不同表里拿的字段,从而显示给前端

数据层
@Mapper
public interface DopRepairWarnDao extends BaseMapper<DopDeviceEntity> {
//查询所有故障设备
IPage<RepairWarnVo> getAllWarn(IPage<RepairWarnVo> page, @Param(Constants.WRAPPER) Wrapper<RepairWarnVo> queryWrapper);
// IPage<RepairWarnVo> getAllWarn(IPage<RepairWarnVo> page);
//添加故障预警 点击红灯生成一条故障预警
}
AI写代码
xml文件
<!-- 多表模糊查询 ${ew.customSqlSegment}相当于占位符 用于自动拼接条件构造器中的多个条件-->
<select id="getAllWarn" resultType="com.wedu.modules.dop.entity.vo.RepairWarnVo">
SELECT distinct dop_repair_warning.`id`,sys_dict.`name` AS workshop,dop_device.`number`,dop_device.`name`,dop_device.`factory`
FROM dop_device JOIN dop_device_status ON dop_device.`id`=dop_device_status.`device_id`
JOIN dop_repair_warning ON dop_device.`id`=dop_repair_warning.`device_id`
JOIN sys_dict ON dop_device.`workshop`=sys_dict.`value`
AND sys_dict.`code`="workshop" AND sys_dict.`type`="2"
${ew.customSqlSegment}
</select>
AI写代码
业务层
@Override
public PageUtils queryPage(Map<String, Object> params) {
String name = (String) params.get("name");
//Ipage对查询结果进行分页
IPage<RepairWarnVo> page = dopRepairWarnDao.getAllWarn(
new Query<RepairWarnVo>().getPage(params), //根据传入参数 自定义sql查询所有数据
new QueryWrapper<RepairWarnVo>() //封装等值查询和模糊查询
.like(StringUtils.isNotBlank(name), "dop_device.`name`", name)
.eq("dop_device_status.`type`",2)
.eq("dop_device_status.`status`",0)
.eq("dop_repair_warning.`status`",0)
);
//返回封装好的 数据 以及 分页等信息
return new PageUtils(page);
}
AI写代码
控制层
@RestController//@ResponseBody+@Controller
@RequestMapping("/dop/repairwarns")
public class DopRepairWarnController extends AbstractController {
@Autowired
DopRepairWarnService dopRepairWarnService;
@GetMapping("/list")
public R list(@RequestParam Map<String, Object> params){
PageUtils page=dopRepairWarnService.queryPage(params);
return R.ok().put("page",page);
}
}
AI写代码
2.2 维修功能
点击维修按钮实现了四个逻辑:
1)将故障预警表中的状态修改为1,表示该预警已经处理了,不会显示在预警列表中了
2)添加一条维修记录到维修记录表中
3)将设备运行状态表中的该设备的type改为1,表示红灯状态结束,给一个结束时间
4)添加一条数据到设备运行状态表中,status为1,type=0,开始时间就是该设备上一个状态的结束时间,表示该设备结束了红灯状态变成了新的状态
后端开启事务,保证这四个逻辑同时成功或结束。
public interface DopRepairRecordService extends IService<DopRepairRecordEntity> {
//开启事务,保证故障预警解除和维修记录添加 同步完成或失败
@Transactional
public void addAndUpdateById(DopRepairRecordEntity dopRepairRecordEntity);
//多表分页查询
//接收参数,查询所有 分页
PageUtils queryPage(Map<String, Object> params);
//逻辑删除
public void logicDelete(Long ids[]);
//修改维修记录
public void updateRecord(DopRepairRecordEntity dopRepairRecordEntity);
}
AI写代码
业务层实现类实现方法,方法中调用四个方法逻辑
@Service("dopRepairRecordService")
public class DopRepairRecordServiceImpl extends ServiceImpl<DopRepairRecordDao, DopRepairRecordEntity> implements DopRepairRecordService {
@Autowired
DopRepairRecordDao dopRepairRecordDao;
//已在业务层接口开启事务
@Override
public void addAndUpdateById(DopRepairRecordEntity dopRepairRecordEntity) {
dopRepairRecordDao.addRecord(dopRepairRecordEntity);//添加保养记录
dopRepairRecordDao.updateStatusById(dopRepairRecordEntity);//修改预警中的状态为 1
dopRepairRecordDao.updateRunStatus(dopRepairRecordEntity);//修改 当前状态改为1
dopRepairRecordDao.insertRunStatus(dopRepairRecordEntity);//添加一条该设备的绿灯记录 初始状态为0 开始时间为上一次的结束时间
}
}
AI写代码
数据层提供这四个方法
@Mapper
public interface DopRepairRecordDao extends BaseMapper<DopRepairRecordEntity> {
//查询所有维修记录
IPage<RepairRecordVo> getAllRecord(IPage<RepairRecordVo> page, @Param(Constants.WRAPPER) Wrapper<RepairRecordVo> queryWrapper);
//添加维修记录
public void addRecord(DopRepairRecordEntity dopRepairRecordEntity);
//根据预警id修改维修预警状态
public void updateStatusById(DopRepairRecordEntity dopRepairRecordEntity);
//逻辑删除
public void logicDelete(Long ids[]);
// 修改设备运行状态里的 status和end_time 红灯状态结束
public void updateRunStatus(DopRepairRecordEntity dopRepairRecordEntity);
// 添加设备运行状态里的记录 修改type为绿灯或红灯 将上次状态的结束时间作为此次添加的开始时间 status=0
public void insertRunStatus(DopRepairRecordEntity dopRepairRecordEntity);
//修改维修记录
public void updateRecord(DopRepairRecordEntity dopRepairRecordEntity);
}
AI写代码
3、设备维修记录管理模块
维修记录由维修预警提供,这里不提供新增按钮,只有查询、修改和删除,形成功能闭环

3.1 列表展示
大致的实现和前面的多表分页查询思路一样
提供DopRepairRecordVo类,封装前端页面要展示的数据

这里展示的车间名称和故障码都使用了数据字典
xml文件
<!-- 多表查询 -->
<select id="getAllRecord" resultType="com.wedu.modules.dop.entity.vo.RepairRecordVo">
SELECT dop_repair_record.`id`,a.`name` AS workshop,dop_device.`number`,
dop_device.`name`,dop_device.`factory`,b.`name` AS fault_code,
dop_repair_record.`fault_express`,dop_repair_record.`repair_plan`,
dop_repair_record.`repair_time`,dop_repair_record.`repair_by`
FROM dop_repair_warning
JOIN dop_repair_record ON dop_repair_warning.`id`=dop_repair_record.`warn_id`
JOIN dop_device ON dop_repair_warning.`device_id`=dop_device.`id`
JOIN sys_dict a ON dop_device.`workshop`=a.`value`
AND a.`code`="workshop" AND a.`type`="2"
JOIN sys_dict b ON dop_repair_record.`fault_code`=b.`value`
AND b.`code`="fault_code" AND b.`type`="2"
${ew.customSqlSegment}
ORDER BY dop_repair_record.`id` ASC
</select>
AI写代码
由于车间名称和故障码在显示的时候都是直接拿的数据字典里的name,并且用的同一个数据字典,因此我在连接表的连接的时候用了a和b给同一个字典表起了别名,这样就不会使相同的name造成冲突。
3.2 修改功能
点击修改弹出修改框,数据回显,通过下拉框选择故障码

前端通过发送请求,传入参数code=faultCode,后端接收请求调用数据字典的方法,获取到所有的故障码字典项集合响应给前端下拉框作展示
init (id) {
this.dataForm.id = id
this.dialogVisible=true
this.$nextTick(() => {
this.$refs['dataForm'].resetFields()
if (this.dataForm.id) {
this.$http({
url:this.$http.adornUrl('/sys/dict/childList'),
method:'get',
params:this.$http.adornParams({
'code':'fault_code'
})
}).then(({data})=>{
console.log(data)
if (data&&data.code===0) {
this.options=data.page.list
}else{
this.options=[]
}
})
this.$http({
url: this.$http.adornUrl(`/dop/repairrecords/info/${this.dataForm.id}`),
method: 'get',
params: this.$http.adornParams()
}).then(({data}) => {
if (data && data.code === 0) {
this.dataForm.faultCode = data.info.faultCode+""
this.dataForm.faultExpress = data.info.faultExpress
this.dataForm.repairPlan = data.info.repairPlan
}
})
}
})
}
AI写代码
后端接收请求,根据传入的code去数据字典拿数据


在提交表单数据时,下拉框的选择的字典数据实际提交的是int类型的数据,还是对应的value值,所以直接修改并保存到维修记录表中的故障码还是value值,形成功能闭环,实现完成。
3.3 删除和批量删除
保养记录一般来说都是需要保留的,删除只是不显示在前端页面,但是后端数据库是需要保存这个数据的。实现的逻辑很简单,前面一样,实际做的是逻辑删除,只需要根据id遍历修改维修记录的状态便可以成功实现。
4、用户关注管理模块
该模块主要实现用户关注设备信息的列表展示、关注功能和取消关注功能。


4.1 用户关注列表
用户登陆平台后可以通过设备信息管理页面点击关注按钮实现对设备的关注,关注信息会在关注列表页面展示。数据库表中存的是当前登录用户id和关注的设备id,在展示的时候通过多表联查获取所有的关注信息以及要展示的字段。
提供DopFocusVo类封装前端要展示的数据,其余实现和其他模块一致。

4.2 关注功能实现
用户点击关注将关注信息添加到关注表中,为了保证同一用户不能关注相同设备,因此当用户关注设备后【关注】按钮变为不可选中的【已关注】。
由于【关注】按钮是在设备信息列表页面加载时一同加载的,因此需要在设备信息页面加载时及时根据当前用户的关注情况给出反馈,没关注的显示【关注】,已关注的显示【已关注】。
设计的思路:
(1)在封装设备信息的vo类中添加一个状态属性attentionStatus,0未关注,1已关注;
(2)遍历判断所有的设备id是否出现在当前用户关注表中。如果出现,当前设备信息的attentionStatus赋值为1,反之,attentionStatus赋值为0;
(3)前端根据相应的attentionStatus值,显示不同状态的按钮;
后端
在页面加载时,一同将用户的关注情况响应回去
@GetMapping("/list")
public R list(@RequestParam Map<String, Object> params){
PageUtils page= dopDeviceService.queryPage(params);
List<DopFocusEntity> focusList=new ArrayList<>();
focusList=dopFocusService.list(new QueryWrapper<DopFocusEntity>().eq("user_id",getUserId()).eq("delete_type",0));
System.out.println("当前用户的关注列表"+focusList);
List<DopDeviceVo> deviceList= (List<DopDeviceVo>) page.getList();
System.out.println("比较前设备信息:"+deviceList);
for (int i=0;i<deviceList.size();i++){
for (int j=0;j<focusList.size();j++){
if (deviceList.get(i).getId()== Math.toIntExact((focusList.get(j).getDeviceId()))) {
deviceList.get(i).setAttentionStatus(1);
System.out.println(deviceList);
System.out.println();
}
}
}
System.out.println("比较后设备信息:"+deviceList);
page.setList(deviceList);
return R.ok().put("page", page);
}
AI写代码
前端
<el-button type="text" size="small" disabled v-if="scope.row.attentionStatus===1">已关注</el-button>
<el-button type="text" size="small" @click="attentionDevice(scope.row.id)" v-if="scope.row.attentionStatus===0">关注</el-button>
AI写代码
4.3 取消关注功能
在设计关注表时,添加一个状态字段,实现的思路和逻辑删除一致,点击取消关注修改关注记录的状态值,前端显示时根据状态值不做显示。
六、出现的问题
1、数据库表中使用了关键字作为字段
主要包括mysql报错:Cause: java.sql.SQLException: sql injection violation, syntax error: ERROR. pos 39, line 2, column 24, token CLOSE
这是一个基本且容易忽略的问题,刚开始设计表时,给表添加了用作逻辑删除的字段delete,delete是mysql中的保留字段,不可以作为表中的字段。
2、在使用Map<String,Object>接收请求参数后,从map里取基本类型数据时,报java.lang.Integer cannot be cast to java.lang.String
不能直接通过(Integer) params.get("upkeepExpire")强转
解决方法先转String类型,再通过Interger.parseInt转换
如下面代码所示
@PostMapping("/add")
public R add(@RequestBody Map<String, Object> params) throws ParseException {
int workshop=Integer.parseInt((String) params.get("workshop"));
String number= (String) params.get("number");
int type= (Integer) params.get("type");
String name=(String)params.get("name");
String model= (String) params.get("model");
String factory= (String) params.get("factory");
//map集合中获取的日期是String 转成日期型
Date startUsetime=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").parse((String)params.get("startUsetime"));
//保养方式 获取到整型的对象 将对象转int型
int upkeep= (Integer) params.get("upkeep");
Date upkeepExpire2=null;
Date remindTime2=null;
int presetTime=0;
if (upkeep==0) {
int upkeepExpire1 = (Integer) params.get("upkeepExpire");
presetTime = 0;
int remindTime1 = (Integer) params.get("remindTime");
//保养到期时间
upkeepExpire2= ScheduleCalculateServiceUtil.addDateTime(startUsetime,upkeepExpire1);
System.out.println(upkeepExpire2);
//提醒时间
remindTime2=ScheduleCalculateServiceUtil.addDateTime(startUsetime,remindTime1);
System.out.println(remindTime2);
}else {
presetTime=(Integer) params.get("presetTime");
//保养到期时间
upkeepExpire2=null;
System.out.println(upkeepExpire2);
//提醒时间
remindTime2=null;
System.out.println(remindTime2);
}
DopDeviceEntity dopDeviceEntity=new DopDeviceEntity();
dopDeviceEntity.setWorkshop(workshop);
dopDeviceEntity.setNumber(number);
dopDeviceEntity.setType(type);
dopDeviceEntity.setName(name);
dopDeviceEntity.setModel(model);
dopDeviceEntity.setFactory(factory);
dopDeviceEntity.setUpkeep(upkeep);
dopDeviceEntity.setStartUsetime(startUsetime);
dopDeviceEntity.setUpkeepExpire(upkeepExpire2);
dopDeviceEntity.setRemindTime(remindTime2);
dopDeviceEntity.setPresetTime(presetTime);
dopDeviceEntity.setCreateTime(new Date());
dopDeviceEntity.setCreateBy(getUserId());
dopDeviceService.addDevice(dopDeviceEntity);
return R.ok();
}
AI写代码
3、多表分页时,自定义多表查询sql中给表取了别名,条件构造器QueryWrapper
这也是一个很容易忽略的问题,由于构造器里条件都会通过${ew.customSqlSegment}自动转换成where关键字并把条件拼接到原sql语句后面,所以应该以正常写sql的思想去使用条件构造器。
正确操作如下
<select id="getAllDevice" resultType="com.wedu.modules.dop.entity.vo.DopDeviceVo">
SELECT dop.`id`,sys.`name` workshop,dop.`number`,dop.`type`,dop.`name`,dop.`model`,dop.`factory`,dop.`create_time`,dop.`update_time`
FROM dop_device dop JOIN sys_dict sys ON dop.`workshop`=sys.`value`
${ew.customSqlSegment}
</select>
AI写代码
IPage<DopDeviceVo> page = dopDeviceDao.getAllDevice(
new Query<DopDeviceVo>().getPage(params),
new QueryWrapper<DopDeviceVo>()
.like(StringUtils.isNotBlank(workshop), "sys.`name`", workshop)
.like(StringUtils.isNotBlank(devicename), "dop.`name`", devicename)
.like(StringUtils.isNotBlank(factory),"dop.`factory`",factory)
.eq("dop.`delete_type`",0)
.eq("sys.`code`","workshop")
.eq("sys.`type`",2)
);
AI写代码
4、在form表单中根据单击不同的单选框显示相应的input输入框,刚开始使用v-show和disabled都没有效果,最终在input输入框通过v-if判断当前点击的单选框做显隐,效果很好,也很直接。
<el-form-item label="保养方式" prop="upkeep">
<el-radio-group v-model="dataForm.upkeep" size="small" :disabled="visibleForm">
<!-- 按周期 维保到期时间=开始时间+选择的天数 -->
<el-radio :label="0" border>按周期(天)</el-radio>
<!-- 按使用时间 预设时间=开始时间+选择的小时 后面用预设-开始时间去比对设备运行时间
定时任务的判断设备运行时间是否达到 预设-开始-->
<el-radio :label="1" border>按使用时长(小时)</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="保养时间" prop="upkeepExpire" v-if="dataForm.upkeep==0">
<el-input v-model.number="dataForm.upkeepExpire" placeholder="几天保养一次"></el-input>
</el-form-item>
<el-form-item label="提醒时间" prop="remindTime" v-if="dataForm.upkeep==0">
<el-input v-model.number="dataForm.remindTime" placeholder="几天后提醒"></el-input>
</el-form-item>
<el-form-item label="保养时间" prop="presetTime" v-if="dataForm.upkeep==1">
<el-input v-model.number="dataForm.presetTime" placeholder="使用几小时保养"
type="number" :min="this.nowRunTime" max="100" @change="runTime(dataForm.presetTime)"></el-input>
</el-form-item>
AI写代码


5、图片回显时,数据库存的是图片的绝对路径,前端img元素标签的src属性无法通过访问本地的绝对路径显示图片。
浏览器报错 Not allowed to load local resource: file:///D:/upload_picture/123.jpg
原因是浏览器的安全机制不允许通过浏览器访问本地文件 。
解决办法 :
给图片设置虚拟路径 ,通过http://localhost:8080/虚拟路径/图片名.jpg 访问图片
数据库

后端
@Configuration
public class PictureConfig implements WebMvcConfigurer {
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/images/**").addResourceLocations("file:/D:/upload_picture/");
}
}
AI写代码
前端
<el-table-column label="故障图片" width="110px">
<template slot-scope="scope">
<img
style="width: 80px; height: 80px"
:src="`http://localhost:8080/wedu/images/${scope.row.img}`"
/>
</template>
</el-table-column>
AI写代码
注意 :这里还可能出现一个问题,请求路径没错,请求也发送成功了,但是读取不到图片,没有响应数据。
经过长时间的处理,发现请求发送成功,说明配置的虚拟路径没有问题,按理说可以拿到数据了,但是被后端的过滤器拦截了,导致请求发送成功但是不会响应任何数据。
解决方法:只需要将配置好的虚拟路径加入过滤器中放行就可以解决,再去试试,图片成功显示。

6、表单校验的问题
在新增和修改设备信息时需要对不同的保养方式输入的时间进行限制,由于涉及到比较,所以输入的时间应该是number类型。
刚开始通过在js里面的return{ }里对绑定的表单数据赋0达到输入数据是number类型的效果
return{
dataForm: {//form表单绑定数据
id:'',
workshop:'',
number:'',
type:0,
name:'',
model:'',
factory:'',
startUsetime:'',
upkeep:0,
upkeepExpire:0,
remindTime:0,
presetTime:0
}
}
AI写代码
但是
<el-form-item label="保养时间" prop="upkeepExpire" v-if="dataForm.upkeep==0">
<el-input v-model.number="dataForm.upkeepExpire" placeholder="几天保养一次"></el-input>
</el-form-item>
<el-form-item label="提醒时间" prop="remindTime" v-if="dataForm.upkeep==0">
<el-input v-model.number="dataForm.remindTime" placeholder="几天后提醒"></el-input>
</el-form-item>
AI写代码
这里使用v-model.number可以实现表单提交时请求携带的参数是number类型的数据。
<el-form-item label="保养时间" prop="presetTime" v-if="dataForm.upkeep==1">
<el-input v-model.number="dataForm.presetTime" placeholder="使用几小时保养"
type="number" :min="this.nowRunTime" max="100" @change="runTime(dataForm.presetTime)"></el-input>
</el-form-item>
AI写代码
给input标签添加type=“number”时,input输入框后面会出现两个可以点击的小按钮,可以配合min和max达到控制表单输入的效果。但是无法控制键盘输入时的数据类型,还是需要v-model.number限制键盘输入的数据类型。

