谷粒商城

您所在的位置:网站首页 商品的三级分类 谷粒商城

谷粒商城

2024-07-14 19:43| 来源: 网络整理| 查看: 265

谷粒商城-基础篇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.结果 在这里插入图片描述

2、配置网关路由与路径重写

启动renren-fast 前后端项目:

点击系统管理,菜单管理,新增 在这里插入图片描述 刷新,看到左侧多了商品系统,添加的这个菜单其实是添加到了guli-admin.sys_menu表里.

继续新增:分类维护菜单 在这里插入图片描述

在左侧点击【分类维护】,希望在此展示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.RELEASE

3.在renren-fast项目的application.yml中添加:

spring: application: name: renren-fast # 意思是把renren-fast项目也注册到nacos中,这样网关才能转发给 cloud: nacos: discovery: server-addr: localhost:8848 # nacos

4.开启服务注册与发现

@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,这样端口就统一了。 在这里插入图片描述配置当次请求允许跨域:让服务器告诉预检请求能跨域 在网关中定义“GulimallCorsConfiguration”类,该类用来做过滤,允许所有的请求跨域。 package com.ljn.gulimall.gateway.config; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.cors.CorsConfiguration; import org.springframework.web.cors.reactive.CorsWebFilter; import org.springframework.web.cors.reactive.UrlBasedCorsConfigurationSource; @Configuration // gateway public class GulimallCorsConfiguration { @Bean // 添加过滤器 public CorsWebFilter corsWebFilter() { // 基于url跨域,选择reactive包下的 UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); // 跨域配置信息 CorsConfiguration corsConfiguration = new CorsConfiguration(); // 允许跨域的头 corsConfiguration.addAllowedHeader("*"); // 允许跨域的请求方式 corsConfiguration.addAllowedMethod("*"); // 允许跨域的请求来源 corsConfiguration.addAllowedOrigin("*"); // 是否允许携带cookie跨域 corsConfiguration.setAllowCredentials(true); // 任意url都要进行跨域配置 source.registerCorsConfiguration("/**", corsConfiguration); return new CorsWebFilter(source); } } 4、查询-树形展示三级分类数据

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 抽取配置) 在这里插入图片描述(3) nacos 配置中心管理product服务,创建bootstrap.proterties # 应用名称 spring.application.name=gulimall-product spring.cloud.nacos.config.server-addr=127.0.0.1:8848 #命名空间的唯一ID spring.cloud.nacos.config.namespace=8a9fe873-8e40-4d56-a77f-0b5993c6f52c (4)配置 produc t服务的 application.yml,配置服务注册与发现 # 配置nacos服务注册与发现 cloud: nacos: discovery: server-addr: 127.0.0.1:8848

(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、前端渲染 在这里插入图片描述 在这里插入图片描述

5、删除

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