主頁 >  其他 > Day398&399.三級分類 -谷粒商城

Day398&399.三級分類 -谷粒商城

2021-09-24 07:48:47 其他

三級分類

0、表結構

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-GntGqpql-1632323412719)(C:/Users/PePe/AppData/Roaming/Typora/typora-user-images/image-20210922203848085.png)]

1、sql腳本

根據老師提供的sql

2、查找所有分類以及子分類

  • 在achangmall-product的com.achang.achangmall.product.controller.CategoryController添加如下方法
/**
     * 查出所有分類以及子分類,以樹形結構組裝
     */
@RequestMapping("/list/tree")
public R list(){
    List<CategoryEntity> treeList = categoryService.listTree();
    return R.ok().put("list", treeList);
}
  • com.achang.achangmall.product.service.impl.CategoryServiceImpl
//查出所有分類以及子分類,以樹形結構組裝
@Override
public List<CategoryEntity> listTree() {
    List<CategoryEntity> allList = baseMapper.selectList(null);

    List<CategoryEntity> parentList = allList.stream()
        .filter(item -> item.getParentCid() == 0)
        .map(item -> {
            item.setChildren(getChildren(item, allList));
            return item;
        }).sorted((item1, item2) -> {
        return item1.getSort() - item2.getSort();
    })
        .collect(Collectors.toList());
    return parentList ;
}

//遞回查找所有選單的子選單
private List<CategoryEntity> getChildren(CategoryEntity root, List<CategoryEntity> allList) {
    List<CategoryEntity> lastList = allList.stream()
        .filter(item -> {return root.getCatId().equals(item.getParentCid());})
        .map(item -> {
            item.setChildren(getChildren(item, allList));
            return item;
        })
        .sorted(
        (item1, item2) -> {
            return (item1.getSort()==null?0:item1.getSort()) - (item2.getSort()==null?0:item2.getSort());
        })
        .collect(Collectors.toList());

    return lastList;
}
  • 請求JSON結果

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-9ENFuZrV-1632323412722)(C:/Users/PePe/AppData/Roaming/Typora/typora-user-images/image-20210922211831278.png)]


3、配置網關路由與路徑重寫

  • 接著操作后臺 localhost:8001 , 點擊系統管理,選單管理,新增 目錄 商品系統 一級選單

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-8mfukTSs-1632323412735)(C:/Users/PePe/AppData/Roaming/Typora/typora-user-images/image-20210922214246487.png)]

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-aDI5mXnQ-1632323412743)(C:/Users/PePe/AppData/Roaming/Typora/typora-user-images/image-20210922214332853.png)]

  • 繼續新增: 選單 分類維護 商品系統 product/category

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-QtQlrG9S-1632323412745)(C:/Users/PePe/AppData/Roaming/Typora/typora-user-images/image-20210922214346930.png)]

在左側點擊【分類維護】,希望在此展示3級分類 注意地址欄http://localhost:8001/#/product-category 可以注意到product-category我們的/被替換為了-

  • 比如sys-role具體的視圖在renren-fast-vue/views/modules/sys/role.vue

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-pu9o7lSY-1632323412748)(C:/Users/PePe/AppData/Roaming/Typora/typora-user-images/image-20210922214536109.png)]

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-lgNrWAGG-1632323412758)(C:/Users/PePe/AppData/Roaming/Typora/typora-user-images/image-20210922214618925.png)]

  • 所以要自定義我們的product/category視圖的話,就是創建mudules/product/category.vue

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-Wugc5DVJ-1632323412759)(C:/Users/PePe/AppData/Roaming/Typora/typora-user-images/image-20210922214651704.png)]

  • 此時我們使用elementui的樹形結構組件

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-lm9FLq5j-1632323412762)(C:/Users/PePe/AppData/Roaming/Typora/typora-user-images/image-20210922214732406.png)]

  • 他要給8080發請求讀取資料,但是資料是在10000埠上,如果找到了這個請求改埠那改起來很麻煩,方法1是改vue專案里的全域配置,方法2是搭建個網關,讓網關路由到10000

Ctrl+Shift+F全域搜索

在static/config/index.js里 window.SITE_CONFIG[‘baseUrl’] = ‘http://localhost:88/api’;

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-R7IZ4z5B-1632323412764)(C:/Users/PePe/AppData/Roaming/Typora/typora-user-images/image-20210922214905069.png)]

接著讓重新登錄http://localhost:8001/#/login,驗證碼是請求88的,所以不顯示,而驗證碼是來源于fast后臺的

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-k8iq9cM0-1632323412765)(C:/Users/PePe/AppData/Roaming/Typora/typora-user-images/image-20210922215249092.png)]

  • 現在的驗證碼請求路徑為,http://localhost:88/api/captcha.jpg?uuid=69c79f02-d15b-478a-8465-a07fd09001e6
  • 原始的驗證碼請求路徑:http://localhost:8001/renren-fast/captcha.jpg?uuid=69c79f02-d15b-478a-8465-a07fd09001e6
  • 88是gateway的埠
spring.cloud.nacos.discovery.server-addr=127.0.0.1:8848
spring.application.name=achangmall-gateway
server.port=88
  • 他要去nacos中查找api服務,但是nacos里是fast服務,就通過把api改成fast服務 ;需要將它交給nacos注冊中心,所以給他加入achangmall-common的依賴
<dependency>
    <groupId>com.achang.achangmall</groupId>
    <artifactId>achangmall-common</artifactId>
    <version>0.0.1-SNAPSHOT</version>
</dependency>
  • 在fast中添加如下配置,指定nacos地址和此服務名
spring:
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848
  application:
    name: renren-fast

