三級分類
0、表結構
![[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-GntGqpql-1632323412719)(C:/Users/PePe/AppData/Roaming/Typora/typora-user-images/image-20210922203848085.png)]](https://img.uj5u.com/2021/09/24/267200240739511.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)]](https://img.uj5u.com/2021/09/24/267200240739514.png)
3、配置網關路由與路徑重寫
- 接著操作后臺 localhost:8001 , 點擊系統管理,選單管理,新增 目錄 商品系統 一級選單
![[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-8mfukTSs-1632323412735)(C:/Users/PePe/AppData/Roaming/Typora/typora-user-images/image-20210922214246487.png)]](https://img.uj5u.com/2021/09/24/267200240739515.png)
![[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-aDI5mXnQ-1632323412743)(C:/Users/PePe/AppData/Roaming/Typora/typora-user-images/image-20210922214332853.png)]](https://img.uj5u.com/2021/09/24/267200240739516.png)
- 繼續新增: 選單 分類維護 商品系統 product/category
![[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-QtQlrG9S-1632323412745)(C:/Users/PePe/AppData/Roaming/Typora/typora-user-images/image-20210922214346930.png)]](https://img.uj5u.com/2021/09/24/267200240739517.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)]](https://img.uj5u.com/2021/09/24/267200240739518.png)
![[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-lgNrWAGG-1632323412758)(C:/Users/PePe/AppData/Roaming/Typora/typora-user-images/image-20210922214618925.png)]](https://img.uj5u.com/2021/09/24/267200240739519.png)
- 所以要自定義我們的product/category視圖的話,就是創建mudules/product/category.vue
![[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-Wugc5DVJ-1632323412759)(C:/Users/PePe/AppData/Roaming/Typora/typora-user-images/image-20210922214651704.png)]](https://img.uj5u.com/2021/09/24/2672002407395110.png)
- 此時我們使用elementui的樹形結構組件
![[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-lm9FLq5j-1632323412762)(C:/Users/PePe/AppData/Roaming/Typora/typora-user-images/image-20210922214732406.png)]](https://img.uj5u.com/2021/09/24/267200240739512.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)]](https://img.uj5u.com/2021/09/24/2672002407395111.png)
接著讓重新登錄http://localhost:8001/#/login,驗證碼是請求88的,所以不顯示,而驗證碼是來源于fast后臺的
![[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-k8iq9cM0-1632323412765)(C:/Users/PePe/AppData/Roaming/Typora/typora-user-images/image-20210922215249092.png)]](https://img.uj5u.com/2021/09/24/2672002407395112.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)]](https://img.uj5u.com/2021/09/24/2672002407395113.png)
- 登錄,還是報錯!!!
![[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-fcB1d3JF-1632323412767)(C:/Users/PePe/AppData/Roaming/Typora/typora-user-images/image-20210922224757337.png)]](https://img.uj5u.com/2021/09/24/2672002407395114.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)]](https://img.uj5u.com/2021/09/24/267200240739513.png)
- 此時這里還跨域的問題是
人人開源的服務里面他自己也配置了跨域,這里把他注釋掉
![[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-gUsiR5L4-1632323412769)(C:/Users/PePe/AppData/Roaming/Typora/typora-user-images/image-20210922230448509.png)]](https://img.uj5u.com/2021/09/24/2672002407395115.png)
- 此時再次登錄,成功!!!
![[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-jpxkjdaA-1632323412770)(C:/Users/PePe/AppData/Roaming/Typora/typora-user-images/image-20210922230543449.png)]](https://img.uj5u.com/2021/09/24/2672002407395116.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)]](https://img.uj5u.com/2021/09/24/2672002407395117.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)]](https://img.uj5u.com/2021/09/24/2672002407395118.png)
- 這里就需要調整網格Gateway里面的路由順序,讓
越模糊的路由越在后面
![[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-xMeDINj4-1632409025637)(C:/Users/PePe/AppData/Roaming/Typora/typora-user-images/image-20210923204656883.png)]](https://img.uj5u.com/2021/09/24/2672002407395119.png)
再次訪問!!!成功!
![[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-YQxlKjnL-1632409025645)(C:/Users/PePe/AppData/Roaming/Typora/typora-user-images/image-20210923204800783.png)]](https://img.uj5u.com/2021/09/24/2672002407395120.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)]](https://img.uj5u.com/2021/09/24/2672002407395121.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://img.uj5u.com/2021/09/24/2672002407395122.png)
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/302428.html
標籤:AI
上一篇:混淆矩陣是什么?Python多分類的混淆矩陣計算及可視化(包含原始混淆矩陣及歸一化的混淆矩陣):基于skelarn框架iris資料集
