【wiki知识库】05.分类管理实现--前端Vue模块

avatar
作者
猴君
阅读量:3

427ef4152dbf4b6c92618a198935cb6c.png

  📝个人主页:哈__

期待您的关注 

1b7335aca73b41609b7f05d1d366f476.gif

目录

 

一、🔥今日目标

二、🌏前端部分的改造

2.1 新增一个tool.ts 

2.2 新增admin-categoty.vue

2.3 添加新的路由规则

2.4 添加the-welcome.vue

2.5 修改HomeView.vue

 三、❗注意


 

一、🔥今日目标

【wiki知识库】04.SpringBoot后端实现电子书的增删改查以及前端界面的展示-CSDN博客

上回带大家把电子书管理的模块做了出来,能够实现电子书的添加、修改和删除功能了,今天带着大家把分类的接口实现一下。下方我添加了一个分类管理的组件,展示我们当前的所有分类,你可以看到这个分类页面还是一个树形结构的。

a865ffb43cac43258309d481fca03d99.png

除了分类管理,我们的首页也变动了一下。首页的导航栏加载的是我们已经有的分类,同时还加上了一个欢迎页面。

179a90137f4148bcb406d84b99034736.png 

二、🌏前端部分的改造

2.1 新增一个tool.ts 

在util包下建立一个tool.ts文件,这个文件是我们的工具文件,之前的代码可能也用到过了,我忘记给大家了。

a70695362c524453896b150267042401.png