如果報錯gson依賴,就匯入google的gson依賴

  • 這里阿昌出現了錯誤Could not find class [org.springframework.cloud.client.loadbalancer.reactive.OnNoRibbonDefaultCondit

發現是阿昌這里所使用的依賴版本是有誤的,服務模塊的專案使用的是2020.1.X的springcloud,所以需要修改:

  • springcloud的版本:Hoxton.RELEASE
  • springboot的版本:2.2.1.RELEASE
  • springcloud alibaba的版本:2.2.1.RELEASE
  • 在gateway中按格式加入
spring:
  cloud:
    gateway:
      routes:
        - id: renren_route
          uri: lb://renren-fast
          predicates:
            - Path=/api/**
          filters:
          		#將/api/的路徑重寫成/renren-fast/
            - RewritePath=/api/(?<segment>.*),/renren-fast/$\{segment}
  • 然后在nacos的服務串列里看到了renren-fast

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-oklveFJK-1632323412766)(C:/Users/PePe/AppData/Roaming/Typora/typora-user-images/image-20210922223800421.png)]

  • 登錄,還是報錯!!!

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-fcB1d3JF-1632323412767)(C:/Users/PePe/AppData/Roaming/Typora/typora-user-images/image-20210922224757337.png)]

  • 這里可知為跨域問題

那我們就在Gateway網關里寫一個配置類去過來請求讓他不跨域

package com.achang.achangmall.gateway.conf;

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;

/******
 @author 阿昌
 @create 2021-09-22 22:41
 *******
 */
@Configuration
public class CorsConfig {

    @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);
    }
}
  • 再次訪問:http://localhost:8001/#/login,發現還是跨域,

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-zfzBzvlk-1632323412768)(C:/Users/PePe/AppData/Roaming/Typora/typora-user-images/image-20210922230336014.png)]

  • 此時這里還跨域的問題是人人開源的服務里面他自己也配置了跨域,這里把他注釋掉

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-gUsiR5L4-1632323412769)(C:/Users/PePe/AppData/Roaming/Typora/typora-user-images/image-20210922230448509.png)]

  • 此時再次登錄,成功!!!

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-jpxkjdaA-1632323412770)(C:/Users/PePe/AppData/Roaming/Typora/typora-user-images/image-20210922230543449.png)]


4、樹形展示三級分類資料

在顯示商品系統/分類資訊的時候,出現了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例外,

  • 解決方法就是定義一個product路由規則,進行路徑重寫: 在網關增加三級分類的路由
        - id: product_route
          uri: lb://achangmall-product
          predicates:
            - Path=/api/product/**
          filters:
            - RewritePath=/api/(?<segment>.*),/$\{segment}
  • 在nacos中新建命名空間,用命名空間隔離專案,(可以在其中新建achangmall-product.yml)

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-wJeMizV4-1632409025602)(C:/Users/PePe/AppData/Roaming/Typora/typora-user-images/image-20210923203628884.png)]

spring:
  datasource:
    password: root
    username: root
    url: jdbc:mysql://192.168.109.101:3306/achangmall-pms?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai
    driver-class-name: com.mysql.jdbc.Driver

  application:
    name: achangmall-product
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848

mybatis-plus:
  mapper-locations: classpath:/mapper/**/*.xml
  global-config:
    db-config:
      id-type: auto

server:
  port: 10000
  • 在product專案中新建bootstrap.properties
spring.application.name=gulimall-product
spring.cloud.nacos.config.server-addr=127.0.0.1:8848
spring.cloud.nacos.config.namespace=07a0c61b-ea34-4c89-9d7e-17a050124943
  • 主類上加上注解@EnableDiscoveryClient

  • 訪問 localhost:88/api/product/category/list/tree invalid token,非法令牌,后臺管理系統中沒有登錄,所以沒有帶令牌

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-62vPQadF-1632409025631)(C:/Users/PePe/AppData/Roaming/Typora/typora-user-images/image-20210923204544528.png)]

  • 這里就需要調整網格Gateway里面的路由順序,讓越模糊的路由越在后面

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-xMeDINj4-1632409025637)(C:/Users/PePe/AppData/Roaming/Typora/typora-user-images/image-20210923204656883.png)]

再次訪問!!!成功!

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-YQxlKjnL-1632409025645)(C:/Users/PePe/AppData/Roaming/Typora/typora-user-images/image-20210923204800783.png)]


  • 接著修改前端部分,data解構,獲取到后端傳來的資料,并賦值給data
.<template>
    <el-tree
             :data="data"
             :props="defaultProps"
             @node-click="handleNodeClick"
             ></el-tree>
</template>

<script>
    export default {
        data() {
            return {
                data: [],//獲取到后端來的資料,并賦值
                defaultProps: {
                    children: "children",
                    label: "name",
                },
            };
        },
        methods: {
            //獲取所有選單
            getMenus() {
                this.$http({
                    url: this.$http.adornUrl(`/product/category/list/tree`),
                    method: "get",
                }).then((resp) => {
                    this.data = resp.data.list;
                });
            },
            handleNodeClick(data) {
                console.log(data);
            },
        },
        created() {
            this.getMenus();
        },
    };
</script>
  • 前端效果圖

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-XxJrFSc0-1632409025650)(C:/Users/PePe/AppData/Roaming/Typora/typora-user-images/image-20210923210245251.png)]


5、洗掉資料

  • scoped slot(插槽):在el-tree標簽里把內容寫到span標簽欄里即可

  • 修改category.vue的代碼

