主頁 > 後端開發 > 谷粒商城_04_商品CRUD

谷粒商城_04_商品CRUD

2021-12-17 08:42:38 後端開發

文章目錄

  • 商品服務-分類管理
    • 查出所有分類以及子分類
      • 后端撰寫
      • 前端展示
        • 服務注冊
        • 配置網關
        • 503問題
      • 跨域
        • 跨域流程
        • 解決跨域
        • 服務注冊
        • 配置網關
    • 洗掉資料
      • 后端介面
        • 邏輯洗掉
        • 測驗
      • 前端請求
        • 洗掉資料
        • 新增分類
        • 修改分類
          • 拖拽效果
          • 批量洗掉

谷粒商城_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,發現服務

在這里插入圖片描述

配置網關

  • 驗證碼前端請求:http://localhost:88/api/captcha.jpg 我們需要的是:http://localhost:8080/renren-fast/captcha.jpg

在這里插入圖片描述

  • 配置網關
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);
    }
}

服務注冊

  • 在顯示商品系統/分類資訊的時候,出現了404例外,請求的http://localhost:88/api/product/category/list/tree不存在
    這是因為網關上所做的路徑映射不正確,映射后的路徑為http://localhost:8080/renren-fast/product/category/list/tree
    但是只有通過http://localhost:10000/product/category/list/tree路徑才能夠正常訪問,所以會報404例外,
  • 所以我們注冊gulimall-product到服務中心,重新再配置網關

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請求"
}
  • 修改的時候只提交我們需要修改的資料

在這里插入圖片描述

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

標籤:java

上一篇:MySQL MVCC底層原理決議

下一篇:二十年碼農現在資產有多少?實作人身自由爽歪歪

標籤雲
其他(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)

