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


    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

    1. export class Tool {
    2. /**
    3. * 空校验 null或""都返回true
    4. */
    5. public static isEmpty(obj: any) {
    6. if ((typeof obj === 'string')) {
    7. return !obj || obj.replace(/\s+/g, "") === ""
    8. } else {
    9. return (!obj || JSON.stringify(obj) === "{}" || obj.length === 0);
    10. }
    11. }
    12. /**
    13. * 非空校验
    14. */
    15. public static isNotEmpty(obj: any) {
    16. return !this.isEmpty(obj);
    17. }
    18. /**
    19. * 对象复制
    20. * @param obj
    21. */
    22. public static copy(obj: object) {
    23. if (Tool.isNotEmpty(obj)) {
    24. return JSON.parse(JSON.stringify(obj));
    25. }
    26. }
    27. /**
    28. * 使用递归将数组转为树形结构
    29. * 父ID属性为parent
    30. */
    31. public static array2Tree(array: any, parentId: number) {
    32. if (Tool.isEmpty(array)) {
    33. return [];
    34. }
    35. const result = [];
    36. for (let i = 0; i < array.length; i++) {
    37. const c = array[i];
    38. // console.log(Number(c.parent), Number(parentId));
    39. if (Number(c.parent) === Number(parentId)) {
    40. result.push(c);
    41. // 递归查看当前节点对应的子节点
    42. const children = Tool.array2Tree(array, c.id);
    43. if (Tool.isNotEmpty(children)) {
    44. c.children = children;
    45. }
    46. }
    47. }
    48. return result;
    49. }
    50. /**
    51. * 随机生成[len]长度的[radix]进制数
    52. * @param len
    53. * @param radix 默认62
    54. * @returns {string}
    55. */
    56. public static uuid(len: number, radix = 62) {
    57. const chars = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'.split('');
    58. const uuid = [];
    59. radix = radix || chars.length;
    60. for (let i = 0; i < len; i++) {
    61. uuid[i] = chars[0 | Math.random() * radix];
    62. }
    63. return uuid.join('');
    64. }
    65. }

    2.2 新增admin-categoty.vue

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


    分类添加功能:

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

    1. <a-modal
    2. title="分类表单"
    3. v-model:visible="modalVisible"
    4. :confirm-loading="modalLoading"
    5. @ok="handleModalOk"
    6. >
    7. <a-form :model="category" :label-col="{ span: 6 }" :wrapper-col="{ span: 18 }">
    8. <a-form-item label="名称">
    9. <a-input v-model:value="category.name" />
    10. a-form-item>
    11. <a-form-item label="父分类">
    12. <a-select
    13. v-model:value="category.parent"
    14. ref="select"
    15. >
    16. <a-select-option :value="0">
    17. a-select-option>
    18. <a-select-option v-for="c in level1" :key="c.id" :value="c.id" :disabled="category.id === c.id">
    19. {{c.name}}
    20. a-select-option>
    21. a-select>
    22. a-form-item>
    23. <a-form-item label="顺序">
    24. <a-input v-model:value="category.sort" />
    25. a-form-item>
    26. a-form>
    27. a-modal>

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

    31e968a8f0fb40cd8559b387472c1425.png


    level变量的封装过程:

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

    1. const handleQuery = () => {
    2. loading.value = true;
    3. // 如果不清空现有数据,则编辑保存重新加载数据后,再点编辑,则列表显示的还是编辑前的数据
    4. level1.value = [];
    5. axios.get("/category/all").then((response) => {
    6. loading.value = false;
    7. const data = response.data;
    8. if (data.success) {
    9. categorys.value = data.content;
    10. console.log("原始数组:", categorys.value);
    11. level1.value = [];
    12. level1.value = Tool.array2Tree(categorys.value, 0);
    13. console.log("树形结构:", level1);
    14. } else {
    15. message.error(data.message);
    16. }
    17. });
    18. };
    '
    运行

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

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

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

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

    1. /**
    2. * 使用递归将数组转为树形结构
    3. * 父ID属性为parent
    4. */
    5. public static array2Tree(array: any, parentId: number) {
    6. if (Tool.isEmpty(array)) {
    7. return [];
    8. }
    9. const result = [];
    10. for (let i = 0; i < array.length; i++) {
    11. const c = array[i];
    12. // console.log(Number(c.parent), Number(parentId));
    13. if (Number(c.parent) === Number(parentId)) {
    14. result.push(c);
    15. // 递归查看当前节点对应的子节点
    16. const children = Tool.array2Tree(array, c.id);
    17. if (Tool.isNotEmpty(children)) {
    18. c.children = children;
    19. }
    20. }
    21. }
    22. return result;
    23. }

     全部代码如下:

    1. <template>
    2. <a-layout>
    3. <a-layout-content
    4. :style="{ background: '#fff', padding: '24px', margin: 0, minHeight: '280px' }"
    5. >
    6. <p>
    7. <a-form layout="inline" :model="param">
    8. <a-form-item>
    9. <a-input v-model:value="param.name" placeholder="名称">
    10. a-input>
    11. a-form-item>
    12. <a-form-item>
    13. <a-button type="primary" @click="handleQuery()">
    14. 查询
    15. a-button>
    16. a-form-item>
    17. <a-form-item>
    18. <a-button type="primary" @click="add()">
    19. 新增
    20. a-button>
    21. a-form-item>
    22. a-form>
    23. p>
    24. <p>
    25. <a-alert
    26. class="tip"
    27. message="小提示:这里的分类会显示到首页的侧边菜单"
    28. type="info"
    29. closable
    30. />
    31. p>
    32. <a-table
    33. v-if="level1.length > 0"
    34. :columns="columns"
    35. :row-key="record => record.id"
    36. :data-source="level1"
    37. :loading="loading"
    38. :pagination="false"
    39. :defaultExpandAllRows="true"
    40. >
    41. <template #cover="{ text: cover }">
    42. <img v-if="cover" :src="cover" alt="avatar" />
    43. template>
    44. <template v-slot:action="{ text, record }">
    45. <a-space size="small">
    46. <a-button type="primary" @click="edit(record)">
    47. 编辑
    48. a-button>
    49. <a-popconfirm
    50. title="删除后不可恢复,确认删除?"
    51. ok-text="是"
    52. cancel-text="否"
    53. @confirm="handleDelete(record.id)"
    54. >
    55. <a-button type="danger">
    56. 删除
    57. a-button>
    58. a-popconfirm>
    59. a-space>
    60. template>
    61. a-table>
    62. a-layout-content>
    63. a-layout>
    64. <a-modal
    65. title="分类表单"
    66. v-model:visible="modalVisible"
    67. :confirm-loading="modalLoading"
    68. @ok="handleModalOk"
    69. >
    70. <a-form :model="category" :label-col="{ span: 6 }" :wrapper-col="{ span: 18 }">
    71. <a-form-item label="名称">
    72. <a-input v-model:value="category.name" />
    73. a-form-item>
    74. <a-form-item label="父分类">
    75. <a-select
    76. v-model:value="category.parent"
    77. ref="select"
    78. >
    79. <a-select-option :value="0">
    80. a-select-option>
    81. <a-select-option v-for="c in level1" :key="c.id" :value="c.id" :disabled="category.id === c.id">
    82. {{c.name}}
    83. a-select-option>
    84. a-select>
    85. a-form-item>
    86. <a-form-item label="顺序">
    87. <a-input v-model:value="category.sort" />
    88. a-form-item>
    89. a-form>
    90. a-modal>
    91. template>
    92. <script lang="ts">
    93. import { defineComponent, onMounted, ref } from 'vue';
    94. import axios from 'axios';
    95. import { message } from 'ant-design-vue';
    96. import {Tool} from "@/util/tool";
    97. export default defineComponent({
    98. name: 'AdminCategory',
    99. setup() {
    100. const param = ref();
    101. param.value = {};
    102. const categorys = ref();
    103. const loading = ref(false);
    104. const columns = [
    105. {
    106. title: '名称',
    107. dataIndex: 'name'
    108. },
    109. // {
    110. // title: '父分类',
    111. // key: 'parent',
    112. // dataIndex: 'parent'
    113. // },
    114. {
    115. title: '顺序',
    116. dataIndex: 'sort'
    117. },
    118. {
    119. title: 'Action',
    120. key: 'action',
    121. slots: { customRender: 'action' }
    122. }
    123. ];
    124. /**
    125. * 一级分类树,children属性就是二级分类
    126. * [{
    127. * id: "",
    128. * name: "",
    129. * children: [{
    130. * id: "",
    131. * name: "",
    132. * }]
    133. * }]
    134. */
    135. const level1 = ref(); // 一级分类树,children属性就是二级分类
    136. level1.value = [];
    137. /**
    138. * 数据查询
    139. **/
    140. const handleQuery = () => {
    141. loading.value = true;
    142. // 如果不清空现有数据,则编辑保存重新加载数据后,再点编辑,则列表显示的还是编辑前的数据
    143. level1.value = [];
    144. axios.get("/category/all").then((response) => {
    145. loading.value = false;
    146. const data = response.data;
    147. if (data.success) {
    148. categorys.value = data.content;
    149. console.log("原始数组:", categorys.value);
    150. level1.value = [];
    151. level1.value = Tool.array2Tree(categorys.value, 0);
    152. console.log("树形结构:", level1);
    153. } else {
    154. message.error(data.message);
    155. }
    156. });
    157. };
    158. // -------- 表单 ---------
    159. const category = ref({});
    160. const modalVisible = ref(false);
    161. const modalLoading = ref(false);
    162. const handleModalOk = () => {
    163. modalLoading.value = true;
    164. axios.post("/category/save", category.value).then((response) => {
    165. modalLoading.value = false;
    166. const data = response.data; // data = commonResp
    167. if (data.success) {
    168. modalVisible.value = false;
    169. // 重新加载列表
    170. handleQuery();
    171. } else {
    172. message.error(data.message);
    173. }
    174. });
    175. };
    176. /**
    177. * 编辑
    178. */
    179. const edit = (record: any) => {
    180. modalVisible.value = true;
    181. category.value = Tool.copy(record);
    182. };
    183. /**
    184. * 新增
    185. */
    186. const add = () => {
    187. modalVisible.value = true;
    188. category.value = {};
    189. };
    190. const handleDelete = (id: number) => {
    191. axios.delete("/category/delete/" + id).then((response) => {
    192. const data = response.data; // data = commonResp
    193. if (data.success) {
    194. // 重新加载列表
    195. handleQuery();
    196. } else {
    197. message.error(data.message);
    198. }
    199. });
    200. };
    201. onMounted(() => {
    202. handleQuery();
    203. });
    204. return {
    205. param,
    206. // categorys,
    207. level1,
    208. columns,
    209. loading,
    210. handleQuery,
    211. edit,
    212. add,
    213. category,
    214. modalVisible,
    215. modalLoading,
    216. handleModalOk,
    217. handleDelete
    218. }
    219. }
    220. });
    221. script>
    222. <style scoped>
    223. img {
    224. width: 50px;
    225. height: 50px;
    226. }
    227. style>

    2.3 添加新的路由规则

    1. {
    2. path: '/admin/category',
    3. name: 'AdminCateGory',
    4. component: AdminCategory
    5. },

    2.4 添加the-welcome.vue

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

    1. <script lang="ts">
    2. name: 'the-welcome'
    3. script>
    4. <style scoped>
    5. style>

    2.5 修改HomeView.vue

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

    1. <script lang="ts">
    2. import { defineComponent, onMounted, ref, reactive, toRef } from 'vue';
    3. import axios from 'axios';
    4. import { message } from 'ant-design-vue';
    5. import {Tool} from "@/util/tool";
    6. import TheWelcome from '@/components/the-welcome.vue';
    7. export default defineComponent({
    8. name: 'Home',
    9. components: {
    10. TheWelcome
    11. },
    12. setup() {
    13. const ebooks = ref();
    14. // const ebooks1 = reactive({books: []});
    15. const openKeys = ref();
    16. const level1 = ref();
    17. let categorys: any;
    18. /**
    19. * 查询所有分类
    20. **/
    21. const handleQueryCategory = () => {
    22. axios.get("/category/all").then((response) => {
    23. const data = response.data;
    24. if (data.success) {
    25. categorys = data.content;
    26. console.log("原始数组:", categorys);
    27. // 加载完分类后,将侧边栏全部展开
    28. openKeys.value = [];
    29. for (let i = 0; i < categorys.length; i++) {
    30. openKeys.value.push(categorys[i].id)
    31. }
    32. level1.value = [];
    33. level1.value = Tool.array2Tree(categorys, 0);
    34. console.log("树形结构:", level1.value);
    35. } else {
    36. message.error(data.message);
    37. }
    38. });
    39. };
    40. const isShowWelcome = ref(true);
    41. let categoryId2 = 0;
    42. const handleQueryEbook = () => {
    43. axios.get("/ebook/list", {
    44. params: {
    45. page: 1,
    46. size: 1000,
    47. categoryId2: categoryId2
    48. }
    49. }).then((response) => {
    50. const data = response.data;
    51. ebooks.value = data.content.list;
    52. // ebooks1.books = data.content;
    53. });
    54. };
    55. const handleClick = (value: any) => {
    56. // console.log("menu click", value)
    57. if (value.key === 'welcome') {
    58. isShowWelcome.value = true;
    59. } else {
    60. categoryId2 = value.key;
    61. isShowWelcome.value = false;
    62. handleQueryEbook();
    63. }
    64. // isShowWelcome.value = value.key === 'welcome';
    65. };
    66. onMounted(() => {
    67. handleQueryCategory();
    68. // handleQueryEbook();
    69. });
    70. return {
    71. ebooks,
    72. // ebooks2: toRef(ebooks1, "books"),
    73. // listData,
    74. pagination: {
    75. onChange: (page: any) => {
    76. console.log(page);
    77. },
    78. pageSize: 3,
    79. },
    80. handleClick,
    81. level1,
    82. isShowWelcome,
    83. openKeys
    84. }
    85. }
    86. });
    87. script>
    88. <style scoped>
    89. .ant-avatar {
    90. width: 50px;
    91. height: 50px;
    92. line-height: 50px;
    93. border-radius: 8%;
    94. margin: 5px 0;
    95. }
    96. style>

     三、❗注意

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

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

    1. onMounted(() => {
    2. handleQueryCategory();
    3. // handleQueryEbook();
    4. });

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

    6e891551aa90455fbdae743a89532010.png

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

    46af899fab6245d7b98830ef95325fdc.png

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

  • 相关阅读:
    全球电梯空气消毒机行业调研及趋势分析报告
    JMeter详解
    linux 的文件权限案列
    python在导入模块时,即import时究竟有哪些动作?
    性能测试学习——项目环境搭建和Jmete学习二
    事件分发机制原理
    一文带你用 Mac M1 跑 RocketMQ
    [EI复现】基于主从博弈的新型城镇配电系统产消者竞价策略(Matlab代码实现)
    java计算机毕业设计供求信息网MyBatis+系统+LW文档+源码+调试部署
    vue+element项目创建步骤
  • 原文地址:https://blog.csdn.net/qq_61024956/article/details/139468674