实战篇
1.项目介绍
帝可得是一个基于物联网概念下的智能售货机运营管理系统
1.1角色与功能
一个完整的售货机系统由五端五角色组成:
管理员:对基础数据(区域、点位、设备、货道、商品等)进行管理,创建工单指派运维或运营人员,查看订单,查看各种统计报表。
运维人员:投放设备、撤除设备、维修设备。
运营人员:补货。
合作商:仅提供点位,坐收渔翁之利。
消费者: 在小程序或屏幕端下单购买商品。
1.2 业务流程
整个工程中,课程里会对主要核心的业务进行实现,主要包含下面的业务流程: (1)平台管理员 :主要作用有基础数据的管理和创建工单排除员工完成维修或补货。 (2)运营人员 :主要作用是处理运营工单业务(补货等操作) (3)运维人员 :主要作用是处理运维工单业务(设备维修等操作) (4)消费者 :提供C端用户使用。消费者扫描售货机上的二维码可以打开此端。主要作用是完成在售货机的购物操作。
1.4.1 平台管理员
上图中的简要流程: ①:平台管理人员登录到系统管理后台系统 ②:创建区域数据 ③:创建区域下点位数据 ④:添加运维/运营人员 ⑤:创建售货机信息 ⑥:设置售货机点位信息 ⑦:创建运维投放工单,由运维人员开始投放设备(安装设备) ⑧:设置售卖的商品信息 ⑨:创建运营补货工单,由运营人员开始投放商品信息
1.4.2 运维人员
上图中的简要流程: ①:运维人员通过App登录运营系统 ②:在App对派送过来的工单进行处理 ③:接受工单后在指定的投放点安装售货机 ④:拒绝工单该运维人员的工单结束
1.4.3 运营人员
上图中的简要流程: ①:运营人员通过App登录运营系统 ②:在App对派送过来的工单进行处理 ③:接受工单后在指定的售货机的商品进行补货 ④:拒绝工单该运维人员的工单结束
1.4.4 消费者
上图中的简要流程:
上图中的简要流程: 方式一: ①:用户通过售货机二维码进行购买商品 ②:扫码后在手机端微信小程序选择商品 ③:支付成功后在售货机取货 方式二: ①:用户在售货机上选择商品 ②:在选择商品后扫码支付商品的二维码 ③:支付成功后在售货机取货
1.5 产品原型
帝可得项目点击链接立即查看 https://codesign.qq.com/s/426304924036117
1.6 库表设计
系统后台基础数据表关系说明:
一个区域可以有多个点位 一个点位可以有多个售货机 一个售货机有多个货道 多个货道可以放置同一样商品 一个商品类型下有多个商品 一个售货机类型下有多个售货机 一个合作商有多个点位 合作商和区域之间没有关系,因为合作商拥有的多个点位可以分布在不同的区域 每个区域下有多个运维和运营人员,他们来负责这个区域下的设备的运维和运营
2.项目搭建
2.1 搭建后端项目
2.1.1初始化项目
Git下载
通过idea克隆源码,仓库地址:https://gitee.com/ys-gitee/dkd-parent.git
Maven构建
使用idea打开项目后,等待环境检查(主要是Maven下载项目依赖)
2.1.2MySQL相关
导入sql
1、创建数据库create schema dkd; 2、执行sql脚本文件,完成导入
配置信息
在dkd-admin模块下,编辑resources目录下的application-druid.yml,修改数据库连接
2.1.3 Redis相关
在redis解压目录下,执行redis-server.exe redis.windows.conf启动
在dkd-admin模块下,resources目录下的application.yml,设置redis密码等相关信息
2.1.4 项目运行
在dkd-admin模块下,运行com.ruoyi.DkdApplication.java
2.2 搭建前端项目
2.2.1 初始化项目
通过vscode克隆源码,仓库地址:https://gitee.com/ys-gitee/dkd-vue.git
2.2.2 安装依赖
2.2.3 项目运行
打开浏览器,输入:([http://localhost:80 ) 默认账户/密码 admin/admin123)若能正确展示登录页面,并能成功登录,菜单及页面展示正常,表明环境搭建成功
3. 点位管理
3.1 需求说明
业务场景 : 假设我们的公司现在有一个宏伟的计划——在北京发展业务。首先,我们需要确定几个有潜力的区域,这些区域可能是人流量大、消费能力高的商业区或居民区。然后,我们要与这些区域内的潜在合作商进行洽谈,比如商场、写字楼、学校等地方的管理者或所有者。
一旦我们与合作商达成协议,确定了合作的细节和点位,我们就可以安排工作人员去投放智能售货机了。这些点位将成为我们智能售货机的“家”,为消费者提供便捷的购买服务。
点位管理主要涉及到三个功能模块,业务流程如下:
登录系统 :后台管理人员登录后台系统
新增区域 : 后台管理人员可以添加区域范围,区域范围与运维/运维人员挂钩,区域下可关联点位。
新增合作商 : 管理人员可以添加合作商,合作商与点位进行关联。
新增区域点位 : 后台管理人员可以在特定区域内新增点位,这些点位是放置智能售货机的具体位置。
3.2库表设计
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 CREATE TABLE `tb_region` ( `id` INT AUTO_INCREMENT COMMENT '主键id' PRIMARY KEY , `region_name` VARCHAR (255 ) NOT NULL COMMENT '区域名称' , `create_time` TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间' , `update_time` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间' , `create_by` VARCHAR (64 ) COMMENT '创建人' , `update_by` VARCHAR (64 ) COMMENT '修改人' , `remark` TEXT COMMENT '备注' ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT ='区域表' ; INSERT INTO `tb_region` (`region_name`,`remark`) VALUES ('北京市朝阳区' ,'北京市朝阳区' ), ('北京市海淀区' ,'北京市海淀区' ), ('北京市东城区' ,'北京市东城区' );CREATE TABLE `tb_partner` ( `id` INT AUTO_INCREMENT COMMENT '主键id' PRIMARY KEY , `partner_name` VARCHAR (255 ) NOT NULL COMMENT '合作商名称' , `contact_person` VARCHAR (64 ) COMMENT '联系人' , `contact_phone` VARCHAR (15 ) COMMENT '联系电话' , `profit_ratio` INT COMMENT '分成比例' , `account` VARCHAR (64 ) COMMENT '账号' , `password ` VARCHAR (64 ) COMMENT '密码' , `create_time` TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间' , `update_time` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间' , `create_by` VARCHAR (64 ) COMMENT '创建人' , `update_by` VARCHAR (64 ) COMMENT '修改人' , `remark` TEXT COMMENT '备注' ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT ='合作商表' ; INSERT INTO `tb_partner` (`partner_name`, `contact_person`, `contact_phone`, `profit_ratio`, `account`, `password `) VALUES ('合作商A' , '张三' , '13800138000' , 30 , 'a001' , 'pwdA' ), ('合作商B' , '李四' , '13912345678' , 25 , 'b002' , 'pwdB' ); CREATE TABLE `tb_node` ( `id` INT AUTO_INCREMENT COMMENT '主键id' PRIMARY KEY , `node_name` VARCHAR (255 ) NOT NULL COMMENT '点位名称' , `address` VARCHAR (255 ) NOT NULL COMMENT '详细地址' , `business_type` INT COMMENT '商圈类型' , `region_id` INT COMMENT '区域ID' , `partner_id` INT COMMENT '合作商ID' , `create_time` TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间' , `update_time` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间' , `create_by` VARCHAR (64 ) COMMENT '创建人' , `update_by` VARCHAR (64 ) COMMENT '修改人' , `remark` TEXT COMMENT '备注' , FOREIGN KEY (`region_id`) REFERENCES `tb_region`(`id`) ON DELETE CASCADE ON UPDATE CASCADE , FOREIGN KEY (`partner_id`) REFERENCES `tb_partner`(`id`) ON DELETE CASCADE ON UPDATE CASCADE ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT ='点位表' ; INSERT INTO `tb_node` (`node_name`, `address`, `business_type`, `region_id`, `partner_id`) VALUES ('三里屯点位' , '北京市朝阳区三里屯路' , 1 , 1 , 1 ), ('五道口点位' , '北京市海淀区五道口' , 2 , 2 , 2 );
对于点位管理数据模型,下面是示意图:
关系字段:region_id、partner_id
数据字典:business_type
3.3 生成基础代码
这里的若依生成代码在基础篇已经讲的很详细了,这里这贴出部分重要的配置图
配置合作商表(参考原型)
配置区域表(参考原型)
配置点位表(参考原型)
3.4 区域管理改造
3.4.1 基础页面
需求
参考页面原型,完成基础布局展示改造
代码实现
将vue代码中的主键id改为序号, 并加上type=“index” width=“50” ,去掉修改和删除前面的图标
在region/index.vue视图组件中修改
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 <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" width ="50" align ="center" prop ="id" /> <el-table-column label ="区域名称" align ="center" prop ="regionName" /> <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" @click ="handleUpdate(scope.row)" v-hasPermi ="['manage:region:edit']" > 修改</el-button > <el-button link type ="primary" @click ="handleDelete(scope.row)" v-hasPermi ="['manage:region:remove']" > 删除</el-button > </template > </el-table-column > </el-table >
3.4.2 区域列表
需求
在区域列表查询中,需要显示每个区域的点位数
实现思路
实现此功能也有多种方案: **(1)同步存储:**在区域表中有点位数的字段,当点位发生变化时,同步区域表中的点位数。
优点:由于是单表查询操作,查询列表效率最高。
缺点:需要在点位增删改时修改区域表中的数据,有额外的开销,数据也可能不一致。
**(2)关联查询:**编写关联查询语句,在mapper 层封装。
优点:实时查询,数据100%正确,不需要单独维护。
缺点:SQL语句较复杂,如果数据量大,性能比较低。
区域和点位表,记录个数都不是很多,所以我们采用关联查询这种方案。
SQL
SQL查询:先聚合统计每个区域的点位数,然后与区域表进行关联查询
1 2 3 4 5 6 7 8 9 10 11 12 select region_id,count(*) as node_count from tb_node group by region_id;select r.id,r.region_name,r.remark,ifnull(n.node_count,0 ) as node_count from tb_region r left join (select region_id,count(*) as node_count from tb_node group by region_id) n on r.id=n.region_id; SELECT r.*, COUNT(n.id) AS node_count FROM tb_region r LEFT JOIN tb_node n ON r.id = n.region_id GROUP BY r.id;
RegionVo
根据接口文档和sql创建RegionVo
RegionMapper
1 2 3 4 5 6 public List<RegionVo> selectRegionVoList (Region region) ;
RegionMapper.xml
1 2 3 4 5 6 7 8 9 <select id="selectRegionVoList" resultType="com.dkd.manage.domain.vo.RegionVo" > select r.id,r.region_name,r.remark,ifnull (n.node_count,0 ) as node_count from tb_region r left join (select region_id,count (*) as node_count from tb_node group by region_id) n on r.id=n.region_id <where > <if test="regionName != null and regionName != ''" > and r.region_name like concat('%' , #{regionName}, '%' )</if > </where > </select >
mybatis-config.xml
IRegionService
1 2 3 4 5 6 public List<RegionVo> selectRegionVoList (Region region) ;
RegionServiceImpl
1 2 3 4 5 6 7 8 9 @Override public List <RegionVo > selectRegionVoList (Region region ) { return regionMapper.selectRegionVoList (region); }
RegionController
1 2 3 4 5 6 7 8 9 10 11 @PreAuthorize("@ss.hasPermi('manage:region:list')" ) @GetMapping("/list" ) public TableDataInfo list(Region region){ startPage(); List<RegionVo> voList = regionService.selectRegionVoList(region); return getDataTable(voList); }
region/index.vue
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 <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" width ="50" align ="center" prop ="id" /> <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" @click ="handleUpdate(scope.row)" v-hasPermi ="['manage:region:edit']" > 修改</el-button > <el-button link type ="primary" @click ="handleDelete(scope.row)" v-hasPermi ="['manage:region:remove']" > 删除</el-button > </template > </el-table-column > </el-table >
3.5合作商管理改造
3.5.1 基础页面
需求
参考页面原型,完成基础布局展示改造
代码实现
在partner/index.vue视图组件中
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 <el-form :model ="queryParams" ref ="queryRef" :inline ="true" v-show ="showSearch" label-width ="90px" > <el-form-item label ="合作商名称" prop ="name" > <el-input v-model ="queryParams.partnerName" 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 ="partnerList" @selection-change ="handleSelectionChange" > <el-table-column type ="selection" width ="55" align ="center" /> <el-table-column label ="序号" type ="index" width ="50" align ="center" prop ="id" /> <el-table-column label ="合作商名称" align ="center" prop ="partnerName" /> <el-table-column label ="账号" align ="center" prop ="account" /> <el-table-column label ="分成比例" align ="center" prop ="profitRatio" > <template #default ="scope" > {{scope.row.profitRatio }} %</template > </el-table-column > <el-table-column label ="联系人" align ="center" prop ="contactPerson" /> <el-table-column label ="联系电话" align ="center" prop ="contactPhone" /> <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:partner:edit']" > 修改</el-button > <el-button link type ="primary" @click ="handleDelete(scope.row)" v-hasPermi ="['manage:partner:remove']" > 删除</el-button > </template > </el-table-column > </el-table > <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 ="contactPhone" v-if ="form.id!=null" > {{form.createTime }} </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" type ="password" 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 > </div > </template >
在PartnerServiceImpl的新增方法中修改
1 2 3 4 5 6 7 8 9 10 11 12 13 @Override public int insertPartner (Partner partner) { partner.setPassword(SecurityUtils.encryptPassword(partner.getPassword())); partner.setCreateTime(DateUtils.getNowDate()); return partnerMapper.insertPartner (partner) ; }
3.5.2 查看详情
在partner/index.vue视图组件中
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 <el-button link type ="primary" @click ="getPartnerInfo(scope.row)" v-hasPermi ="['manage:partner:query']" > 查看详情</el-button > <el-dialog title ="合作商详情" v-model ="partnerInfoOpen" width ="500px" append-to-body > <el-row > <el-col :span ="12" > 合作商名称: {{ form.partnerName }} </el-col > <el-col :span ="12" > 联系人: {{ form.contactPerson }} </el-col > </el-row > <el-row > <el-col :span ="12" > 联系电话: {{ form.contactPhone }} </el-col > <el-col :span ="12" > 分成比例: {{ form.profitRatio }} %</el-col > </el-row > </el-dialog > <script > const partnerInfoOpen = ref (false ); function getPartnerInfo (row ) { reset (); const _id = row.id ; getPartner (_id).then (response => { form.value = response.data ; partnerInfoOpen.value = true ; }); } </script > <el-dialog title ="合作商详情" v-model ="partnerInfoOpen" width ="500px" append-to-body > <el-descriptions :column ="2" border > <el-descriptions-item label ="合作商名称" > {{ form.partnerName }} </el-descriptions-item > <el-descriptions-item label ="联系人" > {{ form.contactPerson }} </el-descriptions-item > <el-descriptions-item label ="联系电话" > {{ form.contactPhone }} </el-descriptions-item > <el-descriptions-item label ="分成比例" > {{ form.profitRatio }} %</el-descriptions-item > </el-descriptions > </el-dialog >
3.5.3 合作商列表
实现思路
与区域列表实现方式相同。
SQL
SQL查询:先聚合统计每个合作商的点位数,然后与合作商表进行关联查询
1 2 3 4 5 6 7 8 9 10 11 12 13 SELECT partner_id, COUNT(1 ) AS node_count from tb_node group by partner_id;select tp.*,ifnull(tn.node_count,0 ) from tb_partner tpleft join (SELECT partner_id, COUNT(1 ) AS node_count from tb_node group by partner_id) tn on tn.partner_id = tp.id; SELECT p.*, COUNT(n.id) AS node_count FROM tb_partner p LEFT JOIN tb_node n ON p.id = n.partner_id GROUP BY p.id;
PartnerVo
根据接口文档和sql创建PartnerVo
PartnerMapper
1 2 3 4 5 6 public List<PartnerVo> selectPartnerVoList (Partner partner) ;
PartnerMapper.xml
1 2 3 4 5 6 7 8 9 10 11 <select id="selectPartnerVoList" resultType="com.dkd.manage.domain.vo.PartnerVo" > SELECT p.*, COUNT (n.id) AS node_count FROM tb_partner p LEFT JOIN tb_node n ON p.id = n.partner_id <where > <if test="partnerName != null and partnerName != ''" >and partner_name like concat('%' , #{partnerName},'%' ) </if > </where > GROUP BY p.id </select >
IPartnerService
1 2 3 4 5 6 public List<PartnerVo> selectPartnerVoList (Partner partner) ;
PartnerServiceImpl
1 2 3 4 5 6 7 8 9 @Override public List <PartnerVo > selectPartnerVoList (Partner partner ) { return partnerMapper.selectPartnerVoList (partner); }
PartnerController
1 2 3 4 5 6 7 8 9 10 @PreAuthorize("@ss.hasPermi('manage:partner:list')" ) @GetMapping("/list" ) public TableDataInfo list(Partner partner) { startPage(); List<PartnerVo> voList = partnerService.selectPartnerVoList(partner); return getDataTable(voList); }
region/index.vue
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 <el-table v-loading ="loading" :data ="partnerList" @selection-change ="handleSelectionChange" > <el-table-column type ="selection" width ="55" align ="center" /> <el-table-column label ="序号" type ="index" width ="50" align ="center" prop ="id" /> <el-table-column label ="合作商名称" align ="center" prop ="partnerName" /> <el-table-column label ="点位数" align ="center" prop ="nodeCount" /> <el-table-column label ="账号" align ="center" prop ="account" /> <el-table-column label ="分成比例" align ="center" prop ="profitRatio" > <template #default ="scope" > {{ scope.row.profitRatio }} %</template > </el-table-column > <el-table-column label ="联系人" align ="center" prop ="contactPerson" /> <el-table-column label ="联系电话" align ="center" prop ="contactPhone" /> <el-table-column label ="操作" align ="center" class-name ="small-padding fixed-width" > <template #default ="scope" > <el-button link type ="primary" @click ="getPartnerInfo(scope.row)" v-hasPermi ="['manage:partner:query']" > 查看详情</el-button > <el-button link type ="primary" @click ="handleUpdate(scope.row)" v-hasPermi ="['manage:partner:edit']" > 修改</el-button > <el-button link type ="primary" @click ="handleDelete(scope.row)" v-hasPermi ="['manage:partner:remove']" > 删除</el-button > </template > </el-table-column > </el-table >
3.5.4 重置密码
后端部分
在PartnerController中
1 2 3 4 5 6 7 8 9 10 11 12 13 14 @PreAuthorize("@ss.hasPermi('manage:partner:edit')" ) @Log(title = "重置合作商密码" , businessType = BusinessType.UPDATE) @PutMapping("/resetPwd/{id}" ) public AjaxResult resetpwd(@PathVariable Long id) { Partner partner = new Partner(); partner.setId(id); partner.setPassword(SecurityUtils.encryptPassword("123456" )); return toAjax(partnerService.updatePartner(partner)); }
前端部分
在manage/partner.js请求api中
1 2 3 4 5 6 7 export function resetPartnerPwd(id ){ return request({ url: '/manage/partner/resetPwd/' + id , method : 'put' }) }
在partner/index.vue视图组件中
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 <el-table-column label ="操作" align ="center" class-name ="small-padding fixed-width" width ="300px" > <template #default ="scope" > <el-button link type ="primary" @click ="resetPwd(scope.row)" v-hasPermi ="['manage:partner:edit']" > 重置密码</el-button > </template > </el-table-column> <script > import { listPartner, getPartner, delPartner, addPartner, updatePartner,resetPartnerPwd } from "@/api/manage/partner" ; function resetPwd (row ) { proxy.$modal .confirm('你确定要重置该合作商密码吗?' ).then (function () { return resetPartnerPwd(row.id ); } ).then (() => { proxy.$modal .msgSuccess("重置成功" ); } ).catch (() => { } ); } </script >
3.6 点位管理改造
3.6.1 基础页面
需求
参考页面原型,完成基础布局展示改造
代码实现
在node/index.vue视图组件中
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 <el-form :model ="queryParams" ref ="queryRef" :inline ="true" v-show ="showSearch" label-width ="68px" > <el-form-item label ="点位名称" prop ="nodeName" > <el-input v-model ="queryParams.nodeName" placeholder ="请输入点位名称" clearable @keyup.enter ="handleQuery" /> </el-form-item > <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 > <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 ="nodeList" @selection-change ="handleSelectionChange" > <el-table-column type ="selection" width ="55" align ="center" /> <el-table-column label ="序号" type ="index" width ="50" align ="center" prop ="id" /> <el-table-column label ="点位名称" align ="center" prop ="nodeName" /> <el-table-column label ="区域ID" align ="center" prop ="regionId" /> <el-table-column label ="商圈类型" align ="center" prop ="businessType" > <template #default ="scope" > <dict-tag :options ="business_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" show-overflow-tooltip /> <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-dialog :title ="title" v-model ="open" width ="500px" append-to-body > <el-form ref ="nodeRef" :model ="form" :rules ="rules" label-width ="100px" > <el-form-item label ="点位名称" prop ="nodeName" > <el-input v-model ="form.nodeName" placeholder ="请输入点位名称" /> </el-form-item > <el-form-item label ="所在区域" prop ="regionId" > <el-select v-model ="form.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 > <el-form-item label ="商圈类型" prop ="businessType" > <el-select v-model ="form.businessType" placeholder ="请选择商圈类型" > <el-option v-for ="dict in business_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-select v-model ="form.partnerId" placeholder ="请选择合作商" clearable > <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 ="address" > <el-input type ="textarea" v-model ="form.address" 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 > <script setup name ="Node" > import { listPartner } from "@/api/manage/partner" ; import { listRegion } from "@/api/manage/region" ; import { loadAllParams } from "@/api/page" ; const partnerList = ref ([]); function getPartnerList ( ) { listPartner (loadAllParams).then (response => { partnerList.value = response.rows ; }); } getPartnerList (); const regionList = ref ([]); function getRegionList ( ) { listRegion (loadAllParams).then (response => { regionList.value = response.rows ; }); } getRegionList (); </script >
在api/page.js中,抽取一个查询所有的条件
1 2 3 4 export const loadAllParams = reactive ({ pageNum : 1 , pageSize : 10000 , });
3.6.2 点位列表
需求
在区域详情中,需要显示每个点位的设备数
在点位列表查询中,会关联显示区域、商圈等信息
实现思路
关联查询 :对于设备数量的统计,我们需要执行关联查询,在mapper 层封装。 关联实体 :对于区域和合作商的数据,我们会采用Mybatis提供的嵌套查询功能。
MyBatis 嵌套查询就是将原来多表查询中的联合查询语句拆成单个表的查询,再使用mybatis的语法嵌套在一 起,通过定义resultMap和sql语句中的association或collection元素来实现嵌套查询。
导入资料中dkd.sql业务表到开发数据库中
SQL
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 create table tb_node( id int auto_increment comment '主键id' primary key , node_name varchar (255 ) not null comment '点位名称' , address varchar (255 ) not null comment '详细地址' , business_type int null comment '商圈类型' , region_id int null comment '区域ID' , partner_id int null comment '合作商ID' , create_time timestamp default CURRENT_TIMESTAMP null comment '创建时间' , update_time timestamp default CURRENT_TIMESTAMP null on update CURRENT_TIMESTAMP comment '修改时间' , create_by varchar (64 ) null comment '创建人' , update_by varchar (64 ) null comment '修改人' , remark text null comment '备注' , constraint tb_node_ibfk_1 foreign key (region_id) references tb_region (id) on update cascade on delete cascade , constraint tb_node_ibfk_2 foreign key (partner_id) references tb_partner (id) on update cascade on delete cascade ) comment '点位表' ; create table tb_vending_machine( id bigint auto_increment comment '主键' primary key , inner_code varchar (15 ) default '000' null comment '设备编号' , channel_max_capacity int null comment '设备容量' , node_id int not null comment '点位Id' , addr varchar (100 ) null comment '详细地址' , last_supply_time datetime default '2000-01-01 00:00:00' not null comment '上次补货时间' , business_type int not null comment '商圈类型' , region_id int not null comment '区域Id' , partner_id int not null comment '合作商Id' , vm_type_id int default 0 not null comment '设备型号' , vm_status int default 0 not null comment '设备状态,0:未投放;1-运营;3-撤机' , running_status varchar (100 ) null comment '运行状态' , longitudes double default 0 null comment '经度' , latitude double default 0 null comment '维度' , client_id varchar (50 ) null comment '客户端连接Id,做emq认证用' , policy_id bigint null comment '策略id' , create_time timestamp default CURRENT_TIMESTAMP not null comment '创建时间' , update_time timestamp default CURRENT_TIMESTAMP null comment '修改时间' , constraint vendingmachine_VmId_uindex unique (inner_code), constraint tb_vending_machine_ibfk_1 foreign key (vm_type_id) references tb_vm_type (id), constraint tb_vending_machine_ibfk_2 foreign key (node_id) references tb_node (id), constraint tb_vending_machine_ibfk_3 foreign key (policy_id) references tb_policy (policy_id) ) comment '设备表' ; SELECT n.id, n.node_name, n.address, n.business_type, n.region_id, n.partner_id, n.create_time, n.update_time, n.create_by, n.update_by, n.remark, COUNT(v.id) AS vm_count FROM tb_node n LEFT JOIN tb_vending_machine v ON n.id = v.node_id GROUP BY n.id; select * from tb_region where id=1 ;select * from tb_partner where id=1 ;
NodeVo
1 2 3 4 5 6 7 8 9 10 11 12 @Data public class NodeVo extends Node { private int vmCount; private Region region; private Partner partner; }
NodeMapper
1 2 3 4 5 6 /** * 查询点位管理列表 * @param node * @return NodeVo集合 */ public List<NodeVo> selectNodeVoList(Node node );
NodeMapper.xml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 <resultMap type ="NodeVo" id="NodeVoResult"> <result property="id" column="id" /> <result property="nodeName" column="node_name" /> <result property="address" column="address" /> <result property="businessType" column="business_type" /> <result property="regionId" column="region_id" /> <result property="partnerId" column="partner_id" /> <result property="createTime" column="create_time" /> <result property="updateTime" column="update_time" /> <result property="createBy" column="create_by" /> <result property="updateBy" column="update_by" /> <result property="remark" column="remark" /> <result property="vmCount" column="vm_count" /> <association property="region" javaType="Region" column="region_id" select ="com.dkd.manage.mapper.RegionMapper.selectRegionById"/> <association property="partner" javaType="Partner" column="partner_id" select ="com.dkd.manage.mapper.PartnerMapper.selectPartnerById"/> </resultMap> <select id="selectNodeVoList" resultMap="NodeVoResult"> SELECT n.id, n.node_name, n.address, n.business_type, n.region_id, n.partner_id, n.create_time, n.update_time, n.create_by, n.update_by, n.remark, COUNT(v.id) AS vm_count FROM tb_node n LEFT JOIN tb_vending_machine v ON n.id = v.node_id <where > <if test="nodeName != null and nodeName != ''"> and n.node_name like concat('%' , #{nodeName}, '%' )</if > <if test="regionId != null "> and n.region_id = #{regionId}</if > <if test="partnerId != null "> and n.partner_id = #{partnerId}</if > </where > GROUP BY n.id </select >
NodeService
1 2 3 4 5 6 /** * 查询点位管理列表 * @param node * @return NodeVo集合 */ public List<NodeVo> selectNodeVoList(Node node );
NodeServiceImpl
1 2 3 4 5 6 7 8 9 10 /** * 查询点位管理列表 * * @param node * @return NodeVo集合 */ @Override public List<NodeVo> selectNodeVoList(Node node ) { return nodeMapper.selectNodeVoList(node ); }
NodeController
1 2 3 4 5 6 7 8 9 10 11 @PreAuthorize("@ss.hasPermi('manage:node:list')" ) @GetMapping("/list" ) public TableDataInfo list(Node node){ startPage(); List<NodeVo> voList = nodeService.selectNodeVoList(node); return getDataTable(voList); }
node/index.vue
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 <el-table v-loading ="loading" :data ="nodeList" @selection-change ="handleSelectionChange" > <el-table-column type ="selection" width ="55" align ="center" /> <el-table-column label ="序号" type ="index" width ="50" align ="center" prop ="id" /> <el-table-column label ="点位名称" align ="center" prop ="nodeName" /> <el-table-column label ="所在区域" align ="center" prop ="region.regionName" /> <el-table-column label ="商圈类型" align ="center" prop ="businessType" > <template #default ="scope" > <dict-tag :options ="business_type" :value ="scope.row.businessType" /> </template > </el-table-column > <el-table-column label ="合作商" align ="center" prop ="partner.partnerName" /> <el-table-column label ="详细地址" align ="center" prop ="address" show-overflow-tooltip ="true" /> <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 >
3.7 区域查看详情
在region/index.vue视图组件中修改
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 <el-button link type ="primary" @click ="getRegionInfo(scope.row)" v-hasPermi ="['manage:node:list']" > 查看详情</el-button > <el-dialog title ="区域详情" v-model ="regionInfoOpen" width ="500px" append-to-body > <el-form-item label ="区域名称" prop ="regionName" > <el-input v-model ="form.regionName" disabled /> </el-form-item > <label > 包含点位:</label > <el-table :data ="nodeList" > <el-table-column label ="序号" type ="index" width ="50" align ="center" /> <el-table-column label ="点位名称" align ="center" prop ="nodeName" /> <el-table-column label ="设备数量" align ="center" prop ="vmCount" /> </el-table > </el-dialog > <script > import { listNode } from "@/api/manage/node" ; import { loadAllParams } from "@/api/page" ; const nodeList = ref ([]); const regionInfoOpen = ref (false ); function getRegionInfo (row ) { reset (); const _id = row.id getRegion (_id).then (response => { form.value = response.data ; }); loadAllParams.regionId = row.id ; listNode (loadAllParams).then (response => { nodeList.value = response.rows ; }); regionInfoOpen.value = true ; </script >
3.8数据完整性
现在我们要思考一个问题,当我们删除区域或合作商数据时,与之关联的点位数据该如何处理?
在默认情况下,由于我们在创建点位表时设置了外键约束,并配置了级联删除操作,所以删除区域或合作商会导致其关联的点位数据一并被删除。从技术角度来看,这是符合数据库的外键约束规则的。 但是,从业务角度来看,这种做法可能不太合适。想象一下,如果一个区域下有多个点位,一次误操作就可能导致所有的点位数据及其关联的设备信息被一并删除,这显然是我们不愿意看到的。 因此,我们需要对级联操作进行修改,将其改为限制删除。这样,当尝试删除一个区域或合作商时,如果它下面还有关联的点位数据,数据库将不会允许删除操作,并会给出错误提示。
**CASCADE(级联操作):**当父表中的某行记录被删除或更新时,与其关联的所有子表中的匹配行也会自动被删除或更新。这种方式适用于希望保持数据一致性的场景,即父记录不存在时,相关的子记录也应该被移除。 **SET NULL(设为空):**若父表中的记录被删除或更新,子表中对应的外键字段会被设置为NULL。选择此选项的前提是子表的外键列允许为NULL值。这适用于那些子记录不再需要明确关联到任何父记录的情况。 **RESTRICT(限制):**在尝试删除或更新父表中的记录之前,数据库首先检查是否有相关联的子记录存在。如果有,则拒绝执行删除或更新操作,以防止意外丢失数据或破坏数据关系的完整性。这是一种保守策略,确保数据间的引用完整性。 **NO ACTION(无操作):**在标准SQL中,NO ACTION是一个关键字,它要求数据库在父表记录被删除或更新前,检查是否会影响子表中的相关记录。在MySQL中,NO ACTION的行为与RESTRICT相同,即如果子表中有匹配的行,则禁止执行父表的删除或更新操作。这意味着如果存在依赖关系,操作将被阻止,从而保护数据的参照完整性。
修改完毕后,如果你尝试进行删除操作,会发现数据库的完整性约束生效了,它会阻止删除操作并给出错误提示。但是,这个错误提示信息可能对于用户来说不够友好,可能会让用户感到困惑。
SQLIntegrityConstraintViolationException是Java中的一个异常类,这个类通常用于表示SQL数据库操作中的完整性约束违反异常 例如:外键约束、唯一约束等。当数据库操作违反了这些约束时,就会抛出这个异常。 这个错误是由于外键约束导致的。它表明在删除或更新父表的行时,存在外键约束,子表中的相关行会受到影响。 是因为在删除tb_region表中的行时,tb_node表中的region_id外键约束会阻止操作。 如果你在使用Spring框架进行数据库操作,可能会先遇到DataIntegrityViolationException,它是对SQLIntegrityConstraintViolationException的一个更高层次的抽象,旨在提供一种更加面向应用的错误表示。 而SQLIntegrityConstraintViolationException是更底层的异常,直接来源于数据库驱动,包含更多底层数据库相关的细节。 在实际开发中,推荐捕获并处理DataIntegrityViolationException,因为它更符合Spring应用的异常处理模式,同时也可以通过其内部的cause(原因)属性来获取具体的SQLIntegrityConstraintViolationException,进而获取详细的错误信息。
为了提升用户体验,我们可以使用Spring Boot框架的全局异常处理器来捕获这些错误信息,并返回更友好的提示信息给用户。这样,当用户遇到这种情况时,他们将收到一个清晰、易懂的提示,告知他们操作无法完成的原因。
修改全局异常处理器,添加以下内容
1 2 3 4 5 6 7 8 9 10 11 12 @ExceptionHandler (DataIntegrityViolationException.class)public AjaxResult handelDataIntegrityViolationException (DataIntegrityViolationException e) { if (e.getMessage().contains("foreign" )) { return AjaxResult.error ("无法删除,有其他数据引用" ) ; } return AjaxResult.error ("您的操作违反了数据库中的完整性约束" ) ; }
4. 人员管理
4.1 需求说明
人员管理业务流程如下:
登录系统: 首先,后台管理人员需要登录到帝可得后台管理系统中。
新增工作人员: 登录系统后,管理人员可以新增工作人员,包括姓名、联系方式等信息。
关联角色: 确定此员工是运维人员还是运营人员,这将影响他们的职责和权限。
关联区域: 确定员工负责的区域范围,确保工作人员能够高效地完成区域内的设备安装、维修、商品补货等工作。
对于人员和其他管理数据,下面是示意图:
关系字段:role_id、region_id
数据字典:status(1启用、0停用)
冗余字段:region_name、role_code、role_name
4.2生成基础代码
操作同上,这里只贴出重要的配置图
配置员工表(参考原型)
配置角色表(无原型)
4.3 人员列表改造
4.3.1基础页面
需求
参考页面原型,完成基础布局展示改造
代码实现
在emp/index.vue视图组件中修改
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 <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-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-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 >
在EmpServiceImpl中新增和修改时,补充区域名称和角色信息
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 @Autowired private RegionMapper regionMapper;@Autowired private RoleMapper roleMapper;@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) ; } @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) ; }
4.3.2 同步存储
实现思路
实现此功能方案: **同步存储:**在员工表中有区域名称的冗余字段,在更新区域表的同时,同步更新员工表中区域名称。
优点:由于是单表查询操作,查询列表效率最高。
缺点:需要在区域修改时修改员工表中的数据,有额外的开销,数据也可能不一致。
sql
1 2 -- 根据区域id修改区域名称 update tb_emp set region_name ='北京市奥体中心' where region_id =5
EmpMapper
1 2 3 4 5 6 7 8 @Update ("update tb_emp set region_name=#{regionName} where region_id=#{regionId}" )int updateByRegionId (@Param ("regionName" ) String regionName, @Param ("regionId" ) Long regionId);
RegionServiceImpl
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 @Autowired private EmpMapper empMapper;@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.4 文件存储
4.4.1 本地存储(不考虑)
4.4.2 阿里云OSS(SSM+Springboot基础篇讲过了这里不再提及)
4.4.3x-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中引入依赖
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 <dependency > <groupId > org.dromara.x-file-storage</groupId > <artifactId > x-file-storage-spring</artifactId > <version > 2.1.0</version > </dependency > <dependency > <groupId > com.aliyun.oss</groupId > <artifactId > aliyun-sdk-oss</artifactId > <version > 3.16.1</version > </dependency >
2)在dkd-admin的application.yml 配置文件中先添加以下基础配置,再添加对应平台的配置
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 dromara: x-file-storage: default-platform: aliyun-oss-1 thumbnail-suffix: ".min.jpg" aliyun-oss: - platform: aliyun-oss-1 enable-storage: true access-key: ? ? secret-key: ? ? end-point: ? ? bucket-name: ? ? domain: ? ? base-path: dkd-images/
3)在dkd-admin的启动类上加上@EnableFileStorage注解
1 2 3 4 5 6 7 8 9 10 11 @EnableFileStorage @SpringBootApplication (exclude = { DataSourceAutoConfiguration.class })public class DkdApplication { public static void main (String[] args) { SpringApplication .run (DkdApplication.class, args); System .out .println ("(♥◠‿◠)ノ゙ 帝可得启动成功 ლ(´ڡ`ლ)゙" ); } }
4)修改若依默认上传图片代码 找到ruoyi-admin模块中的com.ruoyi.web.controller.common.CommonController类 ,修改单个文件上传的方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 @Autowired private FileStorageService fileStorageService; @PostMapping("/upload" ) public AjaxResult uploadFile(MultipartFile file ) throws Exception { try { String objectName = LocalDate.now().format (DateTimeFormatter.ofPattern("yyyy/MM/dd" )) + "/" ; FileInfo fileInfo = fileStorageService.of(file ).setPath(objectName).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()); } }
5)联调测试,重启后台程序,前端上传图片操作,F12 抓包工具查看效果:
5.设备管理
5.1需求说明
点位管理主要涉及到三个功能模块,业务流程如下:
新增设备类型 : 允许管理员定义新的售货机型号,包括其规格和容量。
新增设备 : 在新的设备类型定义后,系统应允许添加新的售货机实例,并将它们分配到特定的点位。
新增货道 : 对于每个新添加的设备,系统应支持定义新的货道,后期用于关联相应的商品SKU。
对于设备和其他管理数据,下面是示意图:
关系字段:vm_type_id、node_id、vm_id
数据字典:vm_status(0未投放、1运营、3撤机)
冗余字段:addr、business_type、region_id、partner_id(简化查询接口、提高查询效率)
5.2 生成基础代码(略)
5.3设备类型改造
5.3.1基础页面
需求
参考页面原型,完成基础布局展示改造
代码实现
在vmType/index.vue视图组件中修改
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 <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 >
5.4设备管理改造
5.4.1基础页面
需求
参考页面原型,完成基础布局展示改造
代码实现
刷新设备表数据
1 2 3 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视图组件中修改
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 <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 >
5.4.2 新增设备
需求
新增设备时,补充设备表其他字段信息,还需要根据售货机类型创建所属货道
我们了解到在新增设备时,添加设备和货道表,还包含点位和设备类型的查询,共涉及到四张表的操作。 这个过程需要我们仔细处理每个字段,确保数据的一致性和完整性
VendingMachineServiceImpl
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 @Autowired private INodeService nodeService;@Autowired private IVmTypeService vmTypeService;@Autowired private IChannelService channelService;@Transactional @Override public int insertVendingMachine (VendingMachine vendingMachine) { String innerCode = UUIDUtils.getUUID(); vendingMachine.setInnerCode(innerCode); VmType vmType = vmTypeService.selectVmTypeById(vendingMachine.getVmTypeId()); vendingMachine.setChannelMaxCapacity(vmType.getChannelMaxCapacity()); Node node = nodeService.selectNodeById(vendingMachine.getNodeId()); BeanUtil.copyProperties(node, vendingMachine, "id" ); vendingMachine.setAddr(node.getAddress()); vendingMachine.setVmStatus(DkdContants.VM_STATUS_NODEPLOY); 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()); channel.setInnerCode(vendingMachine.getInnerCode()); channel.setMaxCapacity(vmType.getChannelMaxCapacity()); channel.setCreateTime(DateUtils.getNowDate()); channel.setUpdateTime(DateUtils.getNowDate()); channelList.add(channel); } } channelService.batchInsertChannel(channelList); return result; }
ChannelMapper接口和xml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 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
1 2 3 4 5 6 public int batchInsertChannel (List<Channel> channelList) ;
ChannelServiceImpl
1 2 3 4 5 6 7 8 9 @Override public int batchInsertChannel (List<Channel> channelList) { return channelMapper.batchInsertChannel (channelList) ; }
5.4.3修改设备
需求
修改设备时,根据点位同步更新冗余字段信息
根据前端提交的点位ID,后端需要查询点位表,来获取点位的详细信息,包括详细地址、商圈类型、区域ID和合作商ID,获取到点位信息后,我们需要更新设备表中的相关冗余字段。
VendingMachineServiceImpl
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 @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.5 设备状态改造
5.5.1 创建视图组件
创建vmStatus/index.vue视图组件
5.5.2 创建二级菜单
5.5.3改造视图组件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 <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" > {{ 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" ;import { ref } from "vue" ;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 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` ) } 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 ();getNodeList ();getPartnerList ();getVmTypeList ();getList ();</script >
5.6点位查看详情
在node/index.vue视图组件中修改
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 <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" > <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 { vm_status } = proxy.useDict ('vm_status' ); const nodeOpen = ref (false );const vmList = ref ([]);function getNodeInfo (row ) { loadAllParams.nodeId = row.id ; listVm (loadAllParams).then (response => { vmList.value = response.rows ; nodeOpen.value = true ; }); } </script >
6.策略管理
6.1需求说明
策略管理主要涉及到二个功能模块,业务流程如下:
新增策略 : 允许管理员定义新的策略,包括策略的具体内容和参数(如折扣率)
策略分配 : 将策略分配给一个或多个售货机。
对于策略和其他管理数据,下面是示意图:
6.2生成基础代码(略)
6.3策略管理改造
6.3.1基础页面
需求
参考页面原型,完成基础布局展示改造
代码实现
在policy/index.vue视图组件中修改
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 <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 ="1" :max ="100" :precision ="0" v-model ="form.discount" 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 >
6.3.2 查看详情
需求
点击查看详情,展示策略名称和该策略下的设备列表
代码实现
在policy/index.vue视图组件中修改
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 <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 ) { form.value = row; loadAllParams.policyId = row.policyId ; listVm (loadAllParams).then (response => { vmList.value = response.rows ; policyOpen.value = true ; }); } </script >
6.4设备策略分配
6.4.1需求
在设备管理页面中点击策略,对设备设置一个固定折扣,用于营销作用
6.4.2代码实现
在vm/index.vue视图组件中修改
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 <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 ; getList (); }); } else { addVm (form.value ).then (response => { proxy.$modal .msgSuccess ("新增成功" ); open.value = false ; getList (); }); } } }); } const policyList = ref ([]);const policyOpen = ref (false );function handleUpdatePolicy (row ) { form.value .id = row.id ; form.value .policyId = row.policyId ; listPolicy (loadAllParams).then ((response ) => { policyList.value = response.rows ; policyOpen.value = true ; }); } </script >
在VendingMachineServiceImpl中修改
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 @Override public int updateVendingMachine (VendingMachine vendingMachine) { if (vendingMachine.getNodeId()!=null ) { Node node = nodeService.selectNodeById(vendingMachine.getNodeId()); BeanUtil.copyProperties(node,vendingMachine,"id" ); vendingMachine.setAddr(node.getAddress()); } vendingMachine.setUpdateTime(DateUtils.getNowDate()); return vendingMachineMapper.updateVendingMachine (vendingMachine) ; }
7.商品管理
7.1 需求说明
点位管理主要涉及到三个功能模块,业务流程如下:
新增商品类型 : 定义商品的不同分类,如饮料、零食、日用品等。
新增商品 : 添加新的商品信息,包括名称、规格、价格、类型等。
设备货道管理 : 将商品与售货机的货道关联,管理每个货道的商品信息。
对于商品和其他管理数据,下面是示意图:
关系字段:class_id、sku_id、vm_id
7.2 生成基础代码(略)
7.3商品类型改造
7.3.1 基础页面
需求
参考页面原型,完成基础布局展示改造
7.3.2 代码实现
在skuClass/index.vue视图组件中修改
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 <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 ="50" align ="center" prop ="classId" /> <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" @click ="handleUpdate(scope.row)" v-hasPermi ="['manage:skuClass:edit']" > 修改</el-button > <el-button link type ="primary" @click ="handleDelete(scope.row)" v-hasPermi ="['manage:skuClass:remove']" > 删除</el-button > </template > </el-table-column > </el-table >
修改全局异常处理器,添加以下内容
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 /** * 数据完整性异常 */ @ExceptionHandler(DataIntegrityViolationException.class ) public AjaxResult handelDataIntegrityViolationException(DataIntegrityViolationException e) { if (e.getMessage().contains ("foreign" )) { return AjaxResult.error ("无法删除,有其他数据引用" ); } if (e.getMessage().contains ("Duplicate" )){ return AjaxResult.error ("无法保存,名称已存在" ); } return AjaxResult.error ("数据完整性异常,请联系管理员" );}
7.4 商品管理改造
7.4.1 基础页面
需求
参考页面原型,完成基础布局展示改造
7.4.2代码实现
在sku/index.vue视图组件中修改
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 <el-form :model ="queryParams" ref ="queryRef" :inline ="true" v-show ="showSearch" label-width ="68px" > <el-form-item label ="商品名称" prop ="skuName" > <el-input v-model ="queryParams.skuName" placeholder ="请输入商品名称" clearable @keyup.enter ="handleQuery" /> </el-form-item > <el-form-item > <el-button type ="primary" 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 ="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 skuClassList" :key ="item.classI" > <span v-if ="item.classId == scope.row.classId" > {{ item.className }} </span > </div > </template > </el-table-column > <el-table-column label ="创建时间" align ="center" prop ="createTime" width ="180" > <template #default ="scope" > <span > {{ parseTime (scope.row.createTime , '{y}-{m}-{d} {h}:{i}:{s}' ) }} </span > </template > </el-table-column > <el-table-column label ="操作" align ="center" class-name ="small-padding fixed-width" > <template #default ="scope" > <el-button link type ="primary" @click ="handleUpdate(scope.row)" v-hasPermi ="['manage:sku:edit']" > 修改</el-button > <el-button link type ="primary" @click ="handleDelete(scope.row)" v-hasPermi ="['manage:sku:remove']" > 删除</el-button > </template > </el-table-column > </el-table > <el-dialog :title ="title" v-model ="open" width ="500px" append-to-body > <el-form ref ="skuRef" :model ="form" :rules ="rules" label-width ="80px" > <el-form-item label ="商品名称" prop ="skuName" > <el-input v-model ="form.skuName" placeholder ="请输入商品名称" /> </el-form-item > <el-form-item label ="品牌" prop ="brandName" > <el-input v-model ="form.brandName" placeholder ="请输入品牌" /> </el-form-item > <el-form-item label ="商品价格" prop ="price" > <el-input-number :min ="0.01" :max ="999.99" :precision ="2" :step ="0.5" v-model ="form.price" placeholder ="请输入" /> 元 </el-form-item > <el-form-item label ="商品类型" prop ="classId" > <el-select v-model ="form.classId" placeholder ="请选择商品类型" > <el-option v-for ="item in skuClassList" :key ="item.classId" :label ="item.className" :value ="item.classId" /> </el-select > </el-form-item > <el-form-item label ="规格" prop ="unit" > <el-input v-model ="form.unit" placeholder ="请输入规格" /> </el-form-item > <el-form-item label ="商品图片" prop ="skuImage" > <image-upload v-model ="form.skuImage" /> </el-form-item > </el-form > <template #footer > <div class ="dialog-footer" > <el-button type ="primary" @click ="submitForm" > 确 定</el-button > <el-button @click ="cancel" > 取 消</el-button > </div > </template > </el-dialog > <script setup name ="Sku" > import { listSkuClass } from "@/api/manage/skuClass" ;import { loadAllParams } from "@/api/page" ;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 = "修改商品管理" ; }); } 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 (); }); } } }); } const skuClassList = ref ([]);function getSkuClassList ( ) { listSkuClass (loadAllParams).then (response => { skuClassList.value = response.rows ; }); } getSkuClassList ();</script >
7.4.2 商品删除
需求
在删除商品时,需要判断此商品是否被售货机的货道关联,如果关联则无法删除
物理外键约束:通过在子表中添加一个外键列和约束,该列与父表的主键列相关联,由数据库维护数据的一致性和完整性
逻辑外键约束:在不使用数据库外键约束的情况下,通常在应用程序中通过代码来检查和维护数据的一致性和完整性
使用逻辑外键约束的原因:我们在新增售货机货道记录时暂不指定商品,货道表中的SKU_ID有默认值0,而这个值在商品表中并不存在,那么物理外键约束会阻止货道表的插入,因为0并不指向任何有效的商品记录
SkuServiceImpl
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 @Autowired private IChannelService channelService;@Override public int deleteSkuBySkuIds (Long[] skuIds) { int count = channelService.countChannelBySkuIds(skuIds); if (count>0 ){ throw new ServiceException("此商品被货道关联,无法删除" ); } return skuMapper.deleteSkuBySkuIds (skuIds) ; }
IChannelService
1 2 3 4 5 6 int countChannelBySkuIds (Long[] skuIds) ;
ChannelServiceImpl
1 2 3 4 5 6 7 8 9 @Override public int countChannelBySkuIds (Long[] skuIds) { return channelMapper.countChannelBySkuIds (skuIds) ; }
ChannelMapper接口和xml
1 2 3 4 5 6 7 8 9 10 11 12 13 int countChannelBySkuIds (Long[] skuIds ) ;<select id="countChannelBySkuIds" resultType="java.lang.Integer" > select count (1 ) from tb_channel where sku_id in <foreach item ="id" collection="array" open="(" separator="," close=")" > #{id} </foreach > </select >
7.5批量导入
7.5.1 需求
点击导入数据弹出导入数据弹窗,实现商品的批量导入
7.5.2 前端
在sku/index.vue视图组件中修改
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 <! <el-col :span="1.5"> <el-button type ="warning" plain icon="Upload" @click="handleExcelImport" v-hasPermi="['manage:sku:add']">导入</el-button> </el-col> <! <el-dialog title="数据导入" v-model="excelOpen" width="400px" append-to -body> <el-upload ref ="uploadRef" class ="upload-demo" :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 class ="ml-3" type ="success" @click="submitUpload"> 上传 </el-button> <template #tip> <div class ="el-upload__tip"> 上传文件仅支持,xls/xlsx格式,文件大小不得超过1 M </div> </template > </el-upload> </el-dialog> <script setup name ="Sku"> import { getToken } from "@/utils/auth"; const excelOpen = ref (false ); function handleExcelImport() { excelOpen.value = true ; } const uploadExcelUrl = ref (import .meta.env.VITE_APP_BASE_API + "/manage/sku/import"); // 上传excel文件地址 const headers = ref ({ Authorization : "Bearer " + getToken() }); const uploadRef = ref ({}); function submitUpload() { uploadRef.value .submit() } const props = defineProps({ modelValue: [String, Object , Array ], // 大小限制(MB) fileSize: { type : Number, default : 1 , }, // 文件类型, 例如["xls", "xlsx"] fileType: { type : Array , default : () => ["xls", "xlsx"], }, }); // 上传前loading加载 function handleBeforeUpload(file) { let isExcel = false ; if (props.fileType.length) { let fileExtension = ""; if (file.name.lastIndexOf(".") > -1 ) { fileExtension = file.name.slice (file.name.lastIndexOf(".") + 1 ); } isExcel = props.fileType.some (type => { if (file.type .indexOf(type ) > -1 ) return true ; if (fileExtension && fileExtension.indexOf(type ) > -1 ) return true ; return false ; }); } if (!isExcel) { proxy.$modal.msgError( `文件格式不正确, 请上传${props.fileType.join ("/")}格式文件!` ); return false ; } if (props.fileSize) { const isLt = file.size / 1024 / 1024 < props.fileSize; if (!isLt) { proxy.$modal.msgError(`上传excel大小不能超过 ${props.fileSize} MB!`); return false ; } } proxy.$modal.loading("正在上传excel,请稍候..."); } // 上传失败 function handleUploadError() { proxy.$modal.msgError("上传excel失败"); uploadRef.value .clearFiles(); proxy.$modal.closeLoading(); } // 上传成功回调 function handleUploadSuccess(res, file) { if (res.code === 200 ) { proxy.$modal.msgSuccess("上传excel成功"); excelOpen.value = false ; getList(); }else { proxy.$modal.msgError("res.msg); } uploadRef.value.clearFiles(); proxy.$modal.closeLoading(); } </script>
7.5.3 后端
SkuController
1 2 3 4 5 6 7 8 9 10 11 @PreAuthorize("@ss.hasPermi('manage:sku:add')" ) @Log(title = "商品管理" , businessType = BusinessType.IMPORT) @PostMapping("/import" ) public AjaxResult excelImport(MultipartFile file) throws Exception { ExcelUtil<Sku> util = new ExcelUtil<Sku>(Sku.class ); List<Sku> skuList = util.importExcel(file.getInputStream()); return toAjax(skuService.insertSkus(skuList)); }
SKuMapper
1 2 3 4 5 6 7 8 9 10 11 12 13 14 /** * 批量新增商品管理 * @param skuList * @return 结果 */ public int insertSkus(List<Sku > skuList); <insert id ="insertSkus" parameterType ="java.util.List" useGeneratedKeys ="true" keyProperty ="skuId" > insert into tb_sku (sku_name, sku_image, brand_Name, unit, price, class_id) values <foreach item ="item" index ="index" collection ="list" separator ="," > (# {item .skuName} , # {item .skuImage} , # {item .brandName} , # {item .unit} , # {item .price} , # {item .classId} ) </foreach > </insert >
ISkuService
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 public int insertSkus (List<Sku> skuList) ;@Override public int insertSkus (List<Sku> skuList) { return skuMapper.insertSkus (skuList) ; }
7.6 EasyExcel
官方地址:https://easyexcel.alibaba.com/ Java解析、生成Excel比较有名的框架有Apache poi、jxl。但他们都存在一个严重的问题就是非常的耗内存,poi有一套SAX模式的API可以一定程度的解决一些内存溢出的问题,但POI还是有一些缺陷,比如07版Excel解压缩以及解压后存储都是在内存中完成的,内存消耗依然很大。 easyexcel重写了poi对07版Excel的解析,一个3M的excel用POI sax解析依然需要100M左右内存,改用easyexcel可以降低到几M,并且再大的excel也不会出现内存溢出;03版依赖POI的sax模式,在上层做了模型转换的封装,让使用者更加简单方便
7.6.1 项目集成
文档地址 集成easyexcel实现excel表格增强
1、dkd-common\pom.xml模块添加整合依赖
1 2 3 4 5 6 7 8 9 <dependency > <groupId > com.alibaba</groupId > <artifactId > easyexcel</artifactId > <version > 4.0.1</version > </dependency >
2、在dkd-common\模块的ExcelUtil.java新增easyexcel导出导入方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 public List<T> importEasyExcel(InputStream is) throws Exception{ return EasyExcel.read(is).head(clazz).sheet().doReadSync(); } public void exportEasyExcel (HttpServletResponse response, List<T> list, String sheetName) { try { EasyExcel.write(response.getOutputStream(), clazz).sheet(sheetName).doWrite(list); } catch (IOException e) { log.error ("导出EasyExcel异常{}" , e.getMessage()); } }
3、Sku.java 修改为@ExcelProperty注解
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 package com.dkd.manage.domain;import com.alibaba.excel.annotation .ExcelIgnoreUnannotated;import com.alibaba.excel.annotation .ExcelProperty;import com.alibaba.excel.annotation .write.style.ColumnWidth;import com.alibaba.excel.annotation .write.style.HeadFontStyle;import com.alibaba.excel.annotation .write.style.HeadRowHeight;import com.dkd.common.annotation .Excel;import com.dkd.common.core.domain.BaseEntity;import org.apache.commons.lang3.builder.ToStringBuilder;import org.apache.commons.lang3.builder.ToStringStyle;@ExcelIgnoreUnannotated @ColumnWidth(16) @HeadRowHeight(14) @HeadFontStyle(fontHeightInPoints = 11) public class Sku extends BaseEntity { private static final long serialVersionUID = 1L ; private Long skuId; @Excel(name = "商品名称" ) @ExcelProperty("商品名称" ) private String skuName; @Excel(name = "商品图片" ) @ExcelProperty("商品图片" ) private String skuImage; @Excel(name = "品牌" ) @ExcelProperty("品牌" ) private String brandName; @Excel(name = "规格(净含量)" ) @ExcelProperty("规格(净含量)" ) private String unit; @Excel(name = "商品价格" ) @ExcelProperty("商品价格" ) private Long price; @Excel(name = "商品类型Id" ) @ExcelProperty("商品类型Id" ) private Long classId; private Integer isDiscount; }
4、SkuController.java 改为importEasyExcel
1 2 3 4 5 6 7 8 9 10 11 @PreAuthorize("@ss.hasPermi('manage:sku:add')" ) @Log(title = "商品管理" , businessType = BusinessType.IMPORT) @PostMapping("/import" ) public AjaxResult excelImport(MultipartFile file) throws Exception { ExcelUtil<Sku> util = new ExcelUtil<Sku>(Sku.class ); List<Sku> skuList = util.importEasyExcel(file.getInputStream()); return toAjax(skuService.insertSkus(skuList)); }
5、SkuController.java 改为exportEasyExcel
1 2 3 4 5 6 7 8 9 10 11 @PreAuthorize ("@ss.hasPermi('manage:sku:export')" )@Log (title = "商品管理" , businessType = BusinessType.EXPORT)@PostMapping ("/export" )public void export (HttpServletResponse response, Sku sku) { List<Sku> list = skuService.selectSkuList(sku); ExcelUtil<Sku> util = new ExcelUtil<Sku>(Sku.class ); util.exportEasyExcel(response, list, "商品管理数据" ); }
7.7 货道关联商品
7.7.1 需求
对智能售货机内部的货道进行商品摆放的管理
此功能涉及四个后端接口
查询设备类型(已完成)
查询货道列表(待完成)
查询商品列表(已完成)
货道关联商品(待完成)
7.7.2 货道对话框
① 从资料中复制货道api请求js文件到api/manage目录下
② 从资料中复制货道的视图组件到views/manage/vm目录下
③ 修改设备index.vue
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 <el-button link type ="primary" @click ="handleGoods(scope.row)" v-hasPermi ="['manage:vm:edit']" > 货道</el-button > <ChannelDialog :goodVisible ="goodVisible" :goodData ="goodData" @handleCloseGood ="handleCloseGood" > </ChannelDialog > <script setup name ="Vm" > import ChannelDialog from './components/ChannelDialog.vue' ; const goodVisible = ref (false ); const goodData = ref ({}); const handleGoods = (row ) => { goodVisible.value = true ; goodData.value = row; }; const handleCloseGood = ( ) => { goodVisible.value = false ; }; </script > <style lang ="scss" scoped src ="./index.scss" > </style >
7.7.3 查询货道列表
ChannelVo
1 2 3 4 5 6 @Data public class ChannelVo extends Channel { private Sku sku; }
ChannelMapper和xml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 /** * 根据售货机编号查询货道列表 * * @param innerCode * @return ChannelVo集合 */ List<ChannelVo> selectChannelVoListByInnerCode(String innerCode); <resultMap type="ChannelVo" id ="ChannelVoResult" > <result property ="id" column="id" /> <result property ="channelCode" column="channel_code" /> <result property ="skuId" column="sku_id" /> <result property ="vmId" column="vm_id" /> <result property ="innerCode" column="inner_code" /> <result property ="maxCapacity" column="max_capacity" /> <result property ="currentCapacity" column="current_capacity" /> <result property ="lastSupplyTime" column="last_supply_time" /> <result property ="createTime" column="create_time" /> <result property ="updateTime" column="update_time" /> <association property ="sku" javaType="Sku" column="sku_id" select="com.dkd.manage.mapper.SkuMapper.selectSkuBySkuId" /> </resultMap> <select id ="selectChannelVoListByInnerCode" resultMap="ChannelVoResult" > <include refid="selectChannelVo" /> where inner_code = </select>
IChannelService接口和实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 List <ChannelVo > selectChannelVoListByInnerCode (String innerCode);@Override public List <ChannelVo > selectChannelVoListByInnerCode (String innerCode ) { return channelMapper.selectChannelVoListByInnerCode (innerCode); }
ChannelController
1 2 3 4 5 6 7 8 9 @PreAuthorize("@ss.hasPermi('manage:channel:list')" ) @GetMapping("/list/{innerCode}" ) public AjaxResult lisetByInnerCode(@PathVariable("innerCode" ) String innerCode) { List<ChannelVo> voList = channelService.selectChannelVoListByInnerCode(innerCode); return success(voList); }
7.7.4 货道关联商品
ChannelSkuDto
1 2 3 4 5 6 7 8 @Data public class ChannelSkuDto { private String innerCode; private String channelCode; private Long skuId; }
ChannelConfigDto
1 2 3 4 5 6 7 8 9 @Data public class ChannelConfigDto { private String innerCode; private List<ChannelSkuDto> channelList; }
ChannelMapper和xml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 /** * 根据售货机编号和货道编号查询货道信息 * @param innerCode * @param channelCode * @return 售货机货道 */ @Select("select * from tb_channel where inner_code =# {innerCode} and channel_code=# {channelCode} ") Channel getChannelInfo(@Param("innerCode") String innerCode, @Param("channelCode") String channelCode); /** * 批量修改货道 * @param list * @return 结果 */ int batchUpdateChannel(List<Channel > list); <update id ="batchUpdateChannel" parameterType ="java.util.List" > <foreach item ="channel" collection ="list" separator =";" > UPDATE tb_channel <set > <if test ="channel.channelCode != null and channel.channelCode != ''" > channel_code = # {channel.channelCode} ,</if > <if test ="channel.skuId != null" > sku_id = # {channel.skuId} ,</if > <if test ="channel.vmId != null" > vm_id = # {channel.vmId} ,</if > <if test ="channel.innerCode != null and channel.innerCode != ''" > inner_code = # {channel.innerCode} ,</if > <if test ="channel.maxCapacity != null" > max_capacity = # {channel.maxCapacity} ,</if > <if test ="channel.currentCapacity != null" > current_capacity = # {channel.currentCapacity} ,</if > <if test ="channel.lastSupplyTime != null" > last_supply_time = # {channel.lastSupplyTime} ,</if > <if test ="channel.createTime != null" > create_time = # {channel.createTime} ,</if > <if test ="channel.updateTime != null" > update_time = # {channel.updateTime} ,</if > </set > WHERE id = # {channel.id} </foreach > </update >
application-druid.yml
允许mybatis框架在单个请求中发送多个sql语句
IChannelService接口和实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 int setChannel (ChannelConfigDto channelConfigDto) ;@Override public int setChannel (ChannelConfigDto channelConfigDto) { List<Channel> list = channelConfigDto.getChannelList().stream().map(c -> { Channel channel = channelMapper.getChannelInfo(c.getInnerCode(), c.getChannelCode()); if (channel != null ) { channel.setSkuId(c.getSkuId()); channel.setUpdateTime(DateUtils.getNowDate()); } return channel; }).collect(Collectors.toList()); return channelMapper.batchUpdateChannel (list) ; }
ChannelController
1 2 3 4 5 6 7 8 9 @PreAuthorize ("@ss.hasPermi('manage:channel:edit')" )@Log (title = "售货机货道" , businessType = BusinessType.UPDATE )@PutMapping ("/config" )public AjaxResult setChannel (@RequestBody ChannelConfigDto channelConfigDto) { return toAjax (channelService.setChannel (channelConfigDto)); }
8.工单管理
8.1 需求说明
工单是一种专业名词,是指用于记录、处理、跟踪一项工作的完成情况。
管理人员登录后台系统选择创建工单,在工单类型里选择合适的工单类型,在设备编号里输入正确的设备编号。
工作人员在运营管理App可以看到分配给自己的工单,根据实际情况选择接收工单并完成,或者拒绝/取消工单。
立可得工单分为两大类 :
运营工单:运营人员来维护售货机商品,即补货工单。
运维工单:运维人员来维护售货机设备,即投放工单、撤机工单、维修工单。
工单有四种状态: 1 待处理 2 进行中 3 已取消 4 已完成
对于工单和其他管理数据,下面是示意图:
关系字段:task_id、 product_type_id、inner_code、user_id、assignor_id、region_id
数据字典:task_status(1待办、2进行、3取消、4完成)
数据字典:create_type(0自动、1手动)
PS:运营的工单包含补货信息,运维工单没有,所以运营工单需要单独创建补货工单
创建所有工单,都会在工单表和工单明细表插入记录吗? 创建运维类工单只会在工单表插入数据。 创建运营类工单(补货工单)会在工单表和工单明细表插入数据。 task_code和task_id有什么区别? task_code是工单编号,具有业务规则 ,格式为年月日+当日序号。 task_id 为工单表数据唯一标识。 工单表的user_id和assignor_id分别是做什么的? user_id是工单执行人的id(运维或运营) assignor_id是工单指派人的id(创建工单的人)
8.2基础代码生成(略)
8.3查询工单列表
8.3.1需求
运营和运营工单共享一套后端接口,通过特定的查询条件区分工单类型,并在返回结果中包含工单类型的详细信息
8.3.2 TaskVo
1 2 3 4 5 6 @Data public class TaskVo extends Task { private TaskType taskType; }
8.3.3 TaskMapper
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 /** * 查询运维工单列表 * * @param task 运维工单 * @return TaskVo集合 */ List<TaskVo > selectTaskVoList(Task task); <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 ="selectTaskVoList" 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 > order by create_time desc </where > </select >
8.3.4 ITaskService
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 /** * 查询运维工单列表 * @param task * @return TaskVo集合 */ List<TaskVo> selectTaskVoList(Task task ); /** * 查询运维工单列表 * @param task * @return TaskVo集合 */ @Override public List<TaskVo> selectTaskVoList(Task task ) { return taskMapper.selectTaskVoList(task ); }
8.3.5 TaskController
1 2 3 4 5 6 7 8 9 10 11 @PreAuthorize("@ss.hasPermi('manage:task:list')" ) @GetMapping("/list" ) public TableDataInfo list(Task task){ startPage(); List<TaskVo> voList = taskService.selectTaskVoList(task); return getDataTable(voList); }
8.4获取运营人员列表
8.4.1需求
根据售货机编号获取负责当前区域下的运营人员列表
8.4.2 VendingMachineMapper
1 2 3 4 5 6 7 8 @Select ("select * from tb_vending_machine where inner_code=#{innerCode}" )VendingMachine selectVendingMachineByInnerCode (String innerCode) ;
8.4.3 IVendingMachineService
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 VendingMachine selectVendingMachineByInnerCode (String innerCode);@Override public VendingMachine selectVendingMachineByInnerCode (String innerCode ) { return vendingMachineMapper.selectVendingMachineByInnerCode (innerCode); }
8.4.4 DkdContants
1 2 3 4 5 6 7 8 9 public static final Long EMP_STATUS_NORMAL = 1 L;public static final Long EMP_STATUS_DISABLE = 0 L;
8.4.5 EmpController
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 @Autowired private IVendingMachineService vendingMachineService;@PreAuthorize("@ss.hasPermi('manage:emp:list')") @GetMapping("/businessList/{innerCode}") public AjaxResult businessList (@PathVariable("innerCode") String innerCode) { VendingMachine vm = vendingMachineService.selectVendingMachineByInnerCode(innerCode); if (vm == null ) { return error(); } Emp empParam = new Emp (); empParam.setRegionId(vm.getRegionId()); empParam.setStatus(DkdContants.EMP_STATUS_NORMAL); empParam.setRoleCode(DkdContants.ROLE_CODE_BUSINESS); return success(empService.selectEmpList(empParam)); }
8.5 获取运维人员列表
8.5.1 需求
根据售货机编号获取负责当前区域下的运维人员列表
8.5.2 EmpController
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 @PreAuthorize("@ss.hasPermi('manage:emp:list')") @GetMapping("/operationList/{innerCode}") public AjaxResult getOperationList (@PathVariable("innerCode") String innerCode) { VendingMachine vm = vendingMachineService.selectVendingMachineByInnerCode(innerCode); if (vm == null ) { return error("售货机不存在" ); } Emp empParam = new Emp (); empParam.setRegionId(vm.getRegionId()); empParam.setStatus(DkdContants.EMP_STATUS_NORMAL); empParam.setRoleCode(DkdContants.ROLE_CODE_OPERATOR); return success(empService.selectEmpList(empParam)); }
8.6 新增工单
8.6.1 需求
本系统中有两类工单需要创建,分别是:
运维工单 :运维工单主要是对售货机的操作,又可以细分为投放工单、撤机工单、维修工单
运营工单 :运营工单主要是对货物的操作,只有一种就是补货工单
8.6.2 思路
新增工单时序图
新增工单业务流程图
8.6.3 TaskDetailsDto
1 2 3 4 5 6 7 8 9 @Data public class TaskDetailsDto { private String channelCode; private Long expectCapacity; private Long skuId; private String skuName; private String skuImage; }
8.6.4 TaskDto
1 2 3 4 5 6 7 8 9 10 11 @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; }
8.6.5 TaskDetailsMapper
1 2 3 4 5 6 7 8 9 10 11 12 13 14 /** * 批量新增工单详情 * @param taskDetailsList * @return 结果 */ int batchInsertTaskDetails(List<TaskDetails > taskDetailsList); <insert id ="batchInsertTaskDetails" > INSERT INTO tb_task_details(task_id, channel_code, expect_capacity, sku_id, sku_name, sku_image) VALUES <foreach item ="item" collection ="list" separator ="," > (# {item .taskId} , # {item .channelCode} , # {item .expectCapacity} , # {item .skuId} , # {item .skuName} , # {item .skuImage} ) </foreach > </insert >
8.6.6 ITaskDetailsService
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 int batchInsertTaskDetails (List<TaskDetails> taskDetailsList) ;@Override public int batchInsertTaskDetails (List<TaskDetails> taskDetailsList) { return taskDetailsMapper.batchInsertTaskDetails (taskDetailsList) ; }
8.6.7 ITaskService
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 int insertTaskDto (TaskDto taskDto) ;@Autowired private IVendingMachineService vendingMachineService;@Autowired private IEmpService empService;@Autowired private ITaskDetailsService taskDetailsService;@Autowired private RedisTemplate redisTemplate;@Transactional @Override public int insertTaskDto (TaskDto taskDto) { VendingMachine vm = vendingMachineService.selectVendingMachineByInnerCode(taskDto.getInnerCode()); if (vm == null ) { throw new ServiceException ("设备不存在" ); } checkCreateTask(vm.getVmStatus(), taskDto.getProductTypeId()); hasTask(taskDto); Emp emp = empService.selectEmpById(taskDto.getUserId()); if (emp == null ) { throw new ServiceException ("员工不存在" ); } if (!emp.getRegionId().equals(vm.getRegionId())) { throw new ServiceException ("员工区域与设备区域不一致,无法处理此工单" ); } Task task = BeanUtil.copyProperties(taskDto, Task.class); task.setTaskStatus(DkdContants.TASK_STATUS_CREATE); task.setUserName(emp.getUserName()); task.setRegionId(vm.getRegionId()); task.setAddr(vm.getAddr()); task.setCreateTime(DateUtils.getNowDate()); task.setTaskCode(generateTaskCode()); int taskResult = taskMapper.insertTask(task); if (taskDto.getProductTypeId().equals(DkdContants.TASK_TYPE_SUPPLY)) { List<TaskDetailsDto> details = taskDto.getDetails(); if (CollUtil.isEmpty(details)) { throw new ServiceException ("补货工单详情不能为空" ); } List<TaskDetails> taskDetailsList = details.stream().map(dto -> { TaskDetails taskDetails = BeanUtil.copyProperties(dto, TaskDetails.class); taskDetails.setTaskId(task.getTaskId()); return taskDetails; }).collect(Collectors.toList()); taskDetailsService.batchInsertTaskDetails(taskDetailsList); } return taskResult; } public String generateTaskCode () { String dateStr = DateUtils.getDate().replaceAll("-" , "" ); String key = "dkd.task.code." + dateStr; if (!redisTemplate.hasKey(key)) { redisTemplate.opsForValue().set(key, 1 , Duration.ofDays(1 )); return dateStr + "0001" ; } return dateStr+StrUtil.padPre(redisTemplate.opsForValue().increment(key).toString(),4 ,'0' ); } private void hasTask (String innerCode, Long productTypeId) { Task taskParam = new Task (); taskParam.setInnerCode(innerCode); taskParam.setProductTypeId(productTypeId); taskParam.setTaskStatus(DkdContants.TASK_STATUS_PROGRESS); List<Task> taskList = taskMapper.selectTaskList(taskParam); if (CollUtil.isNotEmpty(taskList)) { throw new ServiceException ("该设备有未完成的同类型工单,不能重复创建" ); } } private void checkCreateTask (Long vmStatus, Long productTypeId) { if (productTypeId == DkdContants.TASK_TYPE_DEPLOY && vmStatus == DkdContants.VM_STATUS_RUNNING) { throw new ServiceException ("该设备状态为运行中,无法进行投放" ); } if (productTypeId == DkdContants.TASK_TYPE_REPAIR && vmStatus != DkdContants.VM_STATUS_RUNNING) { throw new ServiceException ("该设备状态不是运行中,无法进行维修" ); } if (productTypeId == DkdContants.TASK_TYPE_SUPPLY && vmStatus != DkdContants.VM_STATUS_RUNNING) { throw new ServiceException ("该设备状态不是运行中,无法进行补货" ); } if (productTypeId == DkdContants.TASK_TYPE_REVOKE && vmStatus != DkdContants.VM_STATUS_RUNNING) { throw new ServiceException ("该设备状态不是运行中,无法进行撤机" ); } }
8.6.8 TaskController
1 2 3 4 5 6 7 8 9 10 11 12 @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)); }
8.7 取消工单
8.7.1 需求
对于未完成的工单,管理员可以进行取消操作
8.7.2 TaskController
1 2 3 4 5 6 7 8 9 @PreAuthorize ("@ss.hasPermi('manage:task:edit')" )@Log (title = "工单" , businessType = BusinessType.UPDATE )@PutMapping ("/cancel" )public AjaxResult cancelTask (@RequestBody Task task) { return toAjax (taskService.cancelTask (task)); }
8.7.3 ITaskService
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 int cancelTask(Task task );@Override public int cancelTask(Task task ) { Task taskDb = taskMapper.selectTaskByTaskId(task .getTaskId()); if (taskDb.getTaskStatus().equals(DkdContants.TASK_STATUS_CANCEL)) { throw new ServiceException("该工单已取消了,不能再次取消" ); } if (taskDb.getTaskStatus().equals(DkdContants.TASK_STATUS_FINISH)) { throw new ServiceException("该工单已完成了,不能取消" ); } task .setTaskStatus(DkdContants.TASK_STATUS_CANCEL); task .setUpdateTime(DateUtils.getNowDate()); return taskMapper.updateTask(task ); }
8.8 查询补货详情
8.8.1 需求
运营工单页面可以查看补货详情
8.8.2 TaskDetailsController
1 2 3 4 5 6 7 8 9 10 @PreAuthorize("@ss.hasPermi('manage:taskDetails:list')" ) @GetMapping(value = "/byTaskId/{taskId}" ) public AjaxResult byTaskId(@PathVariable("taskId" ) Long taskId) { TaskDetails taskDetailsParam = new TaskDetails(); taskDetailsParam.setTaskId(taskId); return success(taskDetailsService.selectTaskDetailsList(taskDetailsParam)); }
8.9 Knife4j
如果不习惯使用swagger可以使用前端UI的增强解决方案knife4j,对比swagger相比有以下优势,友好界面,离线文档,接口排序,安全控制,在线调试,文档清晰,注解增强,容易上手。 1、ruoyi-common\pom.xml模块添加整合依赖
1 2 3 4 5 6 7 8 9 <dependency > <groupId > com.github.xiaoymin</groupId > <artifactId > knife4j-spring-boot-starter</artifactId > <version > 3.0.3</version > </dependency >
2、views/tool/swagger/index.vue修改跳转访问地址(已完成)
1 const url = ref (import.meta .env .VITE_APP_BASE_API + "/doc.html" )
3、登录系统,访问菜单系统工具/系统接口,出现如下图表示成功。
4、TaskDetailsController添加swagger注解
@Api: 用于类级别,描述API的标签和描述。
@ApiOperation: 用于方法级别,描述一个HTTP操作。
@ApiParam: 用于参数级别,描述请求参数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 @RestController @RequestMapping ("/manage/taskDetails" )@Api (tags = "工单详情" )public class TaskDetailsController extends BaseController { @ApiOperation ("根据工单id查看工单补货详情" ) @PreAuthorize ("@ss.hasPermi('manage:taskDetails:query')" ) @GetMapping (value = "/byTaskId/{taskId}" ) public R <List <TaskDetails >> byTaskId( @ApiParam (value = "工单ID" , required = true ) @PathVariable Long taskId) { TaskDetails taskDetailsParam = new TaskDetails (); taskDetailsParam.setTaskId(taskId); return R .ok(taskDetailsService.selectTaskDetailsList(taskDetailsParam)); } }
注意:若依框架的AjaxResult由于继承自HashMap导致与Swagger和knife4j不兼容的问题,选择替换返回值类型为R以解决Swagger解析问题,减少整体改动量。
5、TaskDetails实体类添加swagger注解
@ApiModelProperty注解来描述每个字段的意义
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 @ApiModel(description = "任务详情对象" ) public class TaskDetails extends BaseEntity { private static final long serialVersionUID = 1L ; @ApiModelProperty(value = "任务详情ID" , hidden = true) private Long detailsId; @ApiModelProperty(value = "工单Id" ) @Excel(name = "工单Id" ) private Long taskId; @ApiModelProperty(value = "货道编号" ) @Excel(name = "货道编号" ) private String channelCode; @ApiModelProperty(value = "补货期望容量" ) @Excel(name = "补货期望容量" ) private Long expectCapacity; @ApiModelProperty(value = "商品Id" ) @Excel(name = "商品Id" ) private Long skuId; @ApiModelProperty(value = "商品名称" ) @Excel(name = "${comment} " , readConverterExp = "$column .readConverterExp()" ) private String skuName; @ApiModelProperty(value = "商品图片" ) @Excel(name = "${comment} " , readConverterExp = "$column .readConverterExp()" ) private String skuImage; }
6、接口测试
7、设置文档信息
9.运营管理App
9.1 Android模拟器
本项目的App客户端部分已经由前端团队进行开发完成,并且以apk的方式提供出来,供我们测试使用,如果要运行apk,需要先安装安卓的模拟器。
9.2 Java后端
本项目运营管理App的java后端已开发完成,在资料中已提供源码,导入idea中即可
9.3 功能测试
9.3.1 投放工单
帝可得管理端,创建新设备
帝可得管理端,创建投放工单
运营管理App端登录负责此工单员工,即可查看待办工单,可以选择拒绝、接受
如果点击接受,帝可得管理端工单状态改为进行
在进行工单界面,可以点击查看详情,选择取消、完成
如果点击完成工单,帝可得管理端工单状态改为完成
帝可得管理端设备状态改为运营,表示设备投放成功
9.3.2 补货工单
帝可得管理端,货道关联商品(大家练习时,可以全部关联)
帝可得管理端,创建补货工单
运营管理App端登录负责此工单员工,即可查看待办工单,可以选择拒绝、接受
如果点击接受,帝可得管理端工单状态改为进行
在进行工单界面,可以点击查看详情,选择取消、完成
如果点击完成工单,帝可得管理端工单状态改为完成
数据库货道表的库存已同步更新
9.4 源码介绍
技术栈:SpringBoot+MybatisPlus+阿里云短信
10.设备屏幕端
商品列表–选择支付方式–显示支付二维码–用户扫码完成支付
10.1设备屏幕
本项目的设备屏幕客户端部分已经由前端团队进行开发完成,在资料中已提供源码,双击打开index.html即可
10.2 Java后端
10.3功能测试
在设备屏幕端加上innerCode=设备编号,即可显示当前设备货道信息
帝可得管理端,设备策略分配,设置折扣信息
再次访问设备屏幕端,价格就是折扣的了
10.4支付出货流程
我们能够从屏幕上看到支付二维码,其实是经历了“长途跋涉” ,屏幕端实际上是一个H5页面,向后端发起支付请求,订单服务首先会创建订单,然后调用第三方支付来获得用于生成支付二维码的链接。 然后订单微服务将二维码链接返回给屏幕端,屏幕端生成二维码图片展示。
用户看到二维码后,拿出手机扫码支付,此时第三方支付平台确认用户支付成功后会回调订单服务。订单服务收到回调信息后修改订单状态,并通知设备发货。
10.5源码介绍
设备屏幕端的java后端技术栈:SpringBoot+MybatisPlus
11.订单管理
11.1 业务说明
用户在设备屏幕端下单后,系统将自动创建订单数据。后台管理人员可以查看这些订单信息,进行后续的管理操作。
对于订单数据模型,下面是示意图:
11.2代码实现
OrderController
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 package com.dkd.manage.controller;import java.util.List;import javax.servlet.http.HttpServletResponse;import org.springframework.security.access.prepost.PreAuthorize;import org.springframework.beans.factory.annotation .Autowired;import org.springframework.web.bind.annotation .GetMapping;import org.springframework.web.bind.annotation .PostMapping;import org.springframework.web.bind.annotation .PutMapping;import org.springframework.web.bind.annotation .DeleteMapping;import org.springframework.web.bind.annotation .PathVariable;import org.springframework.web.bind.annotation .RequestBody;import org.springframework.web.bind.annotation .RequestMapping;import org.springframework.web.bind.annotation .RestController;import com.dkd.common.annotation .Log;import com.dkd.common.core.controller.BaseController;import com.dkd.common.core.domain.AjaxResult;import com.dkd.common.enums.BusinessType;import com.dkd.manage.domain.Order;import com.dkd.manage.service.IOrderService;import com.dkd.common.utils.poi.ExcelUtil;import com.dkd.common.core.page.TableDataInfo;@RestController @RequestMapping("/manage/order" ) public class OrderController extends BaseController { @Autowired private IOrderService orderService; @PreAuthorize("@ss.hasPermi('manage:order:list')" ) @GetMapping("/list" ) public TableDataInfo list(Order order) { startPage(); List<Order> list = orderService.selectOrderList(order); return getDataTable(list); } @PreAuthorize("@ss.hasPermi('manage:order:export')" ) @Log(title = "订单管理" , businessType = BusinessType.EXPORT) @PostMapping("/export" ) public void export(HttpServletResponse response, Order order) { List<Order> list = orderService.selectOrderList(order); ExcelUtil<Order> util = new ExcelUtil<Order>(Order.class ); util.exportExcel(response, list, "订单管理数据" ); } @PreAuthorize("@ss.hasPermi('manage:order:query')" ) @GetMapping(value = "/{id}" ) public AjaxResult getInfo(@PathVariable("id" ) Long id) { return success(orderService.selectOrderById(id)); } @PreAuthorize("@ss.hasPermi('manage:order:add')" ) @Log(title = "订单管理" , businessType = BusinessType.INSERT) @PostMapping public AjaxResult add(@RequestBody Order order) { return toAjax(orderService.insertOrder(order)); } @PreAuthorize("@ss.hasPermi('manage:order:edit')" ) @Log(title = "订单管理" , businessType = BusinessType.UPDATE) @PutMapping public AjaxResult edit(@RequestBody Order order) { return toAjax(orderService.updateOrder(order)); } @PreAuthorize("@ss.hasPermi('manage:order:remove')" ) @Log(title = "订单管理" , businessType = BusinessType.DELETE) @DeleteMapping("/{ids}" ) public AjaxResult remove(@PathVariable Long [] ids) { return toAjax(orderService.deleteOrderByIds(ids)); } }
Order实体类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 package com.dkd .manage .domain ; import org.apache .commons .lang3 .builder .ToStringBuilder ;import org.apache .commons .lang3 .builder .ToStringStyle ;import com.dkd .common .annotation .Excel ;import com.dkd .common .core .domain .BaseEntity ;public class Order extends BaseEntity { private static final long serialVersionUID = 1L; private Long id; @Excel (name = "订单编号" ) private String orderNo; private String thirdNo; private String innerCode; private String channelCode; private Long skuId; @Excel (name = "商品名称" ) private String skuName; private Long classId; @Excel (name = "订单状态:0-待支付;1-支付完成;2-出货成功;3-出货失败;4-已取消" ) private Long status; @Excel (name = "支付金额" ) private Long amount; private Long price; private String payType; private Long payStatus; private Long bill; private String addr; private Long regionId; private String regionName; private Long businessType; private Long partnerId; private String openId; private Long nodeId; private String nodeName; private String cancelDesc; public void setId (Long id ) { this .id = id; } public Long getId ( ) { return id; } public void setOrderNo (String orderNo ) { this .orderNo = orderNo; } public String getOrderNo ( ) { return orderNo; } public void setThirdNo (String thirdNo ) { this .thirdNo = thirdNo; } public String getThirdNo ( ) { return thirdNo; } public void setInnerCode (String innerCode ) { this .innerCode = innerCode; } public String getInnerCode ( ) { return innerCode; } public void setChannelCode (String channelCode ) { this .channelCode = channelCode; } public String getChannelCode ( ) { return channelCode; } public void setSkuId (Long skuId ) { this .skuId = skuId; } public Long getSkuId ( ) { return skuId; } public void setSkuName (String skuName ) { this .skuName = skuName; } public String getSkuName ( ) { return skuName; } public void setClassId (Long classId ) { this .classId = classId; } public Long getClassId ( ) { return classId; } public void setStatus (Long status ) { this .status = status; } public Long getStatus ( ) { return status; } public void setAmount (Long amount ) { this .amount = amount; } public Long getAmount ( ) { return amount; } public void setPrice (Long price ) { this .price = price; } public Long getPrice ( ) { return price; } public void setPayType (String payType ) { this .payType = payType; } public String getPayType ( ) { return payType; } public void setPayStatus (Long payStatus ) { this .payStatus = payStatus; } public Long getPayStatus ( ) { return payStatus; } public void setBill (Long bill ) { this .bill = bill; } public Long getBill ( ) { return bill; } public void setAddr (String addr ) { this .addr = addr; } public String getAddr ( ) { return addr; } public void setRegionId (Long regionId ) { this .regionId = regionId; } public Long getRegionId ( ) { return regionId; } public void setRegionName (String regionName ) { this .regionName = regionName; } public String getRegionName ( ) { return regionName; } public void setBusinessType (Long businessType ) { this .businessType = businessType; } public Long getBusinessType ( ) { return businessType; } public void setPartnerId (Long partnerId ) { this .partnerId = partnerId; } public Long getPartnerId ( ) { return partnerId; } public void setOpenId (String openId ) { this .openId = openId; } public String getOpenId ( ) { return openId; } public void setNodeId (Long nodeId ) { this .nodeId = nodeId; } public Long getNodeId ( ) { return nodeId; } public void setNodeName (String nodeName ) { this .nodeName = nodeName; } public String getNodeName ( ) { return nodeName; } public void setCancelDesc (String cancelDesc ) { this .cancelDesc = cancelDesc; } public String getCancelDesc ( ) { return cancelDesc; } @Override public String toString ( ) { return new ToStringBuilder (this ,ToStringStyle .MULTI_LINE_STYLE ) .append ("id" , getId ()) .append ("orderNo" , getOrderNo ()) .append ("thirdNo" , getThirdNo ()) .append ("innerCode" , getInnerCode ()) .append ("channelCode" , getChannelCode ()) .append ("skuId" , getSkuId ()) .append ("skuName" , getSkuName ()) .append ("classId" , getClassId ()) .append ("status" , getStatus ()) .append ("amount" , getAmount ()) .append ("price" , getPrice ()) .append ("payType" , getPayType ()) .append ("payStatus" , getPayStatus ()) .append ("bill" , getBill ()) .append ("addr" , getAddr ()) .append ("regionId" , getRegionId ()) .append ("regionName" , getRegionName ()) .append ("businessType" , getBusinessType ()) .append ("partnerId" , getPartnerId ()) .append ("openId" , getOpenId ()) .append ("nodeId" , getNodeId ()) .append ("nodeName" , getNodeName ()) .append ("cancelDesc" , getCancelDesc ()) .append ("createTime" , getCreateTime ()) .append ("updateTime" , getUpdateTime ()) .toString (); } }
IOrderService
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 package com.dkd.manage.service;import java.util.List;import com.dkd.manage.domain.Order;public interface IOrderService { public Order selectOrderById (Long id) ; public List<Order> selectOrderList (Order order) ; public int insertOrder (Order order) ; public int updateOrder (Order order) ; public int deleteOrderByIds (Long[] ids) ; public int deleteOrderById (Long id) ; }
1OrderServiceImpl
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 package com.dkd.manage.service.impl;import java.util.List;import com.dkd.common.utils.DateUtils;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.stereotype.Service;import com.dkd.manage.mapper.OrderMapper;import com.dkd.manage.domain.Order;import com.dkd.manage.service.IOrderService;@Service public class OrderServiceImpl implements IOrderService { @Autowired private OrderMapper orderMapper; @Override public Order selectOrderById (Long id) { return orderMapper.selectOrderById (id) ; } @Override public List<Order> selectOrderList(Order order) { return orderMapper.selectOrderList (order) ; } @Override public int insertOrder (Order order) { order.setCreateTime(DateUtils.getNowDate()); return orderMapper.insertOrder (order) ; } @Override public int updateOrder (Order order) { order.setUpdateTime(DateUtils.getNowDate()); return orderMapper.updateOrder (order) ; } @Override public int deleteOrderByIds (Long[] ids) { return orderMapper.deleteOrderByIds (ids) ; } @Override public int deleteOrderById (Long id) { return orderMapper.deleteOrderById (id) ; } }
mapper
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 package com.dkd.manage.mapper;import java.util.List;import com.dkd.manage.domain.Order;public interface OrderMapper { public Order selectOrderById (Long id) ; public List<Order> selectOrderList (Order order) ; public int insertOrder (Order order) ; public int updateOrder (Order order) ; public int deleteOrderById (Long id) ; public int deleteOrderByIds (Long[] ids) ; }
mapper.xml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 <resultMap type ="Order" id ="OrderResult" > <result property ="id" column ="id" /> <result property ="orderNo" column ="order_no" /> <result property ="thirdNo" column ="third_no" /> <result property ="innerCode" column ="inner_code" /> <result property ="channelCode" column ="channel_code" /> <result property ="skuId" column ="sku_id" /> <result property ="skuName" column ="sku_name" /> <result property ="classId" column ="class_id" /> <result property ="status" column ="status" /> <result property ="amount" column ="amount" /> <result property ="price" column ="price" /> <result property ="payType" column ="pay_type" /> <result property ="payStatus" column ="pay_status" /> <result property ="bill" column ="bill" /> <result property ="addr" column ="addr" /> <result property ="regionId" column ="region_id" /> <result property ="regionName" column ="region_name" /> <result property ="businessType" column ="business_type" /> <result property ="partnerId" column ="partner_id" /> <result property ="openId" column ="open_id" /> <result property ="nodeId" column ="node_id" /> <result property ="nodeName" column ="node_name" /> <result property ="cancelDesc" column ="cancel_desc" /> <result property ="createTime" column ="create_time" /> <result property ="updateTime" column ="update_time" /> </resultMap > <sql id ="selectOrderVo" > select id, order_no, third_no, inner_code, channel_code, sku_id, sku_name, class_id, status, amount, price, pay_type, pay_status, bill, addr, region_id, region_name, business_type, partner_id, open_id, node_id, node_name, cancel_desc, create_time, update_time from tb_order </sql > <select id ="selectOrderList" parameterType ="Order" resultMap ="OrderResult" > <include refid ="selectOrderVo" /> <where > <if test ="orderNo != null and orderNo != ''" > and order_no = # {orderNo} </if > <if test ="params.beginTime != null and params.beginTime != ''" > AND date_format(create_time,'%y%m%d') > = date_format(# {params.beginTime} ,'%y%m%d') </if > <if test ="params.endTime != null and params.endTime != ''" > AND date_format(create_time,'%y%m%d') < = date_format(# {params.endTime} ,'%y%m%d') </if > </where > </select > <select id ="selectOrderById" parameterType ="Long" resultMap ="OrderResult" > <include refid ="selectOrderVo" /> where id = # {id} </select > <insert id ="insertOrder" parameterType ="Order" > insert into tb_order <trim prefix ="(" suffix =")" suffixOverrides ="," > <if test ="id != null" > id,</if > <if test ="orderNo != null" > order_no,</if > <if test ="thirdNo != null" > third_no,</if > <if test ="innerCode != null" > inner_code,</if > <if test ="channelCode != null" > channel_code,</if > <if test ="skuId != null" > sku_id,</if > <if test ="skuName != null" > sku_name,</if > <if test ="classId != null" > class_id,</if > <if test ="status != null" > status,</if > <if test ="amount != null" > amount,</if > <if test ="price != null" > price,</if > <if test ="payType != null" > pay_type,</if > <if test ="payStatus != null" > pay_status,</if > <if test ="bill != null" > bill,</if > <if test ="addr != null" > addr,</if > <if test ="regionId != null" > region_id,</if > <if test ="regionName != null" > region_name,</if > <if test ="businessType != null" > business_type,</if > <if test ="partnerId != null" > partner_id,</if > <if test ="openId != null" > open_id,</if > <if test ="nodeId != null" > node_id,</if > <if test ="nodeName != null" > node_name,</if > <if test ="cancelDesc != null" > cancel_desc,</if > <if test ="createTime != null" > create_time,</if > <if test ="updateTime != null" > update_time,</if > </trim > <trim prefix ="values (" suffix =")" suffixOverrides ="," > <if test ="id != null" > # {id} ,</if > <if test ="orderNo != null" > # {orderNo} ,</if > <if test ="thirdNo != null" > # {thirdNo} ,</if > <if test ="innerCode != null" > # {innerCode} ,</if > <if test ="channelCode != null" > # {channelCode} ,</if > <if test ="skuId != null" > # {skuId} ,</if > <if test ="skuName != null" > # {skuName} ,</if > <if test ="classId != null" > # {classId} ,</if > <if test ="status != null" > # {status} ,</if > <if test ="amount != null" > # {amount} ,</if > <if test ="price != null" > # {price} ,</if > <if test ="payType != null" > # {payType} ,</if > <if test ="payStatus != null" > # {payStatus} ,</if > <if test ="bill != null" > # {bill} ,</if > <if test ="addr != null" > # {addr} ,</if > <if test ="regionId != null" > # {regionId} ,</if > <if test ="regionName != null" > # {regionName} ,</if > <if test ="businessType != null" > # {businessType} ,</if > <if test ="partnerId != null" > # {partnerId} ,</if > <if test ="openId != null" > # {openId} ,</if > <if test ="nodeId != null" > # {nodeId} ,</if > <if test ="nodeName != null" > # {nodeName} ,</if > <if test ="cancelDesc != null" > # {cancelDesc} ,</if > <if test ="createTime != null" > # {createTime} ,</if > <if test ="updateTime != null" > # {updateTime} ,</if > </trim > </insert > <update id ="updateOrder" parameterType ="Order" > update tb_order <trim prefix ="SET" suffixOverrides ="," > <if test ="orderNo != null" > order_no = # {orderNo} ,</if > <if test ="thirdNo != null" > third_no = # {thirdNo} ,</if > <if test ="innerCode != null" > inner_code = # {innerCode} ,</if > <if test ="channelCode != null" > channel_code = # {channelCode} ,</if > <if test ="skuId != null" > sku_id = # {skuId} ,</if > <if test ="skuName != null" > sku_name = # {skuName} ,</if > <if test ="classId != null" > class_id = # {classId} ,</if > <if test ="status != null" > status = # {status} ,</if > <if test ="amount != null" > amount = # {amount} ,</if > <if test ="price != null" > price = # {price} ,</if > <if test ="payType != null" > pay_type = # {payType} ,</if > <if test ="payStatus != null" > pay_status = # {payStatus} ,</if > <if test ="bill != null" > bill = # {bill} ,</if > <if test ="addr != null" > addr = # {addr} ,</if > <if test ="regionId != null" > region_id = # {regionId} ,</if > <if test ="regionName != null" > region_name = # {regionName} ,</if > <if test ="businessType != null" > business_type = # {businessType} ,</if > <if test ="partnerId != null" > partner_id = # {partnerId} ,</if > <if test ="openId != null" > open_id = # {openId} ,</if > <if test ="nodeId != null" > node_id = # {nodeId} ,</if > <if test ="nodeName != null" > node_name = # {nodeName} ,</if > <if test ="cancelDesc != null" > cancel_desc = # {cancelDesc} ,</if > <if test ="createTime != null" > create_time = # {createTime} ,</if > <if test ="updateTime != null" > update_time = # {updateTime} ,</if > </trim > where id = # {id} </update > <delete id ="deleteOrderById" parameterType ="Long" > delete from tb_order where id = # {id} </delete > <delete id ="deleteOrderByIds" parameterType ="String" > delete from tb_order where id in <foreach item ="id" collection ="array" open ="(" separator ="," close =")" > # {id} </foreach > </delete >
实战篇完结