谷粒商城 |
您所在的位置:网站首页 › 商品的三级分类 › 谷粒商城 |
谷粒商城-基础篇2
一、商品服务-API-三级分类1、三级分类2、查出所有分类以及子分类2、配置网关路由与路径重写3、网关统一配置跨域4、查询-树形展示三级分类数据5、删除6、新增7、修改8、修改拖拽效果9、批量删除
商品服务-三级分类 一、商品服务-API-三级分类 1、三级分类 pms_category 表代表商品的分类 cat_id:分类id,cat代表分类,bigint(20) name:分类名称 parent_cid:在哪个父目录下 cat_level:分类层级 show_status:是否显示,用于逻辑删除 sort:同层级同父目录下显示顺序 ico图标,product_unit商品计量单位, InnoDB表,自增大小1437,utf编码,动态行格式 2、查出所有分类以及子分类1.在product服务的package com.ljn.gulimall.product.controller中打开CategoryController,添加方法: @RestController @RequestMapping("product/category") public class CategoryController { @Autowired private CategoryService categoryService; /** * 查出所有分类以及子分类,以树形结构组装起来 */ @RequestMapping("/list/tree") public R list(){ List entities = categoryService.listWithTree(); return R.ok().put("data", entities); }2…在product服务的package com.ljn.gulimall.product.service;中打开CategoryService,添加方法: public interface CategoryService extends IService { PageUtils queryPage(Map params); List listWithTree(); }3.在product服务的package com.ljn.gulimall.product.service.impl;中打开CategoryServiceImpl实现方法: package com.ljn.gulimall.product.service.impl; @Service("categoryService") public class CategoryServiceImpl extends ServiceImpl implements CategoryService { // 注入CategoryDao @Autowired CategoryDao categoryDao; @Override public PageUtils queryPage(Map params) { IPage page = this.page( new Query().getPage(params), new QueryWrapper() ); return new PageUtils(page); } // 实现方法 @Override public List listWithTree() { // 1.查出所有分类 List entities = categoryDao.selectList(null); // 2.组装成父子的树形结构 // 2.1 找到所有一级分类,一级分类父id=0,并返回为一个集合 List Level1Menus = entities.stream().filter(categoryEntity -> { // 一级分类 return categoryEntity.getParentCid() == 0; }).map((menu) -> { // 将查找到的子菜单放入 menu.setChildren(getChildrens(menu, entities)); return menu; }).sorted((menu1, menu2) -> { // 排序 return (menu1.getSort() == null ? 0 : menu1.getSort()) - (menu2.getSort() == null ? 0 : menu2.getSort()); }).collect(Collectors.toList()); return Level1Menus; } // 递归查找所有菜单的子菜单,root:当前菜单 all:所有菜单 private List getChildrens(CategoryEntity root, List all) { // 从所有菜单中过滤出子菜单 List children = all.stream().filter(categoryEntity -> { // 当前菜单的父ID=指定菜单的id 也就是判断在哪个父目录下 return categoryEntity.getParentCid().equals(root.getCatId()); }).map(categoryEntity -> { // 当前菜单还可能有子菜单 categoryEntity.setChildren(getChildrens(categoryEntity, all)); return categoryEntity; }).sorted((menu1, menu2) -> { // 排序 return (menu1.getSort() == null ? 0 : menu1.getSort()) - (menu2.getSort() == null ? 0 : menu2.getSort()); }).collect(Collectors.toList()); return children; } }4.结果 启动renren-fast 前后端项目: 点击系统管理,菜单管理,新增 继续新增:分类维护菜单 在左侧点击【分类维护】,希望在此展示3级分类 注意地址栏http://localhost:8001/#/product-category 可以注意到product-category我们的/被替换为了- 比如sys-role具体的视图在renren-fast-vue/views/modules/sys/role.vue 所以要自定义我们的product/category视图的话,就是创建mudules/product/category.vue export default { name: 'category', components: {}, directives: {}, data() { return { data: [], defaultProps: { children: 'children', label: 'label' } }; }, mounted() { }, methods: { handleNodeClick(data) { console.log(data); }, getMenus(){ this.$http({ url: this.$http.adornUrl('/product/category/list/tree'), method: 'get' }).then(data=>{ console.log(data) }) } }, created(){ this.getMenus(); } };网关88配置 在登录管理后台的时候,我们会发现,他要请求localhost:8080/renren-fast/product/category/list/tree这个url他要给8080发请求读取数据,但是数据是在10000端口上,如果找到了这个请求改端口那改起来很麻烦。方法1是改vue项目里的全局配置.方法2是搭建个网关,让网关路由到10000(即将vue项目里的请求都给网关,网关经过url处理后,去nacos里找到管理后台的微服务,就可以找到对应的端口了,这样我们就无需管理端口,统一交给网关管理端口接口)1.在vue项目的 static/config/index.js里修改 window.SITE_CONFIG['baseUrl'] = 'http://localhost:88/api'; // 意思是说本vue项目中要请求的资源url都发给88/api,那么我们就让网关端口为88,然后匹配到/api请求即可, // 网关可以通过过滤器处理url后指定给某个微服务 // renren-fast服务已经注册到了nacos中刷新后需要重新登录,此时验证码不显示,因为此时验证码是请求88的,所以不显示。而验证码是来源于fast后台即8080端口的的。 解决:将renren-fast 注册到nacos注册中心,这样请求88网关转发到8080fast。 2.让fast里加入注册中心的依赖: com.alibaba.cloud spring-cloud-starter-alibaba-nacos-config 2.1.0.RELEASE com.alibaba.cloud spring-cloud-starter-alibaba-nacos-discovery 2.1.0.RELEASE3.在renren-fast项目的application.yml中添加: spring: application: name: renren-fast # 意思是把renren-fast项目也注册到nacos中,这样网关才能转发给 cloud: nacos: discovery: server-addr: localhost:8848 # nacos4.开启服务注册与发现 @EnableDiscoveryClient @SpringBootApplication public class RenrenApplication { public static void main(String[] args) { SpringApplication.run(RenrenApplication.class, args); } }5.在gateway服务中按格式加入 - id: admin_route # lb 代表负载均衡 uri: lb://renren-fast # 路由给renren-fast predicates: # 什么情况下路由给它 - Path=/api/** # 默认前端项目都带上api前缀,就是我们前面的localhost:88/api filters: - RewritePath=/api/(?.*),/renren-fast/$\{segment} # 把/api/* 改变成 /renren-fast/* 3、网关统一配置跨域 跨域:指的是浏览器不能执行其他网站的脚本。它是由浏览器的同源策略造成的,是浏览器对js施加的安全限制。(ajax可以)同源策略:是指协议,域名,端囗都要相同,其中有一个不同都会产生跨域;跨域流程: 解决跨域: 使用ngnix部署为同一域:设置nginx包含admin和gateway。都先请求nginx,这样端口就统一了。![]() 1、问题描述 在显示商品系统/分类信息的时候,出现了404异常,请求的http://localhost:88/api/product/category/list/tree不存在 这是因为网关上所做的路径映射不正确,映射后的路径为http://localhost:8001/renren-fast/product/category/list/tree 但是只有通过http://localhost:10000/product/category/list/tree路径才能够正常访问,所以会报404异常。 2、解决方法就是定义一个product路由规则,以后/ap/product 的路径都转发给product服务,进行路径重写: (1)在gateway网关增加三级分类的路由 # product服务路由 - id: product_route uri: lb://gulimall-product predicates: - Path=/api/product/** filters: - # 把/api/* 去掉,剩下的留下来 - RewritePath=/api/(?.*),/$\{segment} (2)在nacos中新建命名空间(product),用命名空间隔离项目,(可以在其中新建gulimall-product.yml 抽取配置)![]() (5)GulimallProductApplication 主启动类中开启服务注册与发现功能 @EnableDiscoveryClient (6)启动服务后访问 localhost:88/api/product/category/list/tree {"msg":"invalid token","code":401} invalid token,非法令牌,后台管理系统中没有登录,所以没有带令牌原因:先匹配的先路由,fast和product路由重叠,fast要求登录 修正:在路由规则的顺序上,将精确的路由规则放置到模糊的路由规则的前面,否则的话,精确的路由规则将不会被匹配到,类似于异常体系中try catch子句中异常的处理顺序。 访问 http://localhost:88/api/product/category/list/tree 正常 访问 http://localhost:8001/#/product-category,也就是点击分类维护,正常,数据获取成功 原因是:先访问网关88,网关路径重写后访问nacos8848,通过nacos找到服务 3、前端渲染 1、使用scoped slot(插槽)实现:在el-tree标签里把内容写到span标签栏里即可 :expand-on-click-node=“false” :点击按钮的时候不展开show-checkbox :代表节点是否可被选择node-key=“catId” :节点唯一标识 {{ node.label }} Append Delete export default { name: "category", components: {}, directives: {}, data() { return { menus: [], data: [], defaultProps: { children: "children", label: "name", // 要显显示的内容 }, }; }, mounted() { }, methods: { // 3. 增加标签的方法 append(data) { console.log("append", data) }, // 2. 删除分类的方法 remove(node, data) { console.log("delete", node, data) }, // 1. 获取分类数据的方法 getMenus() { this.$http({ url: this.$http.adornUrl("/product/category/list/tree"), method: "get", }).then(({ data }) => { // 解构出数据中有用的data console.log(data.data); this.menus = data.data; }); }, }, created() { this.getMenus(); }, };2、逻辑删除 修改CategoryController类,添加如下代码 @RequestMapping("/delete") public R delete(@RequestBody Long[] catIds) { // 1、删除之前需要判断待删除的菜单那是否被别的地方所引用。 // 2、自定义删除方法 categoryService.removeMenuByIds(Arrays.asList(catIds)); return R.ok(); } CategoryServiceImpl 实现自定义的方法 @Override public void removeMenuByIds(List asList) { //TODO 1、检查当前删除的菜单,是否被其它地方引用 // 2、逻辑删除 baseMapper.deleteBatchIds(asList); } mybatis-plus逻辑删除配置(可有可无) mybatis-plus: mapper-locations: classpath:/mapper/**/*.xml global-config: db-config: id-type: auto logic-delete-value: 1 logic-not-delete-value: 0 修改product.entity.CategoryEntity实体类,添加上@TableLogic,表明使用逻辑删除 /** * 是否显示[0-不显示,1显示] */ @TableLogic(value = "1",delval = "0") private Integer showStatus;3、apifox测试请求 4、前端请求处理 发送的请求:delete 发送的数据:this.$http.adornData(ids, false) util/httpRequest.js中,封装了一些拦截器 http.adornParams是封装get请求的数据 http.adornData封装post请求的数据 ajax的get请求会被缓存,就不会请求服务器了。 所以我们在url后面拼接个date时间戳,让他每次都请求服务器 发送请求 // 2. 删除分类的方法 remove(node, data) { // 1. 获取当前节点id var ids = [data.catId] // 2. 发送请求前弹框提示 this.$confirm(`是否删除${data.name}菜单?`, '提示', { confirmButtonText: '确定', cancelButtonText: '取消', type: 'warning' }).then(() => { // 3. 确认删除,发送post请求 this.$http({ url: this.$http.adornUrl('/product/category/delete'), method: 'post', data: this.$http.adornData(ids, false) }).then(({ data }) => { // 4. 删除成功提示消息 this.$message({ type: "success", message: "菜单删除成功!", }); // 5. 删除成功后重新请求菜单 this.getMenus(); // 6. 设置默认展开菜单 this.expandedKey=[node.parent.data.catId] }) }).catch(() => { }); console.log("delete", node, data) }, 删除后展开标准 data() { return { expandedKey: [], // 展开基准 }; }, 6、新增 点击append按钮的时候打开对话框 用到属性 visible.sync,动态绑定,:visible.sync="dialogVisible" 取 消 确 定 data() { return { dialogVisible: false, // 是否打开对话框,默认为false }; }, methods: { append(data) { // 1. 点击append 按钮打开对话框 this.dialogVisible = true; console.log("append", data) }, 对话框中添加表单 属性 取 消 确 定 定义添加菜单的方法addCategory,并在append方法中设置默认值 // 4. 添加三级分类的方法 addCategory() { console.log("提交的三级分类数据", this.category) // 1. 发送保存请求,提交 this.$http({ url: this.$http.adornUrl('/product/category/save'), method: 'post', data: this.$http.adornData(this.category, false) // 要发送的数据 }).then(({ data }) => { // 2. 保存成功提示消息 this.$message({ type: "success", message: "菜单保存成功!", }); // 3. 保存成功后关闭对话框 this.dialogVisible = false; // 4. 刷新出新菜单 this.getMenus(); // 5. 设置默认展示的菜单 this.expandedKey = [this.category.parentCid]; }) }, append(data) { console.log("append", data) // 1. 点击append 按钮打开对话框 this.dialogVisible = true; // 2. 点击按钮为category获取默认值 // 2.1 父id,当前点击append的catId this.category.parentCid = data.catId; // 2.2 层级catLevel 当前点击append 的层级+1 this.category.catLevel = data.catLevel * 1 + 1; }, 7、修改 增加修改按钮edit Edit methods: { // 5. 点击修改分类按钮 edit(node, data){ // 显示对话框 this.dialogVisible=true; // 回显数据 this.category.name=data.name; console.log(node,data) }, } 复用对话框 定义对话框类型:dialogType修改对话框确认按钮绑定的事件:submitDate (提交数据)根据对话框类型动态提交数据修改完后,在append中清除回显信息 取 消 确 定 export default { name: 'category', components: {}, directives: {}, data() { return { dialogType: "", // 对话框类型 title: "", // 对话框标题 category: { name: "", parentCid: 0, catLevel: 0, showStatus: 1, sort: 0, catId: null, icon: "", productUnit: "" }, // 表单中的数据对象 dialogVisible: false, // 是否打开对话框,默认为false expandedKey: [], // 展开基准 menus: [], data: [], defaultProps: { children: "children", label: "name", // 要显显示的内容 }, }; }, mounted() { }, methods: { // 7. 对话框确认按钮,提交数据的方法 submitDate() { if (this.dialogType == "append") { // 打开的是添加的对话框,保存分类 this.addCategory(); } if (this.dialogType == "edit") { // 打开的是修改的对话框,修改分类 this.editCategory(); } }, // 6. 修改三级分类数据 editCategory() { // 1. 解构要发送的数据 var { catId, name, icon, productUnit } = this.category; // 2. 发送修改请求 this.$http({ url: this.$http.adornUrl("/product/category/update"), method: 'post', data: this.$http.adornData({ catId, name, icon, productUnit }, false) }).then(({ data }) => { this.$message({ type: "success", message: "菜单修改成功!", }); // 3. 关闭对话框 this.dialogVisible = false; // 4. 刷新菜单 this.getMenus(); // 5. 展开父菜单 this.expandedKey = [this.category.parentCid]; }) }, // 5. 点击修改分类按钮 edit(node, data) { // 1.1设置对话框类型为deit this.dialogType = "edit"; // 1.2显示对话框 this.dialogVisible = true; // 1.3对话框标题 this.title = "修改分类" // 2. 发送请求回显最新数据 this.$http({ url: this.$http.adornUrl(`/product/category/info/${data.catId}`), method: 'get', }).then(({ data }) => { // 3. 请求成功,回显数据 this.category.name = data.data.name; this.category.catId = data.data.catId; this.category.icon = data.data.icon; this.category.productUnit = data.data.productUnit; this.category.parentCid = data.data.parentCid; }) console.log(node, data) }, // 4. 添加三级分类数据的方法 addCategory() { console.log("提交的三级分类数据", this.category) // 1. 发送保存请求,提交 this.$http({ url: this.$http.adornUrl('/product/category/save'), method: 'post', data: this.$http.adornData(this.category, false) // 要发送的数据 }).then(({ data }) => { // 2. 保存成功提示消息 this.$message({ type: "success", message: "菜单保存成功!", }); // 3. 保存成功后关闭对话框 this.dialogVisible = false; // 4. 刷新出新菜单 this.getMenus(); // 5. 设置默认展示的菜单 this.expandedKey = [this.category.parentCid]; }) }, // 3. 点击增加标签按钮 append(data) { console.log("append", data) // 0. 设置对话框类型为append this.dialogType = "append"; // 1. 点击append 按钮打开对话框 this.dialogVisible = true; // 设置标题 this.title = "添加分类"; // 2. 点击按钮为category获取默认值 // 2.1 父id,当前点击append的catId this.category.parentCid = data.catId; // 2.2 层级catLevel 当前点击append 的层级+1 this.category.catLevel = data.catLevel * 1 + 1; // 3. 清空修改后的回显信息 this.category.name = ""; this.category.showStatus = 1; this.category.sort = 0; this.category.catId = null; this.category.icon = ""; // 输入什么绑定什么 this.category.productUnit = ""; }, 8、修改拖拽效果 实现拖拽效果 :draggable 属性开启拖拽效果:allow-drop 拖拽目标是否放置 // 9. 统计当前拖拽节点总层数的方法 countNodeLevel(node) { // 找到所有子节点,求出最大深度 if (node.childNodes != null && node.childNodes.length > 0) { // 有子节点 // 遍历 for (let i = 0; i this.maxLevel) { this.maxLevel = node.childNodes[i].level; } // 还有子节点,递归调用 this.countNodeLevel(node.childNodes[i]); } } }, // 8. 拖拽是否放置的方法 allowDrop(draggingNode, dropNode, type) { // 可以拖动放置的条件:被拖动的当前节点以及所在的父节点总层数不能大于3 console.log("allowFrop:",draggingNode,dropNode,type) // 1. 统计当前节点的总层数 this.countNodeLevel(draggingNode.data); // 2.当前正在拖动的节点+父节点所在的深度不大于3即可 let deep = (this.maxLevel - draggingNode.data.catLevel) + 1; console.log("深度:", deep); // 3. this.maxLevel // 3.1 拖到节点里面 if (type == "inner") { return deep + dropNode.level { this.$message({ type: "success", message: "菜单批量删除成功!", }); // 刷新出新的菜单 this.getMenus(); }) .catch(() => { }); }) .catch(() => { }); }, |
今日新闻 |
推荐新闻 |
CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3 |