.<template>
<el-tree
         :data="data"
         :props="defaultProps"
         show-checkbox
         @node-click="handleNodeClick"
         :expand-on-click-node="false"
         node-key="catId"
         >
    <span class="custom-tree-node" slot-scope="{ node, data }">
        <span>{{ node.label }}</span>
        <span>
            <el-button
                       v-if="node.level != 3"
                       type="text"
                       size="mini"
                       @click="() => append(data)"
                       >
                Append
    </el-button>
            <el-button
                       v-if="node.childNodes <= 0"
                       type="text"
                       size="mini"
                       @click="() => remove(node, data)"
                       >
                Delete
    </el-button>
    </span>
    </span></el-tree
    >
</template>

<script>
    export default {
        data() {
            return {
                data: [],
                defaultProps: {
                    children: "children",
                    label: "name",
                },
            };
        },
        methods: {
            //添加節點
            append(data) {},
            //洗掉節點
            remove(node, data) {
                console.log(node);
            },
            //獲取所有選單
            getMenus() {
                this.$http({
                    url: this.$http.adornUrl(`/product/category/list/tree`),
                    method: "get",
                }).then((resp) => {
                    this.data = resp.data.list;
                });
            },
            handleNodeClick(data) {
                console.log(data);
            },
        },
        created() {
            this.getMenus();
        },
    };
</script>
  • 撰寫后臺的洗掉介面
/**
* 洗掉
*/
@RequestMapping("/delete")
public R delete(@RequestBody Long[] catIds){
    categoryService.removeCategory(catIds);
    return R.ok();
}

@Override
public void removeCategory(Long[] catIds) {
    baseMapper.deleteBatchIds(Arrays.asList(catIds));
}
  • 撰寫后臺的洗掉介面,這里采用邏輯洗掉,那么首先我們就需要通過MybaitsPlus配置邏輯洗掉

在achangmall-product的application.yaml配置如下

mybatis-plus:
  mapper-locations: classpath:/mapper/**/*.xml
  global-config:
    db-config:
      id-type: auto
      logic-delete-value: 1 #邏輯洗掉的值為1
      logic-not-delete-value: 0 #無邏輯洗掉的值0

在CategoryEntity中指定欄位指定邏輯洗掉

public class CategoryEntity implements Serializable {

	/**
	 * 是否顯示[0-不顯示,1顯示]
	 */
	@TableLogic(value = "1",delval = "0")
	private Integer showStatus;
    
    //省略其他屬性.........
}
  • 另外在“src/main/resources/application.yml”檔案中,設定日志級別,列印出SQL陳述句:
logging:
  level: debug

  • 前端代碼
.<template>
<el-tree
         :data="data"
         :props="defaultProps"
         show-checkbox
         @node-click="handleNodeClick"
         :expand-on-click-node="false"
         node-key="catId"
         :default-expanded-keys="expandedKey"
         >
    <span class="custom-tree-node" slot-scope="{ node, data }">
        <span>{{ node.label }}</span>
        <span>
            <el-button
                       v-if="node.level != 3"
                       type="text"
                       size="mini"
                       @click="() => append(data)"
                       >
                Append
    </el-button>
            <el-button
                       v-if="node.childNodes <= 0"
                       type="text"
                       size="mini"
                       @click="() => remove(node, data)"
                       >
                Delete
    </el-button>
    </span>
    </span></el-tree
    >
</template>

<script>
    export default {
        data() {
            return {
                data: [],
                defaultProps: {
                    children: "children",
                    label: "name",
                },
            };
        },
        methods: {
            //添加節點
            append(data) {},
            //洗掉節點
            remove(node, data) {
                var ids = [data.catId]; //洗掉節點的id
                this.$confirm(`是否洗掉【${data.name}】當前選單?`, "提示", {
                    confirmButtonText: "確定",
                    cancelButtonText: "取消",
                    type: "warning",
                })
                    .then(() => {
                    this.$http({
                        url: this.$http.adornUrl("/product/category/delete"),
                        method: "post",
                        data: this.$http.adornData(ids, false),
                    })
                        .then(({ data }) => {
                        this.$message({
                            type: "success",
                            message: "選單洗掉成功!",
                        });
                        // 重繪出新的選單
                        this.getMenus();
                        this.expandedKey = [node.parent.data.catId];
                    })
                        .catch(() => {});
                })
                    .catch(() => {
                    this.$message({
                        type: "info",
                        message: "已取消洗掉",
                    });
                });
            },
            //獲取所有選單
            getMenus() {
                this.$http({
                    url: this.$http.adornUrl(`/product/category/list/tree`),
                    method: "get",
                }).then((resp) => {
                    this.data = resp.data.list;
                });
            },
            handleNodeClick(data) {
                console.log(data);
            },
        },
        created() {
            this.getMenus();
        },
    };
</script>

6、新增分類

  • 后端代碼,已經逆向生成
@RequestMapping("/save")
public R save(@RequestBody CategoryEntity category){
    categoryService.save(category);

    return R.ok();
}
  • 前端代碼
.<template>
    <div>
        <el-tree
                 :data="data"
                 :props="defaultProps"
                 show-checkbox
                 @node-click="handleNodeClick"
                 :expand-on-click-node="false"
                 node-key="catId"
                 :default-expanded-keys="expandedKey"
                 >
            <span class="custom-tree-node" slot-scope="{ node, data }">
                <span>{{ node.label }}</span>
                <span>
                    <el-button
                               v-if="node.level != 3"
                               type="text"
                               size="mini"
                               @click="() => append(data)"
                               >
                        Append
                    </el-button>
                    <el-button
                               v-if="node.childNodes <= 0"
                               type="text"
                               size="mini"
                               @click="() => remove(node, data)"
                               >
                        Delete
                    </el-button>
                </span>
            </span></el-tree
            >

        <el-dialog title="提示" :visible.sync="dialogVisible" width="30%">
            <el-form :model="category">
                <el-form-item label="分類名稱">
                    <el-input v-model="category.name" autocomplete="off"></el-input>
                </el-form-item>
            </el-form>
            <span slot="footer" class="dialog-footer">
                <el-button @click="dialogVisible = false">取 消</el-button>
                <el-button type="primary" @click="addCategory">確 定</el-button>
            </span>
        </el-dialog>
    </div>
