(1)開發環境
IDEA + JDK1.8 + mysql 1.8
SpringBoot 2.2.6 + mybatis-plus
此處僅後臺開發(返回 json 數據),前臺頁面展現後續會講解。css
(2)數據表
以下,僅供參考,能夠添加 修改時間、建立時間、邏輯刪除等字段。html
DROP DATABASE IF EXISTS test; CREATE DATABASE test; USE test; /* 用於測試 樹形的菜單(以商品爲例) */ CREATE TABLE tree_menu( menu_id bigint NOT NULL AUTO_INCREMENT COMMENT "當前菜單ID", name char(50) COMMENT "菜單名", parent_menu_id bigint COMMENT "當前菜單的父菜單 ID", meun_level int COMMENT "當前菜單的層級", sort int COMMENT "排序", PRIMARY KEY (menu_id) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT="樹形菜單";
(3)插入測試數據
爲了數據直觀顯示,以下插入數據,每段數據表明一個菜單欄。
注:
parent_menu_id 從 0 開始,0 表示第一級菜單(沒有父菜單)。
meun_level 從 1 開始,1 表示第一級菜單,2 表示第二級菜單。前端
INSERT INTO tree_menu(menu_id, name, parent_menu_id, meun_level, sort) VALUES (1, '廚具', 0, 1, 0), (2, '刀具', 1, 2, 0),(3, '烹飪工具', 1, 2, 0),(4, '餐具', 1, 2, 0), (5, '菜刀', 2, 3, 0),(6, '剪刀', 2, 3, 0),(7, '水果刀', 2, 3, 0), (8, '炒菜鍋', 3, 3, 0),(9, '壓力鍋', 3, 3, 0),(10, '平底鍋', 3, 3, 0), (11, '筷子', 4, 3, 0),(12, '碗', 4, 3, 0),(13, '果盤', 4, 3, 0), (14, '家用電器', 0, 1, 0), (15, '你們電', 14, 2, 0),(16, '生活家電', 14, 2, 0), (17, '電視', 15, 3, 0),(18, '電腦', 15, 3, 0),(19, '洗衣機', 15, 3, 0),(20, '冰箱', 15, 3, 0), (21, '電風扇', 16, 3, 0),(22, '吸塵器', 16, 3, 0),(23, '飲水機', 16, 3, 0),(24, '加溼器', 16, 3, 0), (25, '數碼', 0, 1, 0), (26, '攝像攝影', 25, 2, 0),(27, '影音娛樂', 25, 2, 0),(28, '教育學習', 25, 2, 0), (29, '數碼相機', 26, 3, 0),(30, '單反相機', 26, 3, 0),(31, '攝像機', 26, 3, 0),(32, '拍立得', 26, 3, 0), (33, 'MP3/MP4/MP5/PSP', 27, 3, 0),(34, '音箱', 27, 3, 0),(35, '麥克風', 27, 3, 0), (36, '學平生板', 28, 3, 0),(37, '復讀機', 28, 3, 0),(38, '電子辭典', 28, 3, 0),(39, '點讀機', 28, 3, 0)
(1)使用 Easycode 插件根據數據表逆向生成相關代碼。
參考地址:
https://www.cnblogs.com/l-y-h/p/12781586.html#_label0_2vue
(2)測試代碼是否能成功調用。
Step1:給 TreeMenuDao 加上 @Mapper 註解。node
Step2:啓動服務並訪問 TreeMenuController。mysql
Step3:訪問 http://localhost:9000/treeMenu/selectOne?id=1,查詢成功即逆向生成的代碼沒問題。ios
(1)實現思路:
每條記錄裏都有 當前菜單 ID,以及 當前菜單的父菜單 ID。
想要查詢出菜單的樹形結構,能夠根據這兩個 ID 來實現。
思路:
首先一次性從數據庫中查詢出全部的菜單數據。
而後定位到 第一級 菜單,遞歸遍歷出其全部的子菜單。sql
(2)一次性查詢出全部的數據。
Step1:在 service 中添加一個查詢全部數據的方法。vue-cli
/** * 查詢數據庫全部數據 * @return */ List<TreeMenu> queryAll();
Step2:在 service 的實現類中重寫該方法。數據庫
/** * 查詢數據庫全部數據 * @return 數據庫全部數據 */ @Override public List<TreeMenu> queryAll() { return treeMenuDao.queryAll(null); }
Step3:修改 controller,調用該方法。
/** * 獲取數據庫全部數據 * @return 全部數據 */ @GetMapping("selectAll") public Result selectAll() { return Result.ok().data("items", treeMenuService.queryAll()); }
Step4:啓動服務,訪問。打開控制檯,能夠看到返回的 json 數據。
(3)對查詢的數據進行處理,返回樹形的 json 數據。
Step1:對於菜單實體類,增長一個實體類屬性,用於保存其子菜單數據。
/** * 用於保存一個菜單的子菜單 */ @TableField(exist = false) private List<TreeMenu> treeMenu;
Step2:在 service 中添加一個查詢全部數據並返回樹形 json 的方法。
/** * 查詢數據庫數據,並處理後返回 樹形數據 * @return 樹形數據 */ List<TreeMenu> listWithTree();
Step3:在 service 的實現類中重寫該方法。
/** * 查詢數據庫數據,並處理後返回 樹形數據 * @return 樹形數據 */ @Override public List<TreeMenu> listWithTree() { // 查找全部菜單數據 List<TreeMenu> lists = treeMenuDao.queryAll(null); // 把數據組合成樹形結構 List<TreeMenu> result = lists.stream() // 查找第一級菜單 .filter(meun -> meun.getMeunLevel() == 1) // 查找子菜單並放到第一級菜單中 .map(menu -> { menu.setTreeMenu(getChildren(menu, lists)); return menu; }) // 根據排序字段排序 .sorted((menu1, menu2) -> { return (menu1.getSort() == null ? 0 : menu1.getSort()) - (menu2.getSort() == null ? 0 : menu2.getSort()); }) // 把處理結果收集成一個 List 集合 .collect(Collectors.toList()); return result; } /** * 遞歸獲取子菜單 * @param root 當前菜單 * @param all 總的數據 * @return 子菜單 */ public List<TreeMenu> getChildren(TreeMenu root, List<TreeMenu> all) { List<TreeMenu> children = all.stream() // 根據 父菜單 ID 查找當前菜單 ID,以便於找到 當前菜單的子菜單 .filter(menu -> menu.getParentMenuId() == root.getMenuId()) // 遞歸查找子菜單的子菜單 .map((menu) -> { menu.setTreeMenu(getChildren(menu, all)); return menu; }) // 根據排序字段排序 .sorted((menu1, menu2) -> { return (menu1.getSort() == null ? 0 : menu1.getSort()) - (menu2.getSort() == null ? 0 : menu2.getSort()); }) // 把處理結果收集成一個 List 集合 .collect(Collectors.toList()); return children; }
Step4:在 controller 中調用該方法。
/** * 獲取數據庫數據,並處理成樹形結構 * @return 樹形結構數據 */ @GetMapping("selectAllWithTree") public Result selectAllWithTree() { return Result.ok().data("items", treeMenuService.listWithTree()); }
Step5:啓動服務,訪問。打開控制檯,能夠看到返回的樹形 json 數據。
前面使用後臺處理返回了 樹形結構 的 json 數據,如今須要將 json 數據按照必定的方式顯示出來便可。
使用 vue-cli 3.0 建立 vue 項目。
使用 element-ui 做爲頁面顯示。
使用 Axios 向後臺發送請求並返回數據。
(1)使用 vue-cli (圖形化界面)建立一個 vue 項目。
參考地址:
https://www.cnblogs.com/l-y-h/p/11241503.html
(2)添加 element-ui 依賴
【官網:】 https://element.eleme.cn/#/zh-CN 【文檔:】 https://element.eleme.cn/#/zh-CN/component/installation 【安裝方式一:(npm 安裝)】 npm install element-ui 【安裝方式二:(CDN 方式引入)】 <!-- 引入樣式 --> <link rel="stylesheet" href="https://unpkg.com/element-ui/lib/theme-chalk/index.css"> <!-- 引入組件庫 --> <script src="https://unpkg.com/element-ui/lib/index.js"></script>
此處使用 npm 方式安裝。
(3)在 vue 項目中 引入 element-ui。
在 main.js 中引入完整的 element-ui。
【main.js】 import Vue from 'vue' import App from './App.vue' // 引入 element-ui import ElementUI from 'element-ui' // 引入 element-ui 的 css 文件 import 'element-ui/lib/theme-chalk/index.css'; // 聲明使用 element-ui Vue.use(ElementUI); Vue.config.productionTip = false new Vue({ render: h => h(App), }).$mount('#app')
(4)引入樹形控件。
直接從官網選擇一個模板,加以修改便可。
以下,複製基本模板到 HelloWorld.vue 組件中,並加以修改。
【Tree 樹形控件:】 https://element.eleme.cn/#/zh-CN/component/tree 【HelloWorld.vue】 <template> <el-tree :data="data" :props="defaultProps" @node-click="handleNodeClick"></el-tree> </template> <script> export default { data() { return { data: [{ label: '一級 1', children: [{ label: '二級 1-1', children: [{ label: '三級 1-1-1' }] }] }, { label: '一級 2', children: [{ label: '二級 2-1', }] }], defaultProps: { children: 'children', label: 'label' } }; }, methods: { handleNodeClick(data) { console.log(data); } } }; </script>
運行項目、查看效果以下。
(1)vue 項目添加 Axios。
【參考地址:】 https://www.cnblogs.com/l-y-h/p/11656129.html#_label1 【npm 安裝:】 npm install axios
(2)使用 Axios 發送請求。
【引入 Axios】 import axios from 'axios'; 【HelloWorld.vue】 <template> <el-tree :data="data" :props="defaultProps" @node-click="handleNodeClick"></el-tree> </template> <script> import axios from 'axios'; export default { data() { return { data: [], defaultProps: { children: 'treeMenu', label: 'name' } }; }, methods: { handleNodeClick(data) { console.log(data); } }, created() { axios.get(`http://localhost:9000/treeMenu/selectAllWithTree`) .then(response => { console.log(response); this.data = response.data.data.items; }) .catch(error => { console.log(error); }); } }; </script>
(3)解決跨域問題。
後端解決:
可使用 @CrossOrigin 註解,在 方法上添加該註解。
@CrossOrigin @GetMapping("selectAllWithTree") public Result selectAllWithTree() { return Result.ok().data("items", treeMenuService.listWithTree()); }
前端解決:
【參考地址:】 https://www.cnblogs.com/l-y-h/p/11815452.html
此處,我使用後端解決,添加了 @CrossOrigin 註解。
(1)樹形控件經常使用屬性分析
【屬性:】 data Array 類型,用於保存樹的 數據。 props Object 類型,用於定義配置選項。 注: props 能夠用來指定 data 裏面的屬性標籤名(其用來指定屬性名,而非屬性值)。 經常使用屬性: label 用於指定節點對象中標籤屬性的屬性名。 children 用於指定節點對象中子對象屬性的屬性名。 isLeaf 用於指定節點對象中葉子節點的屬性名,僅在 lazy = true 時生效。 disabled 用於指定節點對象中是否禁用節點的屬性名。
【屬性:】 node-key String 類型,用於表示惟一的樹節點。 load Function 類型,僅 lazy = true 時生效,用於加載子樹據。 lazy boolean 類型,默認爲 false,是否懶加載子節點。 show-checkbox boolean 類型,默認爲 false,是否展開復選框(節點是否能被選擇)。 【舉例:】 <template> <el-tree :props="props" :load="loadNode" lazy show-checkbox></el-tree> </template> <script> export default { data() { return { props: { // 指定 data 中屬性名 label: 'name', isLeaf: 'leaf' }, }; }, methods: { loadNode(node, resolve) { // 初始加載節點數據 if (node.level === 0) { return resolve([{ name: 'region' },{ name: "region2" }]); } // 點擊第一級節點後,觸發延時操做,返回子節點 if (node.level === 1) { setTimeout(() => { const data = [{ name: 'leaf' }, { name: 'zone', disabled: true, leaf: true }]; resolve(data); }, 500); } // 點擊第二級節點後,沒有節點返回,返回 null。 if (node.level > 1) { return resolve([]); } } } }; </script>
固然,還有其餘屬性,好比能夠自定義樹節點數據,能夠實現拖拽功能等。
此處不過多敘述,詳情能夠參考官方文檔。
此處僅演示了 獲取所有數據 的代碼,能夠根據項目狀況,自行完善增長節點、刪除節點、批量刪除節點、拖拽節點等操做。