export class Tool {     /**      * 空校验 null或""都返回true      */     public static isEmpty(obj: any) {         if ((typeof obj === 'string')) {             return !obj || obj.replace(/\s+/g, "") === ""         } else {             return (!obj || JSON.stringify(obj) === "{}" || obj.length === 0);         }     }      /**      * 非空校验      */     public static isNotEmpty(obj: any) {         return !this.isEmpty(obj);     }      /**      * 对象复制      * @param obj      */     public static copy(obj: object) {         if (Tool.isNotEmpty(obj)) {             return JSON.parse(JSON.stringify(obj));         }     }      /**      * 使用递归将数组转为树形结构      * 父ID属性为parent      */     public static array2Tree(array: any, parentId: number) {         if (Tool.isEmpty(array)) {             return [];         }          const result = [];         for (let i = 0; i < array.length; i++) {             const c = array[i];             // console.log(Number(c.parent), Number(parentId));             if (Number(c.parent) === Number(parentId)) {                 result.push(c);                  // 递归查看当前节点对应的子节点                 const children = Tool.array2Tree(array, c.id);                 if (Tool.isNotEmpty(children)) {                     c.children = children;                 }             }         }         return result;     }      /**      * 随机生成[len]长度的[radix]进制数      * @param len      * @param radix 默认62      * @returns {string}      */     public static uuid(len: number, radix = 62) {         const chars = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'.split('');         const uuid = [];         radix = radix || chars.length;          for (let i = 0; i < len; i++) {             uuid[i] = chars[0 | Math.random() * radix];         }          return uuid.join('');     } } 

2.2 新增admin-categoty.vue

在admin包下,创建admin-category.vue,这个组件用来展示我们的分类信息。这一部分我带着大家稍微过一下。


分类添加功能:

在我们点击添加或者编辑功能的时候,会把下边的代码以一个窗口的模式弹出,在这个窗口中展示了当前分类的名称,当前分类的父分类是谁以及当前分类的分类序号。在我们为一个分类进行添加或者修改的时候,我们都要涉及到这个分类到底是第一分类还是第二分类的问题,我们使用了一个level1变量来保存我们的分类结构,这个结构下边在讲。

<a-modal     title="分类表单"     v-model:visible="modalVisible"     :confirm-loading="modalLoading"     @ok="handleModalOk"   >     <a-form :model="category" :label-col="{ span: 6 }" :wrapper-col="{ span: 18 }">       <a-form-item label="名称">         <a-input v-model:value="category.name" />       </a-form-item>       <a-form-item label="父分类">         <a-select           v-model:value="category.parent"           ref="select"         >           <a-select-option :value="0">             无           </a-select-option>           <a-select-option v-for="c in level1" :key="c.id" :value="c.id" :disabled="category.id === c.id">             {{c.name}}           </a-select-option>         </a-select>       </a-form-item>       <a-form-item label="顺序">         <a-input v-model:value="category.sort" />       </a-form-item>     </a-form>   </a-modal>

我们的分类结构可以用一张图来表示。 我给每一个分类都排上了一个编号,第一级分类的parent编号都为0,二级分类的parent编号则要相对应其父分类的编号。

31e968a8f0fb40cd8559b387472c1425.png


level变量的封装过程:

我们的level变量是和我们全部的分类变量有关的,我们先要获取所有的分类然后对分类进行重新组合。

const handleQuery = () => {         loading.value = true;         // 如果不清空现有数据,则编辑保存重新加载数据后,再点编辑,则列表显示的还是编辑前的数据         level1.value = [];         axios.get("/category/all").then((response) => {           loading.value = false;           const data = response.data;           if (data.success) {             categorys.value = data.content;             console.log("原始数组:", categorys.value);              level1.value = [];             level1.value = Tool.array2Tree(categorys.value, 0);             console.log("树形结构:", level1);           } else {             message.error(data.message);           }         });       };

这时候打开我们的tool.ts来看一看。你会看到一共有两个参数,第一个参数呢我们传的是一个数据数组,第二个我们传来的是0。

首先我们判断一下传过来的数组是不是空的,如果是空的直接返回,否则的话执行下方逻辑。

首先遍历我们所有的分类,检查每一个分类的父分类的编号是不是我们传过来的0,这里你应该会理解为什么这样做,因为我们要把一个数据数据重新格式化成树的形式,那我们一定要先找到这棵树的一级分类,也就是父节点编号为0的分类,找到了之后呢我们把这个分类加到result当中。然后呢我们再次调用array2Tree这个方法,同时传入两个参数,第一个参数还是之前的全部分类的数组,但是第二个参数就不是0了,是我们刚才加入到result中的分类的编号,我们这次调用这个方法的目的是为了找到一级分类的子分类:二级分类。

这样的一次递归调用就可以将数据进行树化。(不懂私信我)

 /**      * 使用递归将数组转为树形结构      * 父ID属性为parent      */     public static array2Tree(array: any, parentId: number) {         if (Tool.isEmpty(array)) {             return [];         }          const result = [];         for (let i = 0; i < array.length; i++) {             const c = array[i];             // console.log(Number(c.parent), Number(parentId));             if (Number(c.parent) === Number(parentId)) {                 result.push(c);                  // 递归查看当前节点对应的子节点                 const children = Tool.array2Tree(array, c.id);                 if (Tool.isNotEmpty(children)) {                     c.children = children;                 }             }         }         return result;     }

 全部代码如下:

<template>   <a-layout>     <a-layout-content       :style="{ background: '#fff', padding: '24px', margin: 0, minHeight: '280px' }"     >       <p>         <a-form layout="inline" :model="param">           <a-form-item>             <a-input v-model:value="param.name" placeholder="名称">             </a-input>           </a-form-item>           <a-form-item>             <a-button type="primary" @click="handleQuery()">               查询             </a-button>           </a-form-item>           <a-form-item>             <a-button type="primary" @click="add()">               新增             </a-button>           </a-form-item>         </a-form>       </p>       <p>         <a-alert                 class="tip"                 message="小提示:这里的分类会显示到首页的侧边菜单"                 type="info"                 closable         />       </p>       <a-table         v-if="level1.length > 0"         :columns="columns"         :row-key="record => record.id"         :data-source="level1"         :loading="loading"         :pagination="false"         :defaultExpandAllRows="true"       >         <template #cover="{ text: cover }">           <img v-if="cover" :src="cover" alt="avatar" />         </template>         <template v-slot:action="{ text, record }">           <a-space size="small">             <a-button type="primary" @click="edit(record)">               编辑             </a-button>             <a-popconfirm               title="删除后不可恢复,确认删除?"               ok-text="是"               cancel-text="否"               @confirm="handleDelete(record.id)"             >               <a-button type="danger">                 删除               </a-button>             </a-popconfirm>           </a-space>         </template>       </a-table>     </a-layout-content>   </a-layout>    <a-modal     title="分类表单"     v-model:visible="modalVisible"     :confirm-loading="modalLoading"     @ok="handleModalOk"   >     <a-form :model="category" :label-col="{ span: 6 }" :wrapper-col="{ span: 18 }">       <a-form-item label="名称">         <a-input v-model:value="category.name" />       </a-form-item>       <a-form-item label="父分类">         <a-select           v-model:value="category.parent"           ref="select"         >           <a-select-option :value="0">             无           </a-select-option>           <a-select-option v-for="c in level1" :key="c.id" :value="c.id" :disabled="category.id === c.id">             {{c.name}}           </a-select-option>         </a-select>       </a-form-item>       <a-form-item label="顺序">         <a-input v-model:value="category.sort" />       </a-form-item>     </a-form>   </a-modal> </template>  <script lang="ts">   import { defineComponent, onMounted, ref } from 'vue';   import axios from 'axios';   import { message } from 'ant-design-vue';   import {Tool} from "@/util/tool";    export default defineComponent({     name: 'AdminCategory',     setup() {       const param = ref();       param.value = {};       const categorys = ref();       const loading = ref(false);        const columns = [         {           title: '名称',           dataIndex: 'name'         },         // {         //   title: '父分类',         //   key: 'parent',         //   dataIndex: 'parent'         // },         {           title: '顺序',           dataIndex: 'sort'         },         {           title: 'Action',           key: 'action',           slots: { customRender: 'action' }         }       ];        /**        * 一级分类树,children属性就是二级分类        * [{        *   id: "",        *   name: "",        *   children: [{        *     id: "",        *     name: "",        *   }]        * }]        */       const level1 = ref(); // 一级分类树,children属性就是二级分类       level1.value = [];        /**        * 数据查询        **/       const handleQuery = () => {         loading.value = true;         // 如果不清空现有数据,则编辑保存重新加载数据后,再点编辑,则列表显示的还是编辑前的数据         level1.value = [];         axios.get("/category/all").then((response) => {           loading.value = false;           const data = response.data;           if (data.success) {             categorys.value = data.content;             console.log("原始数组:", categorys.value);              level1.value = [];             level1.value = Tool.array2Tree(categorys.value, 0);             console.log("树形结构:", level1);           } else {             message.error(data.message);           }         });       };        // -------- 表单 ---------       const category = ref({});       const modalVisible = ref(false);       const modalLoading = ref(false);       const handleModalOk = () => {         modalLoading.value = true;         axios.post("/category/save", category.value).then((response) => {           modalLoading.value = false;           const data = response.data; // data = commonResp           if (data.success) {             modalVisible.value = false;              // 重新加载列表             handleQuery();           } else {             message.error(data.message);           }         });       };        /**        * 编辑        */       const edit = (record: any) => {         modalVisible.value = true;         category.value = Tool.copy(record);       };        /**        * 新增        */       const add = () => {         modalVisible.value = true;         category.value = {};       };        const handleDelete = (id: number) => {         axios.delete("/category/delete/" + id).then((response) => {           const data = response.data; // data = commonResp           if (data.success) {             // 重新加载列表             handleQuery();           } else {             message.error(data.message);           }         });       };        onMounted(() => {         handleQuery();       });        return {         param,         // categorys,         level1,         columns,         loading,         handleQuery,          edit,         add,          category,         modalVisible,         modalLoading,         handleModalOk,          handleDelete       }     }   }); </script>  <style scoped>   img {     width: 50px;     height: 50px;   } </style> 

2.3 添加新的路由规则

  {     path: '/admin/category',     name: 'AdminCateGory',     component: AdminCategory   },

2.4 添加the-welcome.vue

在component文件夹下边 添加the-welcome.vue页面。

<template>   <span>欢迎</span>  </template>  <script lang="ts">     name: 'the-welcome' </script> <style scoped>  </style> 

2.5 修改HomeView.vue

这里做出了一些修改,一个是the-welcome组件的展示,还有一个就是页面侧边栏的展示,我们之前展示的是页面默认的,现在我们要展示数据库当中的分类结构。里边我们有一些代码还用不到,但是我没有注释掉。

<template>   <a-layout>     <a-layout-sider width="200" style="background: #fff">       <a-menu         mode="inline"         :style="{ height: '100%', borderRight: 0 }"         @click="handleClick"         :openKeys="openKeys"       >         <a-menu-item key="welcome">           <MailOutlined />           <span>欢迎</span>         </a-menu-item>         <a-sub-menu v-for="item in level1" :key="item.id" :disabled="true">           <template v-slot:title>             <span><user-outlined />{{item.name}}</span>           </template>           <a-menu-item v-for="child in item.children" :key="child.id">             <MailOutlined /><span>{{child.name}}</span>           </a-menu-item>         </a-sub-menu>         <a-menu-item key="tip" :disabled="true">           <span>以上菜单在分类管理配置</span>         </a-menu-item>       </a-menu>     </a-layout-sider>     <a-layout-content     :style="{ background: '#fff', padding: '24px', margin: 0, minHeight: '280px' }"   >       <div class="welcome" v-show="isShowWelcome">         <the-welcome></the-welcome>       </div>       <a-list v-show="!isShowWelcome" item-layout="vertical" size="large" :grid="{ gutter: 20, column: 3 }" :data-source="ebooks">         <template #renderItem="{ item }">           <a-list-item key="item.name">             <template #actions>               <span>                 <component v-bind:is="'FileOutlined'" style="margin-right: 8px" />                 {{ item.docCount }}               </span>               <span>                 <component v-bind:is="'UserOutlined'" style="margin-right: 8px" />                 {{ item.viewCount }}               </span>               <span>                 <component v-bind:is="'LikeOutlined'" style="margin-right: 8px" />                 {{ item.voteCount }}               </span>             </template>             <a-list-item-meta :description="item.description">               <template #title>                 <router-link :to="'/doc?ebookId=' + item.id">                   {{ item.name }}                 </router-link>               </template>               <template #avatar><a-avatar :src="item.cover"/></template>             </a-list-item-meta>           </a-list-item>         </template>       </a-list>   </a-layout-content>   </a-layout> </template>  <script lang="ts"> import { defineComponent, onMounted, ref, reactive, toRef } from 'vue'; import axios from 'axios'; import { message } from 'ant-design-vue'; import {Tool} from "@/util/tool"; import TheWelcome from '@/components/the-welcome.vue';   export default defineComponent({   name: 'Home',   components: {     TheWelcome   },   setup() {     const ebooks = ref();     // const ebooks1 = reactive({books: []});      const openKeys =  ref();      const level1 =  ref();     let categorys: any;     /**      * 查询所有分类      **/     const handleQueryCategory = () => {       axios.get("/category/all").then((response) => {         const data = response.data;         if (data.success) {           categorys = data.content;           console.log("原始数组:", categorys);            // 加载完分类后,将侧边栏全部展开           openKeys.value = [];           for (let i = 0; i < categorys.length; i++) {             openKeys.value.push(categorys[i].id)           }            level1.value = [];           level1.value = Tool.array2Tree(categorys, 0);           console.log("树形结构:", level1.value);         } else {           message.error(data.message);         }       });     };      const isShowWelcome = ref(true);     let categoryId2 = 0;      const handleQueryEbook = () => {       axios.get("/ebook/list", {         params: {           page: 1,           size: 1000,           categoryId2: categoryId2         }       }).then((response) => {         const data = response.data;         ebooks.value = data.content.list;         // ebooks1.books = data.content;       });     };      const handleClick = (value: any) => {       // console.log("menu click", value)       if (value.key === 'welcome') {         isShowWelcome.value = true;       } else {         categoryId2 = value.key;         isShowWelcome.value = false;         handleQueryEbook();       }       // isShowWelcome.value = value.key === 'welcome';     };      onMounted(() => {       handleQueryCategory();       // handleQueryEbook();     });      return {       ebooks,       // ebooks2: toRef(ebooks1, "books"),       // listData,       pagination: {         onChange: (page: any) => {           console.log(page);         },         pageSize: 3,       },        handleClick,       level1,        isShowWelcome,        openKeys     }   } }); </script>  <style scoped>   .ant-avatar {     width: 50px;     height: 50px;     line-height: 50px;     border-radius: 8%;     margin: 5px 0;   } </style> 

 三、❗注意

在之前写的admin-ebook.vue当中还有一些代码是注释掉的,因为之前没有写分类模块,现在我们需要使用分类的功能了,我们还需要解除一些注释。大家可以看看哪些地方有关category的被注释掉了,大家可以打开,后端接口下一篇文章就会带大家写出来。

这里需要修改一些onMounted()函数中的代码,修改成下边的部分。

 onMounted(() => {       handleQueryCategory();       // handleQueryEbook();     });

此外,这个网站的标题头部的信息可能我之前没加上去,就是这个。

6e891551aa90455fbdae743a89532010.png

 在the-header.vue中修改一下自己的代码。

46af899fab6245d7b98830ef95325fdc.png

在下边的style中加上样式。然后就可以展示出来了。

<style>   .logo {     width: 120px;     height: 31px;     /*background: rgba(255, 255, 255, 0.2);*/     /*margin: 16px 28px 16px 0;*/     float: left;     color: white;     font-size: 18px;   } </style>

 

广告一刻

为您即时展示最新活动产品广告消息,让您随时掌握产品活动新动态!