</template>

<script>
    export default {
        data() {
            return {
                dialogVisible: false,
                expandedKey: [],
                category: { name: "", parentCid: 0, catLevel: 0, showStatus: 1, sort: 0 },
                data: [],
                defaultProps: {
                    children: "children",
                    label: "name",
                },
            };
        },
        methods: {
            //添加節點
            append(data) {
                this.category.parentCid = data.catId;
                this.category.catLevel = data.catLevel * 1 + 1;
                this.dialogVisible = true;
            },
            // 添加三級分類
            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({
                        type: "success",
                        message: "選單保存成功!",
                    });
                    this.dialogVisible = false;
                    // 重繪出新的選單
                    this.getMenus();
                    this.expandedKey = [this.category.parentCid];
                })
                    .catch(() => {});
            },
            //洗掉節點
            remove(node, data) {
                var ids = [data.catId]; //洗掉節點的id
                this.$confirm(`是否洗掉【${data.name}】當前選單?`, "提示", {
                    confirmButtonText: "確定",
                    cancelButtonText: "取消",
                    type: "warning",
                })
                    .then(() => {
                    this.$http({
                        url: this.$http.adornUrl("/product/category/delete"),
                        method: "post",
                        data: this.$http.adornData(ids, false),
                    })
                        .then(({ data }) => {
                        this.$message({
                            type: "success",
                            message: "選單洗掉成功!",
                        });
                        // 重繪出新的選單
                        this.getMenus();
                        this.expandedKey = [node.parent.data.catId];
                    })
                        .catch(() => {});
                })
                    .catch(() => {
                    this.$message({
                        type: "info",
                        message: "已取消洗掉",
                    });
                });
            },
            //獲取所有選單
            getMenus() {
                this.$http({
                    url: this.$http.adornUrl(`/product/category/list/tree`),
                    method: "get",
                }).then((resp) => {
                    this.data = resp.data.list;
                });
            },
            handleNodeClick(data) {
                console.log(data);
            },
        },
        created() {
            this.getMenus();
        },
    };
</script>

<style>
</style>

7、修改分類

  • 后端代碼
//資料回顯
@RequestMapping("/info/{catId}")
public R info(@PathVariable("catId") Long catId){
    CategoryEntity category = categoryService.getById(catId);
    return R.ok().put("category", category);
}
  • 前端代碼
.<template>
    <div>
        <el-tree
                 :data="data"
                 :props="defaultProps"
                 show-checkbox
                 @node-click="handleNodeClick"
                 :expand-on-click-node="false"
                 node-key="catId"
                 :default-expanded-keys="expandedKey"
                 >
            <span class="custom-tree-node" slot-scope="{ node, data }">
                <span>{{ node.label }}</span>
                <span>
                    <el-button
                               v-if="node.level != 3"
                               type="text"
                               size="mini"
                               @click="() => append(data)"
                               >
                        Append
                    </el-button>
                    <el-button
                               v-if="node.childNodes <= 0"
                               type="text"
                               size="mini"
                               @click="() => remove(node, data)"
                               >
                        Delete
                    </el-button>
                    <el-button type="text" size="mini" @click="() => edit(data)">
                        Edit
                    </el-button>
                </span>
            </span></el-tree
            >

        <el-dialog title="提示" :visible.sync="dialogVisible" width="30%">
            <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">
                <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 {
                dialogType: "", //edit,add
                title: "",
                dialogVisible: false,
                expandedKey: [],
                category: {
                    name: "",
                    parentCid: 0,
                    catLevel: 0,
                    showStatus: 1,
                    sort: 0,
                    icon: "",
                    productUnit: "",
                    catId: null,
                },
                data: [],
                defaultProps: {
                    children: "children",
                    label: "name",
                },
            };
        },
        methods: {
            //添加節點
            append(data) {
                console.log("append----", data);
                this.dialogType = "add";
                this.title = "添加分類";
                this.category.parentCid = data.catId;
                this.category.catLevel = data.catLevel * 1 + 1;
                this.category.catId = null;
                this.category.name = null;
                this.category.icon = "";
                this.category.productUnit = "";
                this.category.sort = 0;
                this.category.showStatus = 1;
                this.dialogVisible = true;
            },
            // 修改三級分類資料
            editCategory() {
                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(() => {});
            },
            edit(data) {
                console.log("要修改的資料", data);
                this.dialogType = "edit";
                this.title = "修改分類";
                // 發送請求獲取節點最新的資料,資料回顯
                this.$http({
                    url: this.$http.adornUrl(`/product/category/info/${data.catId}`),
                    method: "get",
                }).then((data) => {
                    // 請求成功
                    console.log("要回顯得資料", data);
                    console.log(data);
                    this.category = data.data.category;
                    // console.log(this.category);
                    this.dialogVisible = true;
                });
            },
            //上交
            submitData() {
                if (this.dialogType == "add") {
                    this.addCategory();
                }
                if (this.dialogType == "edit") {
                    this.editCategory();
                }
            },

            // 添加三級分類
            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({
                        type: "success",
                        message: "選單保存成功!",
                    });
                    this.dialogVisible = false;
                    // 重繪出新的選單
                    this.getMenus();
                    this.expandedKey = [this.category.parentCid];
                })
                    .catch(() => {});
            },
            //洗掉節點
            remove(node, data) {
                var ids = [data.catId]; //洗掉節點的id
                this.$confirm(`是否洗掉【${data.name}】當前選單?`, "提示", {
                    confirmButtonText: "確定",
                    cancelButtonText: "取消",
                    type: "warning",
                })
                    .then(() => {
                    this.$http({
                        url: this.$http.adornUrl("/product/category/delete"),
                        method: "post",
                        data: this.$http.adornData(ids, false),
                    })
                        .then(({ data }) => {
                        this.$message({
                            type: "success",
                            message: "選單洗掉成功!",
                        });
                        // 重繪出新的選單
                        this.getMenus();
                        this.expandedKey = [node.parent.data.catId];
                    })
                        .catch(() => {});
                })
                    .catch(() => {
                    this.$message({
                        type: "info",
                        message: "已取消洗掉",
                    });
                });
            },
            //獲取所有選單
            getMenus() {
                this.$http({
                    url: this.$http.adornUrl(`/product/category/list/tree`),
                    method: "get",
                }).then((resp) => {
                    this.data = resp.data.list;
                });
            },
            handleNodeClick(data) {
                console.log(data);
            },
        },
        created() {
            this.getMenus();
        },
    };