熱門瀏覽
  • 【C++】Microsoft C++、C 和匯編程式檔案

    ......

    uj5u.com 2020-09-10 00:57:23 more
  • 例外宣告

    相比于斷言適用于排除邏輯上不可能存在的狀態,例外通常是用于邏輯上可能發生的錯誤。 例外宣告 Item 1:當函式不可能拋出例外或不能接受拋出例外時,使用noexcept 理由 如果不打算拋出例外的話,程式就會認為無法處理這種錯誤,并且應當盡早終止,如此可以有效地阻止例外的傳播與擴散。 示例 //不可 ......

    uj5u.com 2020-09-10 00:57:27 more
  • Codeforces 1400E Clear the Multiset(貪心 + 分治)

    鏈接:https://codeforces.com/problemset/problem/1400/E 來源:Codeforces 思路:給你一個陣列,現在你可以進行兩種操作,操作1:將一段沒有 0 的區間進行減一的操作,操作2:將 i 位置上的元素歸零。最終問:將這個陣列的全部元素歸零后操作的最少 ......

    uj5u.com 2020-09-10 00:57:30 more
  • UVA11610 【Reverse Prime】

    本人看到此題沒有翻譯,就附帶了一個自己的翻譯版本 思考 這一題,它的第一個要求是找出所有 $7$ 位反向質數及其質因數的個數。 我們應該需要質數篩篩選1~$10^{7}$的所有數,這里就不慢慢介紹了。但是,重讀題,我們突然發現反向質數都是 $7$ 位,而將它反過來后的數字卻是 $6$ 位數,這就說明 ......

    uj5u.com 2020-09-10 00:57:36 more
  • 統計區間素數數量

    1 #pragma GCC optimize(2) 2 #include <bits/stdc++.h> 3 using namespace std; 4 bool isprime[1000000010]; 5 vector<int> prime; 6 inline int getlist(int ......

    uj5u.com 2020-09-10 00:57:47 more
  • C/C++編程筆記:C++中的 const 變數詳解,教你正確認識const用法

    1、C中的const 1、區域const變數存放在堆疊區中,會分配記憶體(也就是說可以通過地址間接修改變數的值)。測驗代碼如下: 運行結果: 2、全域const變數存放在只讀資料段(不能通過地址修改,會發生寫入錯誤), 默認為外部聯編,可以給其他源檔案使用(需要用extern關鍵字修飾) 運行結果: ......

    uj5u.com 2020-09-10 00:58:04 more
  • 【C++犯錯記錄】VS2019 MFC添加資源不懂如何修改資源宏ID

    1. 首先在資源視圖中,添加資源 2. 點擊新添加的資源,復制自動生成的ID 3. 在解決方案資源管理器中找到Resource.h檔案,編輯,使用整個專案搜索和替換的方式快速替換 宏宣告 4. Ctrl+Shift+F 全域搜索,點擊查找全部,然后逐個替換 5. 為什么使用搜索替換而不使用屬性視窗直 ......

    uj5u.com 2020-09-10 00:59:11 more
  • 【C++犯錯記錄】VS2019 MFC不懂的批量添加資源

    1. 打開資源頭檔案Resource.h,在其中預先定義好宏 ID(不清楚其實ID值應該設定多少,可以先新建一個相同的資源項,再在這個資源的ID值的基礎上遞增即可) 2. 在資源視圖中選中專案資源,按F7編輯資源檔案,按 ID 型別 相對路徑的形式添加 資源。(別忘了先把檔案拷貝到專案中的res檔案 ......

    uj5u.com 2020-09-10 01:00:19 more
  • C/C++編程筆記:關于C++的參考型別,專供新手入門使用

    今天要講的是C++中我最喜歡的一個用法——參考,也叫別名。 參考就是給一個變數名取一個變數名,方便我們間接地使用這個變數。我們可以給一個變數創建N個參考,這N + 1個變數共享了同一塊記憶體區域。(參考型別的變數會占用記憶體空間,占用的記憶體空間的大小和指標型別的大小是相同的。雖然參考是一個物件的別名,但 ......

    uj5u.com 2020-09-10 01:00:22 more
  • 【C/C++編程筆記】從頭開始學習C ++:初學者完整指南

    眾所周知,C ++的學習曲線陡峭,但是花時間學習這種語言將為您的職業帶來奇跡,并使您與其他開發人員區分開。您會更輕松地學習新語言,形成真正的解決問題的技能,并在編程的基礎上打下堅實的基礎。 C ++將幫助您養成良好的編程習慣(即清晰一致的編碼風格,在撰寫代碼時注釋代碼,并限制類內部的可見性),并且由 ......

    uj5u.com 2020-09-10 01:00:41 more
最新发布
  • Rust中的智能指標:Box<T> Rc<T> Arc<T> Cell<T> RefCell<T> Weak

    Rust中的智能指標是什么 智能指標(smart pointers)是一類資料結構,是擁有資料所有權和額外功能的指標。是指標的進一步發展 指標(pointer)是一個包含記憶體地址的變數的通用概念。這個地址參考,或 ” 指向”(points at)一些其 他資料 。參考以 & 符號為標志并借用了他們所 ......

    uj5u.com 2023-04-20 07:24:10 more
  • Java的值傳遞和參考傳遞

    值傳遞不會改變本身,參考傳遞(如果傳遞的值需要實體化到堆里)如果發生修改了會改變本身。 1.基本資料型別都是值傳遞 package com.example.basic; public class Test { public static void main(String[] args) { int ......

    uj5u.com 2023-04-20 07:24:04 more
  • [2]SpinalHDL教程——Scala簡單入門

    第一個 Scala 程式 shell里面輸入 $ scala scala> 1 + 1 res0: Int = 2 scala> println("Hello World!") Hello World! 檔案形式 object HelloWorld { /* 這是我的第一個 Scala 程式 * 以 ......

    uj5u.com 2023-04-20 07:23:58 more
  • 理解函式指標和回呼函式

    理解 函式指標 指向函式的指標。比如: 理解函式指標的偽代碼 void (*p)(int type, char *data); // 定義一個函式指標p void func(int type, char *data); // 宣告一個函式func p = func; // 將指標p指向函式func ......

    uj5u.com 2023-04-20 07:23:52 more
  • Django筆記二十五之資料庫函式之日期函式

    本文首發于公眾號:Hunter后端 原文鏈接:Django筆記二十五之資料庫函式之日期函式 日期函式主要介紹兩個大類,Extract() 和 Trunc() Extract() 函式作用是提取日期,比如我們可以提取一個日期欄位的年份,月份,日等資料 Trunc() 的作用則是截取,比如 2022-0 ......

    uj5u.com 2023-04-20 07:23:45 more
  • 一天吃透JVM面試八股文

    什么是JVM? JVM,全稱Java Virtual Machine(Java虛擬機),是通過在實際的計算機上仿真模擬各種計算機功能來實作的。由一套位元組碼指令集、一組暫存器、一個堆疊、一個垃圾回收堆和一個存盤方法域等組成。JVM屏蔽了與作業系統平臺相關的資訊,使得Java程式只需要生成在Java虛擬機 ......

    uj5u.com 2023-04-20 07:23:31 more
  • 使用Java接入小程式訂閱訊息!

    更新完微信服務號的模板訊息之后,我又趕緊把微信小程式的訂閱訊息給實作了!之前我一直以為微信小程式也是要企業才能申請,沒想到小程式個人就能申請。 訊息推送平臺🔥推送下發【郵件】【短信】【微信服務號】【微信小程式】【企業微信】【釘釘】等訊息型別。 https://gitee.com/zhongfuch ......

    uj5u.com 2023-04-20 07:22:59 more
  • java -- 緩沖流、轉換流、序列化流

    緩沖流 緩沖流, 也叫高效流, 按照資料型別分類: 位元組緩沖流:BufferedInputStream,BufferedOutputStream 字符緩沖流:BufferedReader,BufferedWriter 緩沖流的基本原理,是在創建流物件時,會創建一個內置的默認大小的緩沖區陣列,通過緩沖 ......

    uj5u.com 2023-04-20 07:22:49 more
  • Java-SpringBoot-Range請求頭設定實作視頻分段傳輸

    老實說,人太懶了,現在基本都不喜歡寫筆記了,但是網上有關Range請求頭的文章都太水了 下面是抄的一段StackOverflow的代碼...自己大修改過的,寫的注釋挺全的,應該直接看得懂,就不解釋了 寫的不好...只是希望能給視頻網站開發的新手一點點幫助吧. 業務場景:視頻分段傳輸、視頻多段傳輸(理 ......

    uj5u.com 2023-04-20 07:22:42 more
  • Windows 10開發教程_編程入門自學教程_菜鳥教程-免費教程分享

    教程簡介 Windows 10開發入門教程 - 從簡單的步驟了解Windows 10開發,從基本到高級概念,包括簡介,UWP,第一個應用程式,商店,XAML控制元件,資料系結,XAML性能,自適應設計,自適應UI,自適應代碼,檔案管理,SQLite資料庫,應用程式到應用程式通信,應用程式本地化,應用程式 ......

    uj5u.com 2023-04-20 07:22:35 more