谷粒商城官网_尚硅谷谷粒商城电商项目

谷粒商城官网_尚硅谷谷粒商城电商项目后端编写、前端展示、服务注册、配置网关、503问题、跨域、解决跨域、服务注册配置网关、删除数据、后端接口、逻辑删除

大家好,又见面了,我是你们的朋友全栈君。如果您正在找激活码,请点击查看最新教程,关注关注公众号 “全栈程序员社区” 获取激活教程,可能之前旧版本教程已经失效.最新Idea2022.1教程亲测有效,一键激活。

Jetbrains全系列IDE使用 1年只要46元 售后保障 童叟无欺

谷粒商城_01_环境搭建
谷粒商城_02_Nacos、网关
谷粒商城_03_前端基础

商品服务-分类管理

在这里插入图片描述

查出所有分类以及子分类

后端编写

1、定义接口查询所有数据:gulimall-product的com.liu.gulimall.product.controller.CategoryController添加如下方法

/** * 商品三级分类 * * @author liujianyu * @email 380404812@qq.com * @date 2021-12-09 19:15:00 */
@RestController
@RequestMapping("product/category")
public class CategoryController { 
   
    @Autowired
    private CategoryService categoryService;

    /** * 查出所有分类以及子分类,以树形结构组装起来 */
    @RequestMapping("/list/tree")
    public R list(){ 
   
        List<CategoryEntity> entities = categoryService.listWithTree();
        return R.ok().put("data", entities);
    }
}

2、在service层中创建方法 listWithTree

/** * 商品三级分类 * * @author liujianyu * @email 380404812@qq.com * @date 2021-12-09 18:29:39 */
public interface CategoryService extends IService<CategoryEntity> { 
   

    PageUtils queryPage(Map<String, Object> params);

    /** * 将数据以树形分类,分出三级 * @return */
    List<CategoryEntity> listWithTree();
}

3、在service层的Impl中实现方法

  • 最重要的是商品实体中需要多一个字段,children,为了放此分类的子分类
  • 先把所有商品查出来然后根据数据库字段父id查出,没有父id的一级商品,然后在递归找子分类
  • 主要理解流式编程:filter、map、sorted、collect
  • 一级分类有二级,二级有三级,所以是一个递归的方式
@Service("categoryService")
public class CategoryServiceImpl extends ServiceImpl<CategoryDao, CategoryEntity> implements CategoryService { 
   
// @Autowired
// private CategoryDao categoryDao;
// ServiceImpl<CategoryDao, CategoryEntity>已经帮我们注入了dao,就不用自己注入了
    @Override
    public List<CategoryEntity> listWithTree() { 
   
        
        // 1 查出所有分类
        List<CategoryEntity> entities = baseMapper.selectList(null);
        // 2 组装成父子的树形结构
        List<CategoryEntity> level1Menus = entities.stream().filter((categoryEntity) ->{ 
    // 过滤
            // long类型的比较不要直接使用==,要用到longValue()来比较
              return  categoryEntity.getParentCid().longValue() == 0;} // 过滤只要父id等于0的数据,也就是一级分类
        ).map((categoryEntity)->{ 
    // 为过滤后的一级分类的每一个数据设置他的子分类
            categoryEntity.setChildren(getChildrens(categoryEntity,entities));
           return categoryEntity;}
        ).sorted((categoryEntity1,categoryEntity2)->{ 
    // 升序排序根据字段sort来让sort小的的排前面 由于categoryEntity1可能已经为空null了,所以先判断
            return (categoryEntity1.getSort() == null? 0: categoryEntity1.getSort())
                    -
                    (categoryEntity2.getSort() == null?0:categoryEntity2.getSort());
        }).collect(Collectors.toList()); // 将过滤的数据收集成数组
        return level1Menus;
    }

    // 递归查找所有菜单的子菜单,把当前分类,和总分类传进来
    private List<CategoryEntity> getChildrens(CategoryEntity root, List<CategoryEntity> all) { 
   
        List<CategoryEntity> children = all.stream().filter(categoryEntity -> { 
   
            // 过滤取出父id等于当前分类的数据
            return categoryEntity.getParentCid().longValue() == root.getCatId().longValue();
        }).map(categoryEntity -> { 
    // 每一个子分类还有可能有子分类
            // 1 找到子菜单
            categoryEntity.setChildren(getChildrens(categoryEntity, all));
            return categoryEntity;
        }).sorted((menu1, menu2) -> { 
   
            // 2 菜单的排序 由于menu可能已经为空null了,所以先判断
            return (menu1.getSort() == null?0:menu1.getSort()) - (menu2.getSort() == null?0:menu2.getSort());
        }).collect(Collectors.toList());
        return children;
    }
}