</script>

8、拖拽效果

為了防止誤操作,我們通過edit把拖拽功能開啟后才能進行操作,所以添加switch標簽,操作是否可以拖拽,我們也可以體會到el-switch這個標簽是一個開關

批量保存
但是現在存在的一個問題是每次拖拽的時候,都會發送請求,更新資料庫這樣頻繁的與資料庫互動

現在想要實作一個拖拽程序中不更新資料庫,拖拽完成后,統一提交拖拽后的資料,

<el-button v-if="draggable" @click="batchSave">批量保存</el-button>

  • 前端代碼
.<template>
    <div>
        <el-switch
                   v-model="draggable"
                   active-text="開啟拖拽"
                   inactive-text="關閉拖拽"
                   >
        </el-switch>
        <el-button @click="batchSave" v-if="draggable">批量保存</el-button>
        <el-tree
                 :data="data"
                 :props="defaultProps"
                 show-checkbox
                 @node-click="handleNodeClick"
                 :expand-on-click-node="false"
                 node-key="catId"
                 :default-expanded-keys="expandedKey"
                 :draggable="draggable"
                 :allow-drop="allowDrop"
                 >
            <span class="custom-tree-node" slot-scope="{ node, data }">
                <span>{{ node.label }}</span>
                <span>
                    <el-button
                               v-if="node.level != 3"
                               type="text"
                               size="mini"
                               @click="() => append(data)"
                               >
                        Append
                    </el-button>
                    <el-button
                               v-if="node.childNodes <= 0"
                               type="text"
                               size="mini"
                               @click="() => remove(node, data)"
                               >
                        Delete
                    </el-button>
                    <el-button type="text" size="mini" @click="() => edit(data)">
                        Edit
                    </el-button>
                </span>
            </span></el-tree
            >

        <el-dialog title="提示" :visible.sync="dialogVisible" width="30%">
            <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">
                <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,
                dialogType: "", //edit,add
                title: "",
                dialogVisible: false,
                expandedKey: [],
                category: {
                    name: "",
                    parentCid: 0,
                    catLevel: 0,
                    showStatus: 1,
                    sort: 0,
                    icon: "",
                    productUnit: "",
                    catId: null,
                },
                data: [],
                defaultProps: {
                    children: "children",
                    label: "name",
                },
            };
        },
        methods: {
            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(() => {});
            },
            handleDrop(draggingNode, dropNode, dropType, ev) {
                console.log("handleDrop: ", draggingNode, dropNode, dropType);

                //1 當前節點最新的父節點
                let pCid = 0;
                let siblings = null;
                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 被拖動的當前節點總層數
                console.log("allowDrop:", draggingNode, dropNode, type);

                var level = this.countNodeLevel(draggingNode);

                // 當前正在拖動的節點+父節點所在的深度不大于3即可
                let deep = Math.abs(this.maxLevel - draggingNode.level) + 1;
                console.log("深度:", deep);

                // this.maxLevel
                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) {
                // 找到所有子節點,求出最大深度
                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);
                    }
                }
            },
            //添加節點
            append(data) {
                console.log("append----", data);
                this.dialogType = "add";
                this.title = "添加分類";
                this.category.parentCid = data.catId;
                this.category.catLevel = data.catLevel * 1 + 1;
                this.category.catId = null;
                this.category.name = null;
                this.category.icon = "";
                this.category.productUnit = "";
                this.category.sort = 0;
                this.category.showStatus = 1;
                this.dialogVisible = true;
            },
            // 修改三級分類資料
            editCategory() {
                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(() => {});
            },
            edit(data) {
                console.log("要修改的資料", data);
                this.dialogType = "edit";
                this.title = "修改分類";
                // 發送請求獲取節點最新的資料,資料回顯
                this.$http({
                    url: this.$http.adornUrl(`/product/category/info/${data.catId}`),
                    method: "get",
                }).then((data) => {
                    // 請求成功
                    console.log("要回顯得資料", data);
                    console.log(data);
                    this.category = data.data.category;
                    // console.log(this.category);
                    this.dialogVisible = true;
                });
            },
            //上交
            submitData() {
                if (this.dialogType == "add") {
                    this.addCategory();
                }
                if (this.dialogType == "edit") {
                    this.editCategory();
                }
            },

            // 添加三級分類
            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({
                        type: "success",
                        message: "選單保存成功!",
                    });
                    this.dialogVisible = false;
                    // 重繪出新的選單
                    this.getMenus();
                    this.expandedKey = [this.category.parentCid];
                })
                    .catch(() => {});
            },
            //洗掉節點
            remove(node, data) {
                var ids = [data.catId]; //洗掉節點的id
                this.$confirm(`是否洗掉【${data.name}】當前選單?`, "提示", {
                    confirmButtonText: "確定",
                    cancelButtonText: "取消",
                    type: "warning",
                })
                    .then(() => {
                    this.$http({
                        url: this.$http.adornUrl("/product/category/delete"),
                        method: "post",
                        data: this.$http.adornData(ids, false),
                    })
                        .then(({ data }) => {
                        this.$message({
                            type: "success",
                            message: "選單洗掉成功!",
                        });
                        // 重繪出新的選單
                        this.getMenus();
                        this.expandedKey = [node.parent.data.catId];
                    })
                        .catch(() => {});
                })
                    .catch(() => {
                    this.$message({
                        type: "info",
                        message: "已取消洗掉",
                    });
                });
            },
            //獲取所有選單
            getMenus() {
                this.$http({
                    url: this.$http.adornUrl(`/product/category/list/tree`),
                    method: "get",
                }).then((resp) => {
                    this.data = resp.data.list;
                });
            },
            handleNodeClick(data) {
                console.log(data);
            },
        },
        created() {
            this.getMenus();
        },
    };
</script>

<style>
</style>
  • 后端代碼
@RestController
@RequestMapping("product/category")
public class CategoryController {
    @Autowired
    private CategoryService categoryService;
    /**
     * 修改分類
     */
    @RequestMapping("/update/sort")
    // @RequiresPermissions("product:category:update")
    public R update(@RequestBody CategoryEntity[] category){

        categoryService.updateBatchById(Arrays.asList(category));
        return R.ok();
    }

}

9、批量洗掉

  • 前端代碼
.<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>
        <el-tree
                 :data="data"
                 :props="defaultProps"
                 show-checkbox
                 @node-click="handleNodeClick"
                 :expand-on-click-node="false"
                 node-key="catId"
                 :default-expanded-keys="expandedKey"
                 :draggable="draggable"
                 :allow-drop="allowDrop"
                 ref="menuTree"
                 >
            <span class="custom-tree-node" slot-scope="{ node, data }">
                <span>{{ node.label }}</span>
                <span>
                    <el-button
                               v-if="node.level != 3"
                               type="text"
                               size="mini"
                               @click="() => append(data)"
                               >
                        Append
                    </el-button>
                    <el-button
                               v-if="node.childNodes <= 0"
                               type="text"
                               size="mini"
                               @click="() => remove(node, data)"
                               >
                        Delete
                    </el-button>
                    <el-button type="text" size="mini" @click="() => edit(data)">
                        Edit
                    </el-button>
                </span>
            </span></el-tree
            >

        <el-dialog title="提示" :visible.sync="dialogVisible" width="30%">
            <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">
                <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,
                dialogType: "", //edit,add
                title: "",
                dialogVisible: false,
                expandedKey: [],
                category: {
                    name: "",
                    parentCid: 0,
                    catLevel: 0,
                    showStatus: 1,
                    sort: 0,
                    icon: "",
                    productUnit: "",
                    catId: null,
                },
                data: [],
                defaultProps: {
                    children: "children",
                    label: "name",
                },
            };
        },
        methods: {
            // 批量洗掉
            batchDelete() {
                let catIds = [];
                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: "確定",
                    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(() => {});
            },
            handleDrop(draggingNode, dropNode, dropType, ev) {
                console.log("handleDrop: ", draggingNode, dropNode, dropType);

                //1 當前節點最新的父節點
                let pCid = 0;
                let siblings = null;
                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 被拖動的當前節點總層數
                console.log("allowDrop:", draggingNode, dropNode, type);

                var level = this.countNodeLevel(draggingNode);

                // 當前正在拖動的節點+父節點所在的深度不大于3即可
                let deep = Math.abs(this.maxLevel - draggingNode.level) + 1;
                console.log("深度:", deep);

                // this.maxLevel
                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) {
                // 找到所有子節點,求出最大深度
                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);
                    }
                }
            },
            //添加節點
            append(data) {
                console.log("append----", data);
                this.dialogType = "add";
                this.title = "添加分類";
                this.category.parentCid = data.catId;
                this.category.catLevel = data.catLevel * 1 + 1;
                this.category.catId = null;
                this.category.name = null;
                this.category.icon = "";
                this.category.productUnit = "";
                this.category.sort = 0;
                this.category.showStatus = 1;
                this.dialogVisible = true;
            },
            // 修改三級分類資料
            editCategory() {
                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(() => {});
            },
            edit(data) {
                console.log("要修改的資料", data);
                this.dialogType = "edit";
                this.title = "修改分類";
                // 發送請求獲取節點最新的資料,資料回顯
                this.$http({
                    url: this.$http.adornUrl(`/product/category/info/${data.catId}`),
                    method: "get",
                }).then((data) => {
                    // 請求成功
                    console.log("要回顯得資料", data);
                    console.log(data);
                    this.category = data.data.category;
                    // console.log(this.category);
                    this.dialogVisible = true;
                });
            },
            //上交
            submitData() {
                if (this.dialogType == "add") {
                    this.addCategory();
                }
                if (this.dialogType == "edit") {
                    this.editCategory();
                }
            },

            // 添加三級分類
            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({
                        type: "success",
                        message: "選單保存成功!",
                    });
                    this.dialogVisible = false;
                    // 重繪出新的選單
                    this.getMenus();
                    this.expandedKey = [this.category.parentCid];
                })
                    .catch(() => {});
            },
            //洗掉節點
            remove(node, data) {
                var ids = [data.catId]; //洗掉節點的id
                this.$confirm(`是否洗掉【${data.name}】當前選單?`, "提示", {
                    confirmButtonText: "確定",
                    cancelButtonText: "取消",
                    type: "warning",
                })
                    .then(() => {
                    this.$http({
                        url: this.$http.adornUrl("/product/category/delete"),
                        method: "post",
                        data: this.$http.adornData(ids, false),
                    })
                        .then(({ data }) => {
                        this.$message({
                            type: "success",
                            message: "選單洗掉成功!",
                        });
                        // 重繪出新的選單
                        this.getMenus();
                        this.expandedKey = [node.parent.data.catId];
                    })
                        .catch(() => {});
                })
                    .catch(() => {
                    this.$message({
                        type: "info",
                        message: "已取消洗掉",
                    });
                });
            },
            //獲取所有選單
            getMenus() {
                this.$http({
                    url: this.$http.adornUrl(`/product/category/list/tree`),
                    method: "get",
                }).then((resp) => {
                    this.data = resp.data.list;
                });
            },
            handleNodeClick(data) {
                console.log(data);
            },
        },
        created() {
            this.getMenus();
        },
    };