前端展示

1、创建商品系统的目录,以及内容转发的路由

在这里插入图片描述

在这里插入图片描述

数据库同步数据

在这里插入图片描述

2、根据人人开源策略,根据路由product/category 来请求product-category,比如sys-role具体的视图在renren-fast-vue/views/modules/sys/role.vue 所以要自定义我们的product/category视图的话,就是创建mudules/product/category.vue,根据策略创建product文件,在文件下创建category.vue组件,这样请求路由就会跳转到这个组件中,

在这里插入图片描述

在这里插入图片描述

3、编写category.vue组件

  • 可以模仿人人开源的组件方式,包括里面怎么转发,怎么数据交互等。
  • 采用Element UI来开发,采用树形控件来管理三级数据
  • 在这里插入图片描述
<template>
  <el-tree :data="data" :props="defaultProps" @node-click="handleNodeClick" ></el-tree>
 
</template>

<script> export default { 
      data() { 
      return { 
      data: [], defaultProps: { 
      children: "children", label: "label", }, }; }, methods: { 
      handleNodeClick(data) { 
      console.log(data); }, getMenus(data) { 
      // 自定义方法 this.$http({ 
      // htpp请求 url: this.$http.adornUrl("/product/category/list/tree"), // 请求后端的接口 method: "get", }).then((data) => { 
      // 成功了之后的操作 console.log("请求数据," + data); }); }, }, created() { 
      // 加载组件的时候就调用改方法 this.getMenus(); }, }; </script>

<style lang="scss" scoped> </style>

4、我们的数据肯定是从后端取出来,所以要请求gulimall-product服务后端的接口,但是现在请求的是:人人开源的接口,所以修改前端配置:renren-fast-vue\static\config\index.js中修改,但是我们gulimall-product服务可能在很多服务器上,所以每次都要修改端口很麻烦,直接交给网关来做,让网关跟我们去请求服务器

在这里插入图片描述

由于我们想要访问的是:http://localhost:10000/product/category/list/tree这个请求,而且这里面的10000端口也存在多服务问题,所以采用网关来转发请求。http://localhost:88/product/category/list/tree

在这里插入图片描述

服务注册

  • 我们登录有验证码,这个是renrne-fast服务的数据,所以先注册renrne-fast服务

  • 通过网关把请求转发给后端renren-fast服务,所以先让网关发现服务,也就是注册服务到注册中心去

1、在renren-fast中导入的依赖【如果依赖无法彻底删除,复制全文,删除原来的,重新创建pom文件】

在这里插入图片描述

2、在配置文件中配置,服务名称和注册中心地址

3、注册到注册中心中,加入注解:@EnableDiscoveryClient

4、启动nacos,发现服务

在这里插入图片描述

配置网关

在这里插入图片描述

  • 配置网关
spring:
  cloud:
    gateway:
      # 路由规则
      routes:
        - id: admin_route
          uri: lb://renren-fast # 路由给renren-fast,lb代表负载均衡
          predicates: # 什么情况下路由给它
            - Path=/api/** # 默认前端项目都带上api前缀,但是这样还是请求的:http://localhost:88/api/..我们想要请求 http://localhost:8080/renren-fast/..,所以加上过滤器
            			# 所以在前端index.js中改为 window.SITE_CONFIG['baseUrl'] = 'http://localhost:88/api';
          filters: # 过滤器,
            - RewritePath=/api/(?<segment>.*),/renren-fast/$\{ 
   segment} 
  • 启动renren-fast、gulimall-gateway、nacos,发现验证出现

503问题

  • 检查服务是否配置到注册中心,并且保证在同一命名空间
  • 检查配置是否正确,路径,服务名等
  • 在gateway中更换alibaba依赖版本为2.2.0;

在这里插入图片描述

  • 200,没有图片:将renren-fast的跨域配置中的allowedOrigins("*")改为 allowedOriginPatterns("*")

在这里插入图片描述

  • 当验证码成功后登录遇到跨域问题

跨域

在这里插入图片描述

已拦截跨源请求:同源策略禁止读取位于 http://localhost:88/api/sys/login 的远程资源。(原因:CORS 头缺少 ‘Access-Control-Allow-Origin’)。

这是一种跨域问题。访问的域名和端口和原来的请求不同,请求就会被限制

跨域:指的是浏览器不能执行其他网站的脚本。它是由浏览器的同源策略造成的,是浏览器对js施加的安全限制。(ajax可以)

同源策略:是指协议,域名,端囗都要相同,其中有一个不同都会产生跨域;

跨域流程

  • 浏览器先发送OPTIONS,请求服务器能否跨域,服务器需要配置允许跨域,然后浏览器在发送真实请求

在这里插入图片描述

解决跨域

在这里插入图片描述

  • 我们现在学习常用,自己后端编写配置

在这里插入图片描述

  • 在网关中定义GulimallCorsConfiguration类,该类用来做过滤,允许所有的请求跨域。【注意】需要把renren-fast的跨域注解掉,因为先走我们网关跨域再走renrne-fast就重复了,然后就成功解决跨域
/** * @author ljy * @version 1.0.0 * @Description TODO * @createTime 2021年12月12日 22:46:00 */
@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("*");
        corsConfiguration.addAllowedOriginPattern("*");
        // 是否允许携带cookie跨域
        corsConfiguration.setAllowCredentials(true);

        // 任意url都要进行跨域配置
        source.registerCorsConfiguration("/**",corsConfiguration);
        return new CorsWebFilter(source);
    }
}

服务注册

1、编写配置文件,加上配置中心地址和服务名称

2、添加注解@EnableDiscoveryClient

3、添加spring-cloud-starter-alibaba-nacos-discovery服务发现依赖

配置网关

  • 在网关配置中添加gulimall-product服务的路由配置,并且要写在renren-fast的前面, 路由的顺序有影响,把更具体的路由请求放前面
- id: product_route
  uri: lb://gulimall-product
  predicates:
    - Path=/api/product/**
  filters:
    - RewritePath=/api/(?<segment>.*),/$\{ 
   segment}

再访问http://localhost:88/api/product/category/list/tree,数据正常出现,数据正常显示,就应该编写页面了

<template>
  <!-- :dataA="data" 表示dataA跟我们组件中的data进行绑定-->
  <el-tree :data="menus" :props="defaultProps" >
  </el-tree>
</template>

<script> export default { 
      data() { 
      return { 
      menus: [], // 请求后端返回的数据 defaultProps: { 
      children: "children", // 表示children显示data的children属性 label: "name", // label指的是显示数据中的哪个属性这里就显示data中的name属性,这些label、children都可以在官方文档 中查看的 }, }; }, methods: { 
      getMenus(data) { 
      // 自定义方法 this.$http({ 
      // htpp请求 url: this.$http.adornUrl("/product/category/list/tree"), // 请求后端的接口 method: "get", // }).then((data) => { 由于我们的data是一个对象,我们只需要拿到其中的data属性的数据,所以要把data对象解构出来 }).then(({ 
       data }) => { 
      // 成功了之后的操作 // console.log("请求数据," + data); +号不能显示出数据 // console.log("请求数据," , data); ,才可以显示出数据 console.log("请求数据,", data.data); // 解构data对象取出data属性 this.menus = data.data; // 将后端取出的数据给到组件中的menus属性 }); } }, created() { 
      // 加载组件的时候就调用改方法 this.getMenus(); }, }; </script>

<style lang="scss" scoped> </style>

删除数据

后端接口

  • com.liu.gulimall.product.controller.CategoryController
/** * 删除 * @RequestBody 获取到请求体里面的内容,必须发送Post请求 * springMVC会自动将请求体的数据(json),转为对应的对象(Long[]) */
@RequestMapping("/delete")
public R delete(@RequestBody Long[] catIds){ 
   
// categoryService.removeByIds(Arrays.asList(catIds));
    // 删除之前需要判断待删除的菜单那是否被别的地方所引用。
    categoryService.removeMenuByIds(Arrays.asList(catIds));

    return R.ok();
}
  • com/liu/gulimall/product/service/impl/CategoryServiceImpl.java
@Override
public void removeMenuByIds(List<Long> asList) { 
   
    // TODO 先检查当前的菜单是否被别的地方所引用
    // TODO表示代办事项,以后可以直接在最下方查看代办
    // 开发期间多用逻辑删除:使用字段来标识
    baseMapper.deleteBatchIds(asList);
}

逻辑删除

  • 多数时候,我们并不希望删除数据,而是标记它被删除了,这就是逻辑删除; 逻辑删除是mybatis-plus 的内容,会在项目中配置一些内容,告诉此项目执行delete语句时并不删除,只是标志位 可以设置show_status为0,标记它已经被删除。
  • 官网:https://mp.baomidou.com/ 中有提到逻辑删除的用法

在这里插入图片描述

1、配置mybatis-plus

# MapperScan
# sql映射文件位置
mybatis-plus:
  mapper-locations: classpath:/mapper/**/*.xml
  global-config:
    db-config:
      id-type: auto
      logic-delete-value: 1  # 表示逻辑已删除 
      logic-not-delete-value: 0

2、添加注解@TableLogic

/** * 是否显示[0-不显示,1显示],由于我们数据库和mybatis-plus规则相反,所以我们自己定义逻辑删除,1表示未删除 */
@TableLogic(value = "1",delval = "0")
private Integer showStatus;

测试

http://localhost:88/api/product/category/delete

在这里插入图片描述

在这里插入图片描述

  • 可以查看sql信息,我们需要配置日志
logging:
  level:
    com.liu.gulimall: debug
==>  Preparing: UPDATE pms_category SET show_status=0 WHERE cat_id IN ( ? ) AND show_status=1 
==> Parameters: 1000(Long)
<==    Updates: 1

前端请求

  • 直接采用Element UI跟我们提供的一些组件,然后结合renrne-vue中的一些请求方式即可

删除数据

  • 只有没有子分类的才允许有删除的功能
  • 在删除上绑定事件,弹出提示框,确定删除才会给后端发请求走数据库并且是逻辑删除
  • 删除完后需要弹出成功消息,刷新新页面,并且展开刚才被删除的父分类

新增分类

  • 只要有子分类的才允许有此功能
  • 绑定事件,弹出弹框,在弹框中内嵌表格,给属性置为默认值,确定添加,给后端发请求
  • 弹出消息,刷新页面,展开次分类

修改分类

  • 自定义一个按钮,每一数据都有此功能
  • 绑定事件,和新增共用一个弹框,只需判断弹框的类型,并且需要将此分类的数据先从后端请求(每次都拿数据库,避免高并发)回显到弹框的内嵌表格中,只回显需要修改的属性,并且只将修改的属性请求到后端
  • 弹出提示,刷新页面,展开父分类

在这里插入图片描述

在这里插入图片描述

拖拽效果
  • 避免误操作,是否开启拖拽需要设置一个按钮switch标签来确定

  • 可以直接拖动每一分类来改变每一分类的顺序,以及其父和子分类,并且需要判断是否可以拖拽到此分类

  • 官网:拖拽完成触发方法,不同拖拽,会有不同的父id,确定拖拽的新顺序,将所有改动的分类都放到一个数组里面

  • 由于不能反复请求后端,设置批量保存按钮,触发事件请求后端,将数组传给后端

  • 弹出提示,刷新页面,展开所有的父分类,并将数组置空

批量删除
  • 获取被选中的元素,获取元素的id
  • 弹出警告框,确定删除,请求后端,将元素id数组传入
  • 刷新菜单
<template>
  <div>
    <el-switch v-model="draggable" active-text="开启拖拽" inactive-text="关闭拖拽" >
    </el-switch>
    <el-button @click="batchSave()" v-if="draggable">批量保存</el-button>
    <el-button type="danger" @click="batchDelete">批量删除</el-button>
    <!-- :dataA="data" 表示dataA跟我们组件中的data进行绑定-->
    <!-- :expand-on-click-node表示禁止网上冒泡,官网都有解释,直接在官网ctrl+f搜索即可 -->
    <!--el-tree中的 @node-click="handleNodeClick" 删除没什么用-->
    <!-- show-checkbox表示该标签是否可以被选中 -->
    <!-- node-key表示每个节点的唯一标识,catId是我们各个data中唯一的属性,也就是数据库中的主键 -->
    <!-- :default-expanded-keys表示动态绑定需要展开的节点id,因为当我们删除的时候,不想让父节点折叠起来,所以定义让删除节点的父节点都展开 -->
    <!-- :draggable="draggable"是否可以拖动-->
    <!-- :allow-drop="allowDrop"是否允许拖动比如层级关系等等。有三个情况,分别表示放置在目标节点前、插入至目标节点和放置在目标节点后, -->
    <!-- @node-drop拖拽成功触发的方法-->
    <!-- ref="menuTree" 方便指定是哪个组件,主要是批量删除的时候用到 -->
    <el-tree node-key="catId" show-checkbox :data="menus" :props="defaultProps" :expand-on-click-node="false" :default-expanded-keys="expandedKey" :draggable="draggable" :allow-drop="allowDrop" @node-drop="handleDrop()" ref="menuTree" >
      <!-- 官网找的span slot-scope插槽机制,每一个元素后面都会格外加上是这个span-->
      <!-- node代表当前的节点也即是对象,node.label就是当前节点的name,data就是此节点的数据 -->
      <span class="custom-tree-node" slot-scope="{ node, data }">
        <span>{
  
  { node.label }}</span>
        <span>
          <!-- @click点击就会跳转到我们的方法-->
          <!-- 由于只要一级二级才有append操作,所以加上v-if="node.level<=2",node的level<=2表示为一二级-->
          <el-button v-if="node.level <= 2" type="text" size="mini" @click="() => append(data)" >
            Append
          </el-button>
          <!-- 自己定义修改按钮,每一个菜单都要显示 -->
          <el-button type="text" size="mini" @click="edit(data)">
            Edit
          </el-button>
          <!--childNodes是node的子节点数组属性, v-if="node.childNodes==0"表示没有子节点才有delete按钮 -->
          <el-button v-if="node.childNodes == 0" type="text" size="mini" @click="() => remove(node, data)" >
            Delete
          </el-button>
        </span>
      </span>
    </el-tree>

    <!-- :visible.sync 为true显示该弹框 -->
    <!-- close-on-click-modal 是否点击弹框外边就会让弹框消失 -->
    <el-dialog :title="title" :visible.sync="dialogVisible" width="30%" :close-on-click-modal="false" >
      <!-- 弹框嵌套 官网 model表单绑定对象 category我们自己的分类对象-->
      <el-form :model="category">
        <el-form-item label="分类名">
          <el-input v-model="category.name" autocomplete="off"></el-input>
        </el-form-item>
        <el-form-item label="图标">
          <el-input v-model="category.icon" autocomplete="off"></el-input>
        </el-form-item>
        <el-form-item label="计量单位">
          <el-input v-model="category.productUnit" autocomplete="off" ></el-input>
        </el-form-item>
      </el-form>
      <span slot="footer" class="dialog-footer">
        <!-- 取消就把他设为false即可 -->
        <el-button @click="dialogVisible = false">取 消</el-button>
        <el-button type="primary" @click="submitData()">确 定</el-button>
      </span>
    </el-dialog>
  </div>
</template>

<script> export default { 
      data() { 
      return { 
      pCid: [], // 是否可以拖动 draggable: false, updateNodes: [], // 最大的层级 maxLevel: 0, // 弹框的标题 title: "", // 弹框的类型,因为修改和添加复用一个弹框 dialogType: "", // 添加的分类对象 category: { 
      // 里面的属性都是根据后端数据库中来定义的 name: "", parentCid: 0, catLevel: 0, showStatus: 1, sort: 0, catId: null, icon: "", productUnit: "", }, // 表单对象 // 是否弹框,默认为false dialogVisible: false, // 需要展开的id expandedKey: [], // 分类对象 menus: [], defaultProps: { 
      children: "children", // 表示children显示data的children属性 label: "name", // label指的是显示数据中的哪个属性这里就显示data中的name属性,这些label、children都可以在官方文档中查看的 }, }; }, methods: { 
      // 批量删除 batchDelete() { 
      let catIds = []; // 拿到组件menuTree let checkedNodes = this.$refs.menuTree.getCheckedNodes(); console.log("被选中的元素", checkedNodes); for (let i = 0; i < checkedNodes.length; i++) { 
      catIds.push(checkedNodes[i].catId); } this.$confirm(`是否批量删除【${ 
       catIds}】菜单?`, "提示", { 
      confirmButtonText: "确定", // 点击确定就调用then cancelButtonText: "取消", type: "warning", }) .then(() => { 
      this.$http({ 
      url: this.$http.adornUrl("/product/category/delete"), method: "post", data: this.$http.adornData(catIds, false), }) .then(({ 
       data }) => { 
      this.$message({ 
      type: "success", message: "菜单批量删除成功!", }); // 刷新出新的菜单 this.getMenus(); }) .catch(() => { 
     }); }) .catch(() => { 
     }); }, // 批量修改 batchSave() { 
      this.$http({ 
      url: this.$http.adornUrl("/product/category/update/sort"), method: "post", data: this.$http.adornData(this.updateNodes, false), }) .then(({ 
       data }) => { 
      this.$message({ 
      type: "success", message: "菜单顺序修改成功!", }); // 刷新出新的菜单 this.getMenus(); // 设置需要默认展开的菜单 this.expandedKey = this.pCid; this.updateNodes = []; this.maxLevel = 0; // this.pCid = 0; }) .catch(() => { 
     }); }, // 拖拽完成触发方法,draggingNode当前拖拽的节点,拖拽到哪个节点dropNode,dropType类型:前面,后面,里面 handleDrop(draggingNode, dropNode, dropType, ev) { 
      console.log("handleDrop: ", draggingNode, dropNode, dropType); //1 当前节点最新的父节点 let pCid = 0; let siblings = null; // 不同拖拽,会有不同的父id if (dropType == "before" || dropType == "after") { 
      pCid = dropNode.parent.data.catId == undefined ? 0 : dropNode.parent.data.catId; siblings = dropNode.parent.childNodes; } else { 
      pCid = dropNode.data.catId; siblings = dropNode.childNodes; } this.pCid.push(pCid); //2 当前拖拽节点的最新顺序 for (let i = 0; i < siblings.length; i++) { 
      if (siblings[i].data.catId == draggingNode.data.catId) { 
      // 如果遍历的是当前正在拖拽的节点 let catLevel = draggingNode.level; if (siblings[i].level != draggingNode.level) { 
      // 当前节点的层级发生变化 catLevel = siblings[i].level; // 修改他子节点的层级 this.updateChildNodeLevlel(siblings[i]); } this.updateNodes.push({ 
      catId: siblings[i].data.catId, sort: i, parentCid: pCid, catLevel: catLevel, }); } else { 
      this.updateNodes.push({ 
      catId: siblings[i].data.catId, sort: i }); } } //3 当前拖拽节点的最新层级 console.log("updateNodes", this.updateNodes); }, updateChildNodeLevlel(node) { 
      if (node.childNodes.length > 0) { 
      for (let i = 0; i < node.childNodes.length; i++) { 
      var cNode = node.childNodes[i].data; this.updateNodes.push({ 
      catId: cNode.catId, catLevel: node.childNodes[i].level, }); this.updateChildNodeLevlel(node.childNodes[i]); } } }, // 判断是否可以拖动 allowDrop(draggingNode, dropNode, type) { 
      //1 被拖动的当前节点以及所在的父节点总层数不能大于3 //1 被拖动的当前节点总层数 // draggingNode当前正在拖动的节点 // dropNode 拖到哪个节点,也就是draggingNode和dropNode同级 console.log("allowDrop:", draggingNode, dropNode, type); // 返回总层数 var level = this.countNodeLevel(draggingNode); // 当前正在拖动的节点+父节点所在的深度不大于3即可 // draggingNode.level表示层级一级标题为1,三级标题为3 // 当前深度 let deep = Math.abs(this.maxLevel - draggingNode.level) + 1; console.log("this.maxLevel", this.maxLevel); console.log("深度:", deep); // this.maxLevel // innner表示拖到里面 if (type == "innner") { 
      // console.log( // `this.maxLevel: ${this.maxLevel}; draggingNode.data.catLevel:${draggingNode.data.catLevel};dropNode.level: ${dropNode.level}` // ); return deep + dropNode.level <= 3; } else { 
      return deep + dropNode.parent.level <= 3; } }, countNodeLevel(node) { 
      // 找到所有子节点,求出最大深度 // 如果当前节点不为null,并且有子节点 if (node.childNodes != null && node.childNodes.length > 0) { 
      for (let i = 0; i < node.childNodes.length; i++) { 
      // 找到最大的深度 if (node.childNodes[i].level > this.maxLevel) { 
      this.maxLevel = node.childNodes[i].level; } this.countNodeLevel(node.childNodes); } } }, // 获取所有的菜单 getMenus(data) { 
      // 自定义方法 this.$http({ 
      // htpp请求 url: this.$http.adornUrl("/product/category/list/tree"), // 请求后端的接口 method: "get", // }).then((data) => { 由于我们的data是一个对象,我们只需要拿到其中的data属性的数据,所以要把data对象解构出来 }).then(({ 
       data }) => { 
      // 成功了之后的操作 // console.log("请求数据," + data); +号不能显示出数据 // console.log("请求数据," , data); ,才可以显示出数据 console.log("请求数据,", data.data); // 解构data对象取出data属性 this.menus = data.data; // 将后端取出的数据给到组件中的menus属性 }); }, // 添加分类方法 addCategory() { 
      console.log("提交的三级分类数据", this.category); // 提交对象到后端,给后端发请求 this.$http({ 
      url: this.$http.adornUrl("/product/category/save"), method: "post", data: this.$http.adornData(this.category, false), }).then(({ 
       data }) => { 
      // 提交成功 this.$message({ 
      message: "保存成功", type: "success", }); // 保存完后,关闭弹框 this.dialogVisible = false; // 刷新出新的菜单 this.getMenus(); // 展开添加菜单的父菜单 this.expandedKey = [this.category.parentCid]; }); }, // 修改分类数据 editCategory() { 
      // 只往后端发需要更新的数据,没有发送的数据为null后端则不会更新 // 从category取出数据,解构对象! var { 
      catId, name, icon, productUnit } = this.category; 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: "菜单修改成功!", }); // 关闭对话框 this.dialogVisible = false; // 刷新出新的菜单 this.getMenus(); // 设置需要默认展开的菜单 this.expandedKey = [this.category.parentCid]; }) .catch(() => { 
     }); }, submitData() { 
      if (this.dialogType == "add") { 
      this.addCategory(); } if (this.dialogType == "edit") { 
      this.editCategory(); } }, append(data) { 
      console.log("append----", data); // 打开弹框 this.dialogVisible = true; this.dialogType = "add"; // 将弹框类型变为添加 this.title = "添加分类"; // 取出当前节点的一些属性赋给要填加的节点 this.category.parentCid = data.catId; // 父id this.category.catLevel = data.catLevel * 1 + 1; // 层级 // 由于我们修改过后的category回显时设置了属性,所以我们添加的时候要清空这些属性,让他等于默认值 this.category.catId = null; // 为null,表示后端添加的时候不加上id,自增 this.category.name = null; this.category.icon = ""; this.category.productUnit = ""; this.category.sort = 0; this.category.showStatus = 1; // this.dialogVisible = true; }, edit(data) { 
      console.log("要修改的数据", data); // 将弹框类型变为修改 this.dialogType = "edit"; this.title = "修改分类"; // 打开弹框 this.dialogVisible = true; // 修改框内显示要修改的name this.category.name = data.name; // 发送请求获取节点最新的数据,因为有并发 this.$http({ 
      url: this.$http.adornUrl(`/product/category/info/${ 
       data.catId}`), method: "get", }).then(({ 
       data }) => { 
      // 请求成功 这个data是从服务器拿过来的data console.log("要回显得数据", data); // 因为服务器返回的data是数据对象,里面还有一个data才是分类对象,所以data.data.name this.category.name = data.data.name; this.category.catId = data.data.catId; this.category.icon = data.data.icon; this.category.productUnit = data.data.productUnit; //由于修改完后要展开父菜单,所以把父id也回显 this.category.parentCid = data.data.parentCid; // this.dialogVisible = true; }); }, remove(node, data) { 
      // 定义我们要删除的id数组,data.catId数据的id,也就是数据库中的id var ids = [data.catId]; // 在删除之前显示一个弹框,官网 this.$confirm(`此操作将永久删除【${ 
       data.name}】文件, 是否继续?`, "提示", { 
      confirmButtonText: "确定", cancelButtonText: "取消", type: "warning", }) .then(() => { 
      // 确定删除才会真正请求删除业务 // 模仿renrne-vue的Post请求写法即可 this.$http({ 
      // adornUrl是renrne-vue定义的工具类:renren-fast-vue\src\utils\httpRequest.js url: this.$http.adornUrl("/product/category/delete"), // 请求的地址 method: "post", // 发送Post请求 data: this.$http.adornData(ids, false), }).then((data) => { 
      // 删除成功,发送一个消息提示 this.$message({ 
      message: "恭喜你,删除成功", type: "success", }); // 删除成功后,要刷新页面 this.getMenus(); // 重新请求方法,就会获取到最新的menus,然而el-tree又和menus是双向绑定的,所以会实时切换 // 刷新后我们想要展开刚才被删除的节点的父结点 // node当前节点,中有个parent属性里面放的是父节点的内容,父节点中的data就是父节点的数据对象,然后再拿到父节点的id即可 this.expandedKey = [node.parent.data.catId]; }); }) .catch(() => { 
      console.log("取消删除"); }); console.log(node, data); // 打印当前结点和当前数据 }, }, created() { 
      // 加载组件的时候就调用改方法 this.getMenus(); }, }; </script>

<style lang="scss" scoped> </style>
  • 设置全局代码片段,将我们常用的代码片段提出来
  • 文件–首选项–用户片段–即可添加
  • 比如下面,下次直接写httpget+回车,就会快捷生成http-get请求里面的片段
"http-get请求": { 
   
	"prefix": "httpget",
	"body": [
		"this.\\$http({",
		"url: this.\\$http.adornUrl(''),",
		"method: 'get',",
		"params: this.\\$http.adornParams({})",
		"}).then(({ data }) => {",
		"})"
	],
	"description": "httpGet请求"
},
"http-post请求": { 
   
	"prefix": "httppost",
	"body": [
		"this.\\$http({",
		"url: this.\\$http.adornUrl(''),",
		"method: 'post',",
		"data: this.\\$http.adornData(data, false)",
		"}).then(({ data }) => { });" 
	],
	"description": "httpPOST请求"
}
  • 修改的时候只提交我们需要修改的数据

在这里插入图片描述

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/164733.html原文链接:https://javaforall.cn

【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛

【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...

(0)


相关推荐

  • python deepcopy函数实现_python 多线程

    python deepcopy函数实现_python 多线程我有一个非常简单的python例程,它涉及循环遍历大约20000个纬度、经度坐标的列表,并计算每个点到参考点的距离。defcompute_nearest_points(lat,lon,nPoints=5):”””FindthenearestNpoints,giventheinputcoordinates.”””points=session.query(PointInd…

  • url转码 java_javaurlencode转码

    url转码 java_javaurlencode转码什么是URL转码不管是以何种方式传递url时,如果要传递的url中包含特殊字符,如想要传递一个+,但是这个+会被url会被编码成空格,想要传递&,被url处理成分隔符。尤其是当传递的url是经过Base64加密或者RSA加密后的,存在特殊字符时,这里的特殊字符一旦被url处理,就不是原先你加密的结果了。url特殊符号及对应的编码: 符号 url中的含义 编码 + URL中+号表示..

    2022年10月28日
  • BeanUtils.copyProperties 用法

    BeanUtils.copyProperties 用法BeanUtils.copyProperties方法不用操作过多的set/get方法,两个类字段相同的时候我们可以通过此方法来进行复制,如有不想复制的属性则用第三个构造参数来进行,如果你只需要改其中的一个属性或者两个属性没必要重新get/set一边直接复制原来的类,付给新类属性名相同即可赋值。…

  • pycharm 激活码 2022【在线注册码/序列号/破解码】

    pycharm 激活码 2022【在线注册码/序列号/破解码】,https://javaforall.cn/100143.html。详细ieda激活码不妨到全栈程序员必看教程网一起来了解一下吧!

  • flash 外国小游戏教程网站[通俗易懂]

    flash 外国小游戏教程网站[通俗易懂]http://www.tutorialized.com/tutorial/game-tutorial-part-1-character-movement/44240  相关的小游戏制作教程:有兴趣可以看看 http://www.emanueleferonato.com/2007/05/15/create-a-flash-racing-game-tutorial/

    2022年10月29日
  • matlab 实现二值图像孔洞填充函数imfill()

    matlab 实现二值图像孔洞填充函数imfill()代码如下:function[I2,locations]=imfill(varargin)[I,locations,conn,do_fillholes]=parse_inputs(varargin{:});ifdo_fillholesifislogical(I)mask=uint8(I);elsemask=I;endmask=padarray(mask,ones(1,ndims(mask)),

发表回复

您的电子邮箱地址不会被公开。

关注全栈程序员社区公众号