</script>

<style>
</style>
  • 后端代碼
@RequestMapping("/delete")
public R delete(@RequestBody Long[] catIds){
    categoryService.removeCategory(catIds);
    return R.ok();
}
  • 效果

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-S2AYFyHx-1632409025660)(C:/Users/PePe/AppData/Roaming/Typora/typora-user-images/image-20210923225501258.png)]

轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/302428.html

標籤:AI

上一篇:混淆矩陣是什么?Python多分類的混淆矩陣計算及可視化(包含原始混淆矩陣及歸一化的混淆矩陣):基于skelarn框架iris資料集

下一篇:深度學習100例 | 第52天-圖卷積神經網路(GCN):實作論文分類

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 網閘典型架構簡述

    網閘架構一般分為兩種:三主機的三系統架構網閘和雙主機的2+1架構網閘。 三主機架構分別為內端機、外端機和仲裁機。三機無論從軟體和硬體上均各自獨立。首先從硬體上來看,三機都用各自獨立的主板、記憶體及存盤設備。從軟體上來看,三機有各自獨立的作業系統。這樣能達到完全的三機獨立。對于“2+1”系統,“2”分為 ......

    uj5u.com 2020-09-10 02:00:44 more
  • 如何從xshell上傳檔案到centos linux虛擬機里

    如何從xshell上傳檔案到centos linux虛擬機里及:虛擬機CentOs下執行 yum -y install lrzsz命令,出現錯誤:鏡像無法找到軟體包 前言 一、安裝lrzsz步驟 二、上傳檔案 三、遇到的問題及解決方案 總結 前言 提示:其實很簡單,往虛擬機上安裝一個上傳檔案的工具 ......

    uj5u.com 2020-09-10 02:00:47 more
  • 一、SQLMAP入門

    一、SQLMAP入門 1、判斷是否存在注入 sqlmap.py -u 網址/id=1 id=1不可缺少。當注入點后面的引數大于兩個時。需要加雙引號, sqlmap.py -u "網址/id=1&uid=1" 2、判斷文本中的請求是否存在注入 從文本中加載http請求,SQLMAP可以從一個文本檔案中 ......

    uj5u.com 2020-09-10 02:00:50 more
  • Metasploit 簡單使用教程

    metasploit 簡單使用教程 浩先生, 2020-08-28 16:18:25 分類專欄: kail 網路安全 linux 文章標簽: linux資訊安全 編輯 著作權 metasploit 使用教程 前言 一、Metasploit是什么? 二、準備作業 三、具體步驟 前言 Msfconsole ......

    uj5u.com 2020-09-10 02:00:53 more
  • 游戲逆向之驅動層與用戶層通訊

    驅動層代碼: #pragma once #include <ntifs.h> #define add_code CTL_CODE(FILE_DEVICE_UNKNOWN,0x800,METHOD_BUFFERED,FILE_ANY_ACCESS) /* 更多游戲逆向視頻www.yxfzedu.com ......

    uj5u.com 2020-09-10 02:00:56 more
  • 北斗電力時鐘(北斗授時服務器)讓網路資料更精準

    北斗電力時鐘(北斗授時服務器)讓網路資料更精準 北斗電力時鐘(北斗授時服務器)讓網路資料更精準 京準電子科技官微——ahjzsz 近幾年,資訊技術的得了快速發展,互聯網在逐漸普及,其在人們生活和生產中都得到了廣泛應用,并且取得了不錯的應用效果。計算機網路資訊在電力系統中的應用,一方面使電力系統的運行 ......

    uj5u.com 2020-09-10 02:01:03 more
  • 【CTF】CTFHub 技能樹 彩蛋 writeup

    ?碎碎念 CTFHub:https://www.ctfhub.com/ 筆者入門CTF時時剛開始刷的是bugku的舊平臺,后來才有了CTFHub。 感覺不論是網頁UI設計,還是題目質量,賽事跟蹤,工具軟體都做得很不錯。 而且因為獨到的金幣制度的確讓人有一種想去刷題賺金幣的感覺。 個人還是非常喜歡這個 ......

    uj5u.com 2020-09-10 02:04:05 more
  • 02windows基礎操作

    我學到了一下幾點 Windows系統目錄結構與滲透的作用 常見Windows的服務詳解 Windows埠詳解 常用的Windows注冊表詳解 hacker DOS命令詳解(net user / type /md /rd/ dir /cd /net use copy、批處理 等) 利用dos命令制作 ......

    uj5u.com 2020-09-10 02:04:18 more
  • 03.Linux基礎操作

    我學到了以下幾點 01Linux系統介紹02系統安裝,密碼啊破解03Linux常用命令04LAMP 01LINUX windows: win03 8 12 16 19 配置不繁瑣 Linux:redhat,centos(紅帽社區版),Ubuntu server,suse unix:金融機構,證券,銀 ......

    uj5u.com 2020-09-10 02:04:30 more
  • 05HTML

    01HTML介紹 02頭部標簽講解03基礎標簽講解04表單標簽講解 HTML前段語言 js1.了解代碼2.根據代碼 懂得挖掘漏洞 (POST注入/XSS漏洞上傳)3.黑帽seo 白帽seo 客戶網站被黑帽植入劫持代碼如何處理4.熟悉html表單 <html><head><title>TDK標題,描述 ......

    uj5u.com 2020-09-10 02:04:36 more
最新发布
  • 2023年最新微信小程式抓包教程

    01 開門見山 隔一個月發一篇文章,不過分。 首先回顧一下《微信系結手機號資料庫被脫庫事件》,我也是第一時間得知了這個訊息,然后跟蹤了整件事情的經過。下面是這起事件的相關截圖以及近日流出的一萬條資料樣本: 個人認為這件事也沒什么,還不如關注一下之前45億快遞資料查詢渠道疑似在近日復活的訊息。 訊息是 ......

    uj5u.com 2023-04-20 08:48:24 more
  • web3 產品介紹:metamask 錢包 使用最多的瀏覽器插件錢包

    Metamask錢包是一種基于區塊鏈技術的數字貨幣錢包,它允許用戶在安全、便捷的環境下管理自己的加密資產。Metamask錢包是以太坊生態系統中最流行的錢包之一,它具有易于使用、安全性高和功能強大等優點。 本文將詳細介紹Metamask錢包的功能和使用方法。 一、 Metamask錢包的功能 數字資 ......

    uj5u.com 2023-04-20 08:47:46 more
  • vulnhub_Earth

    前言 靶機地址->>>vulnhub_Earth 攻擊機ip:192.168.20.121 靶機ip:192.168.20.122 參考文章 https://www.cnblogs.com/Jing-X/archive/2022/04/03/16097695.html https://www.cnb ......

    uj5u.com 2023-04-20 07:46:20 more
  • 從4k到42k,軟體測驗工程師的漲薪史,給我看哭了

    清明節一過,盲猜大家已經無心上班,在數著日子準備過五一,但一想到銀行卡里的余額……瞬間心情就不美麗了。最近,2023年高校畢業生就業調查顯示,本科畢業月平均起薪為5825元。調查一出,便有很多同學表示自己又被平均了。看著這一資料,不免讓人想到前不久中國青年報的一項調查:近六成大學生認為畢業10年內會 ......

    uj5u.com 2023-04-20 07:44:00 more
  • 最新版本 Stable Diffusion 開源 AI 繪畫工具之中文自動提詞篇

    🎈 標簽生成器 由于輸入正向提示詞 prompt 和反向提示詞 negative prompt 都是使用英文,所以對學習母語的我們非常不友好 使用網址:https://tinygeeker.github.io/p/ai-prompt-generator 這個網址是為了讓大家在使用 AI 繪畫的時候 ......

    uj5u.com 2023-04-20 07:43:36 more
  • 漫談前端自動化測驗演進之路及測驗工具分析

    隨著前端技術的不斷發展和應用程式的日益復雜,前端自動化測驗也在不斷演進。隨著 Web 應用程式變得越來越復雜,自動化測驗的需求也越來越高。如今,自動化測驗已經成為 Web 應用程式開發程序中不可或缺的一部分,它們可以幫助開發人員更快地發現和修復錯誤,提高應用程式的性能和可靠性。 ......

    uj5u.com 2023-04-20 07:43:16 more
  • CANN開發實踐:4個DVPP記憶體問題的典型案例解讀

    摘要:由于DVPP媒體資料處理功能對存放輸入、輸出資料的記憶體有更高的要求(例如,記憶體首地址128位元組對齊),因此需呼叫專用的記憶體申請介面,那么本期就分享幾個關于DVPP記憶體問題的典型案例,并給出原因分析及解決方法。 本文分享自華為云社區《FAQ_DVPP記憶體問題案例》,作者:昇騰CANN。 DVPP ......

    uj5u.com 2023-04-20 07:43:03 more
  • msf學習

    msf學習 以kali自帶的msf為例 一、msf核心模塊與功能 msf模塊都放在/usr/share/metasploit-framework/modules目錄下 1、auxiliary 輔助模塊,輔助滲透(埠掃描、登錄密碼爆破、漏洞驗證等) 2、encoders 編碼器模塊,主要包含各種編碼 ......

    uj5u.com 2023-04-20 07:42:59 more
  • Halcon軟體安裝與界面簡介

    1. 下載Halcon17版本到到本地 2. 雙擊安裝包后 3. 步驟如下 1.2 Halcon軟體安裝 界面分為四大塊 1. Halcon的五個助手 1) 影像采集助手:與相機連接,設定相機引數,采集影像 2) 標定助手:九點標定或是其它的標定,生成標定檔案及內參外參,可以將像素單位轉換為長度單位 ......

    uj5u.com 2023-04-20 07:42:17 more
  • 在MacOS下使用Unity3D開發游戲

    第一次發博客,先發一下我的游戲開發環境吧。 去年2月份買了一臺MacBookPro2021 M1pro(以下簡稱mbp),這一年來一直在用mbp開發游戲。我大致分享一下我的開發工具以及使用體驗。 1、Unity 官網鏈接: https://unity.cn/releases 我一般使用的Apple ......

    uj5u.com 2023-04-20 07:40:19 more