day07_分类管理EasyExcel品牌管理

文章目录

  • 1 分类管理
    • 1.1 菜单添加
    • 1.2 表结构介绍
    • 1.3 页面制作
    • 1.4 列表查询
      • 1.4.1 需求分析
      • 1.4.2 后端接口
        • Category
        • CategoryController
        • CategoryService
        • CategoryMapper
        • CategoryMapper.xml
      • 1.4.3 前端对接
        • category.js
        • category.vue
  • 2 EasyExcel
    • 2.1 数据导入导出意义
    • 2.2 EasyExcel简介
    • 2.3 入门案例
      • 2.3.1 解析Excel数据
      • 2.3.2 存储数据到Excel
    • 2.4 导出功能
      • 2.4.1 需求说明
      • 2.4.2 后端接口
        • CategoryController
        • CategoryService
        • CategoryMapper
        • CategoryMapper.xml
      • 2.4.3 前端对接
        • category.js
        • category.vue
    • 2.5 导入功能
      • 2.5.1 需求说明
      • 2.5.2 后端接口
        • CategoryController
        • CategoryService
        • CategoryMapper
        • CategoryMapper.xml
      • 2.5.3 前端对接
  • 3 品牌管理
    • 3.1 菜单添加
    • 3.2 表结构介绍
    • 3.3 页面制作
    • 3.4 列表查询
      • 3.4.1 后端接口
        • Brand
        • BrandController
        • BrandService
        • BrandMapper
        • BrandMapper.xml
      • 3.4.2 前端对接
        • brand.js
        • brand.vue
    • 3.5 品牌添加
      • 3.5.1 需求说明
      • 3.5.2 页面制作
      • 3.5.3 后端接口
        • BrandController
        • BrandService
        • BrandMapper
        • BrandMapper.xml
      • 3.5.4 前端对接
        • brand.js
        • brand.vue
    • 3.6 修改品牌
      • 3.6.1 需求说明
      • 3.6.2 数据回显
      • 3.6.3 提交修改
        • 后端接口
          • BrandController
          • BrandService
          • BrandMapper
          • BrandMapper.xml
        • 前端对接
          • brand.js
          • brand.vue
    • 3.7 删除品牌
      • 3.7.1 需求说明
      • 3.7.2 后端接口
        • BrandController
        • BrandService
        • BrandMapper
        • BrandMapper.xml
      • 3.7.3 前端对接
        • brand.js
        • brand.vue

1 分类管理

分类管理就是对商品的分类数据进行维护。常见的分类数据:电脑办公、手机、家居家装、汽车用品…

1.1 菜单添加

首先在系统中添加分类管理的菜单,具体步骤如下所示:

1、在后台管理系统中通过系统管理的菜单管理添加分类管理的相关菜单,如下所示:

在这里插入图片描述

2、给系统管理员角色分配分类管理菜单访问权限:

在这里插入图片描述

3、在前端项目中创建对应的页面,以及配置对应的异步路由

在src/views/product的文件夹,在该文件夹中加入分类管理页面文件,如下所示

在这里插入图片描述

在src/router/modules文件夹下创建product.js路由文件,文件内容如下所示:

const Layout = () => import('@/layout/index.vue')
const category = () => import('@/views/product/category.vue')

export default [
  {
    path: '/product',
    component: Layout,
    name: 'product',
    meta: {
      title: '商品管理',
    },
    icon: 'Histogram',
    children: [
      {
        path: '/category',
        name: 'category',
        component: category,
        meta: {
          title: '分类管理',
        },
      },
    ],
  },
]

在src/router/index.js中添加异步路由,如下所示:

import product from './modules/product'
    
// 动态菜单
export const asyncRoutes = [...system,...base,...product]

1.2 表结构介绍

分类数据所对应的表结构如下所示:

CREATE TABLE `category` (
  `id` bigint NOT NULL AUTO_INCREMENT COMMENT '分类id',
  `name` varchar(50) DEFAULT NULL COMMENT '分类名称',
  `image_url` varchar(200) DEFAULT NULL,
  `parent_id` bigint DEFAULT NULL COMMENT '父分类id',
  `status` tinyint DEFAULT NULL COMMENT '是否显示[0-不显示,1显示]',
  `order_num` int DEFAULT NULL COMMENT '排序',
  `create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
  `is_deleted` tinyint NOT NULL DEFAULT '0' COMMENT '删除标记(0:不可用 1:可用)',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=704 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='商品分类';

注意:分类数据是具有层级结构的,因此在进行数据展示的时候可以考虑使用树形表格进行展示。在咱们的尚品甄选项目中关于分类的数据只支持三级。

1.3 页面制作

对比如下页面结构,使用Element Plus制作出对应的页面,数据可以暂时使用假数据。

在这里插入图片描述

该页面可以将其分为2部分:

1、导入导出按钮

2、分类列表展示【树形表格】

代码实现如下所示:

<template>
    <div class="tools-div">
      <el-button type="success" size="small" >导出</el-button>
      <el-button type="primary" size="small" >导入</el-button>
    </div>

    <!---懒加载的树形表格-->
    <el-table
        :data="list"
        style="width: 100%"
        row-key="id"
        border
        lazy
        :load="fetchData"
        :tree-props="{ children: 'children', hasChildren: 'hasChildren' }"
    >
        <el-table-column prop="name" label="分类名称" />
        <el-table-column prop="imageUrl" label="图标" #default="scope">
            <img :src="scope.row.imageUrl" width="50" />
        </el-table-column>
        <el-table-column prop="orderNum" label="排序" />
        <el-table-column prop="status" label="状态" #default="scope">
        {{ scope.row.status == 1 ? '正常' : '停用' }}
        </el-table-column>
        <el-table-column prop="createTime" label="创建时间" />
    </el-table>
  
  </template>

<script setup>
import { ref } from 'vue';

// 定义list属性模型
const list = ref([
    {"id":1 , "name":"数码" , "orderNum":"1" , "status":1 , "createTime":"2023-05-22" , "hasChildren": true},
    {"id":2 , "name":"手机" , "orderNum":"1", "status":1, "createTime":"2023-05-22"},
])

// 加载数据的方法
const fetchData = (row, treeNode, resolve) => {
    
    // 向后端发送请求获取数据
    const data = [
        {"id":3 , "name":"智能设备" , "orderNum":"1" , "status":1 , "createTime":"2023-05-22" },
        {"id":4 , "name":"电子教育" , "orderNum":"2" , "status":1 , "createTime":"2023-05-22" },
    ]

    // 返回数据
    resolve(data)

}

</script>

<style scoped>
.search-div {
  margin-bottom: 10px;
  padding: 10px;
  border: 1px solid #ebeef5;
  border-radius: 3px;
  background-color: #fff;
}
.tools-div {
  margin: 10px 0;
  padding: 10px;
  border: 1px solid #ebeef5;
  border-radius: 3px;
  background-color: #fff;
}
</style>

1.4 列表查询

1.4.1 需求分析

当页面初始化完毕以后,此时就需要从请求后端接口查询所有的一级分类数据,一级分类数据的parent_id为0。当用户点击某一个分类前的小箭头,那么此时就需要查询该分类下所对应的所有的子分类数据。对应的sql语句如下所示:

select * from category where parent_id = 0 ;

1.4.2 后端接口

Category

定义一个与数据库表相对应的实体类:

// com.atguigu.spzx.model.entity.product
@Data
public class Category extends BaseEntity {

	private String name;
	private String imageUrl;
	private Long parentId;
	private Integer status;
	private Integer orderNum;
	private Boolean hasChildren;
	private List<Category> children;

}
CategoryController

表现层代码实现:

// com.atguigu.spzx.manager.controller
@RestController
@RequestMapping(value="/admin/product/category")
public class CategoryController {

    @Autowired
    private CategoryService categoryService;

    @Operation(summary = "根据parentId获取下级节点")
    @GetMapping(value = "/findByParentId/{parentId}")
    public Result<List<Category>> findByParentId(@PathVariable Long parentId) {
        List<Category> list = categoryService.findByParentId(parentId);
        return Result.build(list , ResultCodeEnum.SUCCESS) ;
    }
}
CategoryService

业务层代码实现:

// com.atguigu.spzx.manager.service.impl
@Service
public class CategoryServiceImpl implements CategoryService {

    @Autowired
    private CategoryMapper categoryMapper ;

    @Override
    public List<Category> findByParentId(Long parentId) {

        // 根据分类id查询它下面的所有的子分类数据
        List<Category> categoryList = categoryMapper.selectByParentId(parentId);
        if(!CollectionUtils.isEmpty(categoryList)) {

            // 遍历分类的集合,获取每一个分类数据
            categoryList.forEach(item -> {

                // 查询该分类下子分类的数量
                int count = categoryMapper.countByParentId(item.getId());
                if(count > 0) {
                    item.setHasChildren(true);
                } else {
                    item.setHasChildren(false);
                }

            });
        }
        return categoryList;
    }

}
CategoryMapper

持久层代码实现:

@Mapper
public interface CategoryMapper {
    public abstract List<Category> selectByParentId(Long parentId);
    public abstract int countByParentId(Long id);
}
CategoryMapper.xml

映射文件中添加如下的sql语句:

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.atguigu.spzx.manager.mapper.CategoryMapper">

	<resultMap id="categoryMap" type="com.atguigu.spzx.model.entity.product.Category" autoMapping="true">
	</resultMap>
	
	<!-- 用于select查询公用抽取的列 -->
	<sql id="columns">
		id,name,image_url,parent_id,status,order_num,create_time,update_time,is_deleted
	</sql>

    <select id="selectByParentId" resultMap="categoryMap">
    	select <include refid="columns" />
	    from category
		where parent_id = #{parentId}
		and is_deleted = 0
		order by id desc
    </select>

	<select id="countByParentId" resultType="Integer">
		select count(id)
		from category
		where parent_id = #{parentId}
		and is_deleted = 0
	</select>

</mapper>

1.4.3 前端对接

category.js

在src/api文件夹下创建一个category.js文件,文件的内容如下所示:

import request from '@/utils/request'

const api_name = '/admin/product/category'

// 根据parentId获取下级节点
export const FindCategoryByParentId = parentId => {
  return request({
    url: `${api_name}/findByParentId/${parentId}`,
    method: 'get',
  })
}
category.vue

修改category.vue文件,内容如下所示:

<script setup>
import { ref , onMounted} from 'vue';
import { FindCategoryByParentId } from '@/api/category.js'

// 定义list属性模型
const list = ref([])

// 页面初始化完毕以后请求后端接口查询数据
onMounted(async () => {
  const {code , data , message} = await FindCategoryByParentId(0)
  list.value = data ; 
})

// 加载数据的方法
const fetchData = async (row, treeNode, resolve) => {
    
    // 向后端发送请求获取数据
    const {code , data , message} = await FindCategoryByParentId(row.id)

    // 返回数据
    resolve(data)

}

</script>

2 EasyExcel

2.1 数据导入导出意义

后台管理系统是管理、处理企业业务数据的重要工具,在这样的系统中,数据的导入和导出功能是非常重要的,其主要意义包括以下几个方面:

1、提高数据操作效率:手动逐条添加或修改数据不仅费时费力,而且容易出错,此时就可以将大量数据从Excel等表格软件中导入到系统中时,通过数据导入功能,可以直接将表格中的数据批量导入到系统中,提高了数据操作的效率。

2、实现数据备份与迁移:通过数据导出功能,管理员可以将系统中的数据导出为 Excel 或其他格式的文件,以实现数据备份,避免数据丢失。同时,也可以将导出的数据文件用于数据迁移或其他用途。

3、方便企业内部协作:不同部门可能会使用不同的系统或工具进行数据处理,在这种情况下,通过数据导入和导出功能,可以方便地转换和共享数据,促进企业内部协作。

2.2 EasyExcel简介

官网地址:https://easyexcel.opensource.alibaba.com/

在这里插入图片描述

EasyExcel 的主要特点如下:

1、高性能:EasyExcel 采用了异步导入导出的方式,并且底层使用 NIO 技术实现,使得其在导入导出大数据量时的性能非常高效。

2、易于使用:EasyExcel 提供了简单易用的 API,用户可以通过少量的代码即可实现复杂的 Excel 导入导出操作。

3、增强的功能“EasyExcel 支持多种格式的 Excel 文件导入导出,同时还提供了诸如合并单元格、数据校验、自定义样式等增强的功能。

4、可扩展性好:EasyExcel 具有良好的扩展性,用户可以通过自定义 Converter 对自定义类型进行转换,或者通过继承 EasyExcelListener 来自定义监听器实现更加灵活的需求。

2.3 入门案例

2.3.1 解析Excel数据

需求:对资料中的excel数据进行解析,将其存储到对应的List集合中,并遍历List集合

步骤:

1、在spzx-model的pom.xml文件中添加如下依赖:

<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>easyexcel</artifactId>
</dependency>
<dependency>
    <groupId>ch.qos.logback</groupId>
    <artifactId>logback-classic</artifactId>
</dependency>

2、定义一个实体类来封装每一行的数据,如下所示:

// com.atguigu.spzx.model.vo.product
@Data
public class CategoryExcelVo {

	@ExcelProperty(value = "id" ,index = 0)
	private Long id;

	@ExcelProperty(value = "名称" ,index = 1)
	private String name;

	@ExcelProperty(value = "图片url" ,index = 2)
	private String imageUrl ;

	@ExcelProperty(value = "上级id" ,index = 3)
	private Long parentId;

	@ExcelProperty(value = "状态" ,index = 4)
	private Integer status;

	@ExcelProperty(value = "排序" ,index = 5)
	private Integer orderNum;
}

3、定义一个监听器,监听解析到的数据,如下所示:

public class ExcelListener<T> extends AnalysisEventListener<T> {
    
    //可以通过实例获取该值
    private List<T> datas = new ArrayList<>();

    @Override
    public void invoke(T o, AnalysisContext analysisContext) {  // 每解析一行数据就会调用一次该方法
        datas.add(o);//数据存储到list,供批量处理,或后续自己业务逻辑处理。
    }

    public List<T> getDatas() {
        return datas;
    }

    @Override
    public void doAfterAllAnalysed(AnalysisContext analysisContext) {
        // excel解析完毕以后需要执行的代码
    }
    
}

4、编写测试方法

public class EasyExcelTest {

    @Test
    public void easyExcelReadTest() {
        String fileName = "E://分类数据.xlsx" ;
        ExcelListener<CategoryExcelVo> excelListener = new ExcelListener<>();  // 创建一个监听器对象
        EasyExcel.read(fileName, CategoryExcelVo.class, excelListener).sheet().doRead();    // 解析excel表格
        List<CategoryExcelVo> excelVoList = excelListener.getDatas();       // 获取解析到的数据
        excelVoList.forEach(s -> System.out.println(s) );   // 进行遍历操作
    }

}

2.3.2 存储数据到Excel

需求:将如下的集合数据存储到Excel中文件中

List<CategoryExcelVo> list = new ArrayList<>() ;
list.add(new CategoryExcelVo(1L , 0L , "数码办公" , 1 , 1)) ;
list.add(new CategoryExcelVo(2L , 1L , "手机通讯" , 1 , 1)) ;
list.add(new CategoryExcelVo(3L , 2L , "手机" , 1 , 0)) ;

代码实现:

@Test
public void saveDataToExcel() {
    List<CategoryExcelVo> list = new ArrayList<>() ;
    list.add(new CategoryExcelVo(1L , 0L , "数码办公" , 1 , 1)) ;
    list.add(new CategoryExcelVo(2L , 1L , "手机通讯" , 1 , 1)) ;
    list.add(new CategoryExcelVo(3L , 2L , "手机" , 1 , 0)) ;
    EasyExcel.write("D://分类数据.xlsx" , CategoryExcelVo.class).sheet("分类数据").doWrite(list);
}

2.4 导出功能

2.4.1 需求说明

当用户点击导出按钮的时候,此时将数据库中的所有的分类的数据导出到一个excel文件中,如下所示:

在这里插入图片描述

2.4.2 后端接口

CategoryController

表现层代码实现:

// com.atguigu.spzx.manager.controller#CategoryController
@GetMapping(value = "/exportData")
public void exportData(HttpServletResponse response) {
    categoryService.exportData(response);
}
CategoryService

业务层代码实现:

// com.atguigu.spzx.manager.service.impl#CategoryServiceImpl
@Override
public void exportData(HttpServletResponse response) {

    try {

        // 设置响应结果类型
        response.setContentType("application/vnd.ms-excel");
        response.setCharacterEncoding("utf-8");

        // 这里URLEncoder.encode可以防止中文乱码 当然和easyexcel没有关系
        String fileName = URLEncoder.encode("分类数据", "UTF-8");
        response.setHeader("Content-disposition", "attachment;filename=" + fileName + ".xlsx");
        response.setHeader("Access-Control-Expose-Headers", "Content-Disposition");

        // 查询数据库中的数据
        List<Category> categoryList = categoryMapper.selectAll();
        List<CategoryExcelVo> categoryExcelVoList = new ArrayList<>(categoryList.size());

        // 将从数据库中查询到的Category对象转换成CategoryExcelVo对象
        for(Category category : categoryList) {
            CategoryExcelVo categoryExcelVo = new CategoryExcelVo();
            BeanUtils.copyProperties(category, categoryExcelVo, CategoryExcelVo.class);
            categoryExcelVoList.add(categoryExcelVo);
        }

        // 写出数据到浏览器端
        EasyExcel.write(response.getOutputStream(), CategoryExcelVo.class).sheet("分类数据").doWrite(categoryExcelVoList);

    } catch (IOException e) {
        e.printStackTrace();
    }
}
CategoryMapper

持久层代码实现:

@Mapper
public interface CategoryMapper {
    public abstract List<Category> selectAll();
}
CategoryMapper.xml

在映射文件中添加如下sql语句:

<select id="selectAll" resultMap="categoryMap">
    select <include refid="columns" />
    from category
    where is_deleted = 0
    order by id
</select>

2.4.3 前端对接

category.js

在src/api文件夹下创建一个category.js文件,文件的内容如下所示:

// 导出方法
export const ExportCategoryData = () => {
  return request({
    url: `${api_name}/exportData`,
    method: 'get',
    responseType: 'blob'  // 这里指定响应类型为blob类型,二进制数据类型,用于表示大量的二进制数据
  })
}
category.vue

修改category.vue文件,内容如下所示:

<div class="tools-div">
    <el-button type="success" size="small" @click="exportData">导出</el-button>
</div>

<script setup>
import { ExportCategoryData } from '@/api/category.js'

const exportData = () => {
  // 调用 ExportCategoryData() 方法获取导出数据
  ExportCategoryData().then(res => {
      // 创建 Blob 对象,用于包含二进制数据
      const blob = new Blob([res]);             
      // 创建 a 标签元素,并将 Blob 对象转换成 URL
      const link = document.createElement('a'); 
      link.href = window.URL.createObjectURL(blob);
      // 设置下载文件的名称
      link.download = '分类数据.xlsx';
      // 模拟点击下载链接
      link.click();
  })  
}
</script>

2.5 导入功能

2.5.1 需求说明

当用户点击导入按钮的时候,此时会弹出一个对话框,让用户选择要导入的excel文件,选择完毕以后将文件上传到服务端,服务端通过easyExcel解析文件的内容,然后将解析的结果存储到category表中。如下所示:

在这里插入图片描述

2.5.2 后端接口

CategoryController

表现层代码实现:

// com.atguigu.spzx.manager.controller#CategoryController
@PostMapping("importData")
public Result importData(MultipartFile file) {
    categoryService.importData(file);
    return Result.ok();
}
CategoryService

业务层代码实现:

// com.atguigu.spzx.manager.service.impl#CategoryServiceImpl
@Override
public void importData(MultipartFile file) {

    // 使用EasyExcel解析数据
    List<CategoryExcelVo> categoryExcelVoList = ExcelHelper.importExcel(file, CategoryExcelVo.class);
    List<Category> categoryList = new ArrayList<>();

    // 如果解析到的数据不为空,那么此时将解析到的对象转换成Category对象
    if(!CollectionUtils.isEmpty(categoryExcelVoList)) {
        for(CategoryExcelVo categoryExcelVo : categoryExcelVoList) {
            Category category = new Category();
            BeanUtils.copyProperties(categoryExcelVo, category, Category.class);
            categoryList.add(category);
        }

        // 进行数据的批量保存
        categoryMapper.batchInsert(categoryList);
    }
}
CategoryMapper

持久层代码实现:

@Mapper
public interface CategoryMapper {
    public abstract void batchInsert(List<Category> categoryList);
}
CategoryMapper.xml

映射文件中添加如下sql语句:

<insert id="batchInsert" useGeneratedKeys="true" keyProperty="id">
    insert into category (
        id,
        name,
        image_url,
        parent_id,
        status,
        order_num,
    	create_time ,
    	update_time ,
    	is_deleted
    ) values
    <foreach collection="categoryList" item="item" separator="," >
        (
        #{item.id},
        #{item.name},
        #{item.imageUrl},
        #{item.parentId},
        #{item.status},
        #{item.orderNum},
        now(),
        now(),
        0
        )
    </foreach>
</insert>

2.5.3 前端对接

修改category.vue文件,内容如下所示:

<div class="tools-div">
    <el-button type="primary" size="small" @click="importData">导入</el-button>
</div>

<el-dialog v-model="dialogImportVisible" title="导入" width="30%">
    <el-form label-width="120px">
        <el-form-item label="分类文件">
            <el-upload
                       class="upload-demo"
                       action="http://localhost:8501/admin/product/category/importData"
                       :on-success="onUploadSuccess"
                       :headers="headers"
                       >
                <el-button type="primary">上传</el-button>
            </el-upload>
        </el-form-item>
    </el-form>
</el-dialog>

<script setup>
import { useApp } from '@/pinia/modules/app'
    
// 文件上传相关变量以及方法定义
const dialogImportVisible = ref(false)
const headers = {
  token: useApp().authorization.token // 从pinia中获取token,在进行文件上传的时候将token设置到请求头中
}
const importData = () => {
  dialogImportVisible.value = true
}

// 上传文件成功以后要执行方法
const onUploadSuccess = async (response, file) => {
  ElMessage.success('操作成功')
  dialogImportVisible.value = false
  const { data } = await FindCategoryByParentId(0)
  list.value = data ; 
}
</script>

3 品牌管理

品牌管理就是对商品的所涉及到的品牌数据进行维护。常见的品牌数据:小米、华为、海尔…

3.1 菜单添加

首先在系统中添加品牌管理的菜单,具体步骤如下所示:

1、在后台管理系统中通过系统管理的菜单管理添加品牌管理的菜单,如下所示:

在这里插入图片描述

2、给系统管理员角色分配品牌管理菜单访问权限:

在这里插入图片描述

3、在前端项目中创建对应的页面,以及配置对应的异步路由

在src/views/product的文件夹,在该文件夹中加入品牌管理页面文件,如下所示:

在这里插入图片描述

在src/router/modules文件夹下创建product.js路由文件,文件内容如下所示:

const Layout = () => import('@/layout/index.vue')
const category = () => import('@/views/product/category.vue')
const brand = () => import('@/views/product/brand.vue')

export default [
  {
    path: '/product',
    component: Layout,
    name: 'product',
    meta: {
      title: '商品管理',
    },
    icon: 'Histogram',
    children: [
      {
        path: '/category',
        name: 'category',
        component: category,
        meta: {
          title: '分类管理',
        },
      },
      {
        path: '/brand',
        name: 'brand',
        component: brand,
        meta: {
          title: '品牌管理',
        },
      },
    ],
  },
]

3.2 表结构介绍

品牌数据所对应的表结构如下所示:

CREATE TABLE `brand` (
  `id` bigint NOT NULL AUTO_INCREMENT COMMENT 'ID',
  `name` varchar(100) CHARACTER SET utf8mb3 COLLATE utf8_general_ci DEFAULT NULL COMMENT '品牌名称',
  `logo` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8_general_ci DEFAULT NULL COMMENT '品牌图标',
  `create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
  `is_deleted` tinyint NOT NULL DEFAULT '0' COMMENT '删除标记(0:不可用 1:可用)',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=11 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci ROW_FORMAT=DYNAMIC COMMENT='分类品牌'

3.3 页面制作

对比如下页面结构,使用Element Plus制作出对应的页面,数据可以暂时使用假数据。

在这里插入图片描述

该页面可以将其分为3部分:

1、添加按钮

2、数据表格

3、分页组件

代码实现如下所示:

<template>
    <div class="tools-div">
        <el-button type="success" size="small">添 加</el-button>
    </div>

    <el-table :data="list" style="width: 100%">
        <el-table-column prop="name" label="品牌名称" />
        <el-table-column prop="logo" label="品牌图标" #default="scope">
            <img :src="scope.row.logo" width="50" />
        </el-table-column>
        <el-table-column prop="createTime" label="创建时间" />
        <el-table-column label="操作" align="center" width="200" >
            <el-button type="primary" size="small">
                修改
            </el-button>
            <el-button type="danger" size="small">
                删除
            </el-button>
        </el-table-column>
    </el-table>

    <el-pagination
        :page-sizes="[10, 20, 50, 100]"
        layout="total, sizes, prev, pager, next"
        :total="total"
    />

</template>

<script setup>
import { ref } from 'vue'

// 定义表格数据模型
const list = ref([
    {"id":1 , "name":"华为" , "logo":"http://139.198.127.41:9000/sph/20230506/华为.png"} , 
    {"id":2 , "name":"小米" , "logo":"http://139.198.127.41:9000/sph/20230506/小米.png"} , 
])

// 分页条数据模型
const total = ref(0)

</script>

<style scoped>
.tools-div {
  margin: 10px 0;
  padding: 10px;
  border: 1px solid #ebeef5;
  border-radius: 3px;
  background-color: #fff;
}
</style>

3.4 列表查询

需求说明:当品牌管理页面加载完毕以后就向后端发送分页查询请求,后端进行分页查询,返回分页结果数据。

3.4.1 后端接口

Brand

创建一个与数据库表相对应的实体类,如下所示:

@Data
public class Brand extends BaseEntity {

	private String name;
	private String logo;

}
BrandController

表现层代码实现:

// com.atguigu.spzx.manager.controller
@RestController
@RequestMapping(value="/admin/product/brand")
public class BrandController {

    @Autowired
    private BrandService brandService ;

    @GetMapping("/{page}/{limit}")
    public Result<PageInfo<Brand>> findByPage(@PathVariable Integer page, @PathVariable Integer limit) {
        PageInfo<Brand> pageInfo = brandService.findByPage(page, limit);
        return Result.build(pageInfo , ResultCodeEnum.SUCCESS) ;
    }

}
BrandService

业务层代码实现:

// com.atguigu.spzx.manager.service.impl;
@Service
public class BrandServiceImpl implements BrandService {

    @Autowired
    private BrandMapper brandMapper ;

    @Override
    public PageInfo<Brand> findByPage(Integer page, Integer limit) {
        PageHelper.startPage(page, limit);
        List<Brand> brandList = brandMapper.findByPage() ;
        return new PageInfo(brandList);
    }
}
BrandMapper

持久层代码实现:

// com.atguigu.spzx.manager.mapper
@Mapper
public interface BrandMapper {
    
    public abstract List<Brand> findByPage();
    
}
BrandMapper.xml

在BrandMapper.xml映射文件中添加如下的sql语句:

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.atguigu.spzx.manager.mapper.BrandMapper">

    <resultMap id="brandMap" type="com.atguigu.spzx.model.entity.product.Brand" autoMapping="true">
    </resultMap>

    <!-- 用于select查询公用抽取的列 -->
    <sql id="columns">
        id,name,logo,create_time,update_time,is_deleted
    </sql>

    <select id="findByPage" resultMap="brandMap">
        select <include refid="columns" />
        from brand
        where is_deleted = 0
        order by id desc
    </select>

</mapper>

3.4.2 前端对接

brand.js

在src/api目录下添加brand.js文件,内容如下所示:

import request from '@/utils/request'

const api_name = '/admin/product/brand'

// 分页列表
export const GetBrandPageList = (page, limit) => {
  return request({
    url: `${api_name}/${page}/${limit}`,
    method: 'get'
  })
}
brand.vue

修改brand.vue文件,内容如下所示:

<el-pagination
               v-model:current-page="pageParams.page"
               v-model:page-size="pageParams.limit"
               :page-sizes="[10, 20, 50, 100]"
               layout="total, sizes, prev, pager, next"
               :total="total"
               @size-change="handleSizeChange"
               @current-change="handleCurrentChange"
               />

<script setup>
import { ref , onMounted } from 'vue'
import { GetBrandPageList } from '@/api/brand.js'

// 定义表格数据模型
const list = ref([])

// 分页条数据模型
const total = ref(0)

//分页条数据模型
const pageParamsForm = {
  page: 1, // 页码
  limit: 10, // 每页记录数
}
const pageParams = ref(pageParamsForm)

// 钩子函数
onMounted(()=> {
    fetchData()
})

//页面变化
const handleSizeChange = size => {
  pageParams.value.limit = size
  fetchData()
}
const handleCurrentChange = number => {
  pageParams.value.page = number
  fetchData()
}

// 分页查询
const fetchData = async () => {
   const {code , message , data} = await GetBrandPageList(pageParams.value.page , pageParams.value.limit) 
   list.value = data.list
   total.value = data.total
}

</script>

3.5 品牌添加

3.5.1 需求说明

用户点击添加按钮,此时需要展示一个添加数据的表单对话框,用户填写表单数据,点击提交按钮,请求后端接口完成数据的保存操作。效果如下所示:

在这里插入图片描述

3.5.2 页面制作

页面代码如下所示:

<div class="tools-div">
    <el-button type="success" size="small" @click="addShow">添 加</el-button>
</div>

<el-dialog v-model="dialogVisible" title="添加或修改" width="30%">
    <el-form label-width="120px">
        <el-form-item label="品牌名称">
            <el-input />
        </el-form-item>
        <el-form-item label="品牌图标">
            <el-upload
                       class="avatar-uploader"
                       action="http://localhost:8503/admin/system/fileUpload"
                       :show-file-list="false"
                       >
                <img v-if="brand.logo" :src="brand.logo" class="avatar" />
                <el-icon v-else class="avatar-uploader-icon"><Plus /></el-icon>
            </el-upload>
        </el-form-item>
        <el-form-item>
            <el-button type="primary">提交</el-button>
            <el-button @click="dialogVisible = false">取消</el-button>
        </el-form-item>
    </el-form>
</el-dialog>

<script setup>

// 定义提交表单数据模型
const defaultForm = {
    logo: ""
}
const brand = ref(defaultForm)
const dialogVisible = ref(false) 

// 显示添加品牌表单
const addShow = () => {
    dialogVisible.value = true 
}

</script>

<style>
.avatar-uploader .el-upload {
  border: 1px dashed var(--el-border-color);
  border-radius: 6px;
  cursor: pointer;
  position: relative;
  overflow: hidden;
  transition: var(--el-transition-duration-fast);
}

.avatar-uploader .el-upload:hover {
  border-color: var(--el-color-primary);
}

.el-icon.avatar-uploader-icon {
  font-size: 28px;
  color: #8c939d;
  width: 178px;
  height: 178px;
  text-align: center;
}
</style>

3.5.3 后端接口

BrandController

表现层代码实现:

// com.atguigu.spzx.manager.controller
@PostMapping("save")
public Result save(@RequestBody Brand brand) {
    brandService.save(brand);
    return Result.build(null , ResultCodeEnum.SUCCESS) ;
}
BrandService

业务层代码实现:

// com.atguigu.spzx.manager.service.impl;
public void save(Brand brand) {
    brandMapper.save(brand) ;
}
BrandMapper

持久层代码实现:

// com.atguigu.spzx.manager.mapper
@Mapper
public interface BrandMapper {
    public abstract void save(Brand brand);
}
BrandMapper.xml

在BrandMapper.xml映射文件中添加如下的sql语句:

<insert id="save">
    insert into brand (
        id,
        name,
        logo,
        create_time ,
        update_time ,
        is_deleted
    ) values (
        #{id},
        #{name},
        #{logo},
        now(),
        now(),
        0
    )
</insert>

3.5.4 前端对接

brand.js

在src/api目录下添加brand.js文件,内容如下所示:

// 保存品牌
export const SaveBrand = brand => {
    return request({
        url: `${api_name}/save`,
        method: 'post',
        data: brand,
    })
}
brand.vue

修改brand.vue文件,内容如下所示:

<el-dialog v-model="dialogVisible" title="添加或修改" width="30%">
    <el-form label-width="120px">
        <el-form-item label="品牌名称">
            <el-input v-model="brand.name"/>
        </el-form-item>
        <el-form-item label="品牌图标">
            <el-upload
                       class="avatar-uploader"
                       action="http://localhost:8503/admin/system/fileUpload"
                       :show-file-list="false"
                       :on-success="handleAvatarSuccess"
                       >
                <img v-if="brand.logo" :src="brand.logo" class="avatar" />
                <el-icon v-else class="avatar-uploader-icon"><Plus /></el-icon>
            </el-upload>
        </el-form-item>
        <el-form-item>
            <el-button type="primary" @click="saveOrUpdate">提交</el-button>
            <el-button @click="dialogVisible = false">取消</el-button>
        </el-form-item>
    </el-form>
</el-dialog>

<script setup>
import { SaveBrand } from '@/api/brand.js'
import { ElMessage, ElMessageBox } from 'element-plus'
    
// 定义提交表单数据模型
const defaultForm = {
    id: '',
    name: '',
    logo: ""
}
const brand = ref(defaultForm)
const dialogVisible = ref(false) 

// 显示添加品牌表单
const addShow = () => {
    brand.value = {}
    dialogVisible.value = true 
}

//上传
const handleAvatarSuccess = (response) => {
  brand.value.logo = response.data
}

// 保存数据
const saveOrUpdate = () => {
  if (!brand.value.id) {
    saveData()
  } 
}

// 新增
const saveData = async () => {
  await SaveBrand(brand.value)
  dialogVisible.value = false
  ElMessage.success('操作成功')
  fetchData()
}
</script>

3.6 修改品牌

3.6.1 需求说明

当用户点击修改按钮的时候,那么此时就弹出对话框,在该对话框中需要将当前行所对应的品牌数据在该表单页面进行展示。当用户在该表单中点击提交按钮的时候那么此时就需要将表单进行提交,在后端需要提交过来的表单数据修改数据库中的即可。

效果如下所示:

在这里插入图片描述

3.6.2 数据回显

分析:

1、使用添加数据的表单即可

2、要将当前操作行的数据展示在表单中,那么此时需要用到插槽

代码如下所示:

<el-table-column label="操作" align="center" width="200" #default="scope">
    <el-button type="primary" size="small" @click="editShow(scope.row)">
        修改
    </el-button>
</el-table-column>

<script setup>
    
//进入修改
const editShow = row => {
  brand.value = row
  dialogVisible.value = true
}

</script>

3.6.3 提交修改

后端接口
BrandController

表现层代码实现:

// com.atguigu.spzx.manager.controller
@PutMapping("updateById")
public Result updateById(@RequestBody Brand brand) {
    brandService.updateById(brand);
    return Result.build(null , ResultCodeEnum.SUCCESS) ;
}
BrandService

业务层代码实现:

// com.atguigu.spzx.manager.service.impl;
@Override
public void updateById(Brand brand) {
    brandMapper.updateById(brand) ;
}
BrandMapper

持久层代码实现:

// com.atguigu.spzx.manager.mapper
@Mapper
public interface BrandMapper {
    public abstract void updateById(Brand brand);
}
BrandMapper.xml

在BrandMapper.xml映射文件中添加如下的sql语句:

<update id="updateById" >
    update brand set
    <if test="name != null and name != ''">
        name = #{name},
    </if>
    <if test="logo != null and logo != ''">
        logo = #{logo},
    </if>
    update_time =  now()
    where
    id = #{id}
</update>
前端对接
brand.js

在src/api目录下添加brand.js文件,内容如下所示:

// 修改信息
export const UpdateBrandById = brand => {
    return request({
        url: `${api_name}/updateById`,
        method: 'put',
        data: brand,
    })
}
brand.vue

修改brand.vue文件,内容如下所示:

<script setup>
import { UpdateBrandById } from '@/api/brand.js'
    
// 保存数据
const saveOrUpdate = () => {
  if (!brand.value.id) {
    saveData()
  } else {
    updateData() 
  }
}

// 修改
const updateData = async () => {
    await UpdateBrandById(brand.value)
    dialogVisible.value = false
    ElMessage.success('操作成功')
    fetchData()
}

</script>

3.7 删除品牌

3.7.1 需求说明

当点击删除按钮的时候此时需要弹出一个提示框,询问是否需要删除数据?如果用户点击是,那么此时向后端发送请求传递id参数,后端接收id参数进行逻辑删除。

效果如下所示:

在这里插入图片描述

3.7.2 后端接口

BrandController

表现层代码实现:

// com.atguigu.spzx.manager.controller
@DeleteMapping("/deleteById/{id}")
public Result deleteById(@PathVariable Long id) {
    brandService.deleteById(id);
    return Result.build(null , ResultCodeEnum.SUCCESS) ;
}
BrandService

业务层代码实现:

// com.atguigu.spzx.manager.service.impl;
@Override
public void deleteById(Long id) {
    brandMapper.deleteById(id) ;
}
BrandMapper

持久层代码实现:

// com.atguigu.spzx.manager.mapper
@Mapper
public interface BrandMapper {
    public abstract void deleteById(Long id);
}
BrandMapper.xml

在BrandMapper.xml映射文件中添加如下的sql语句:

<update id="deleteById">
    update brand set
        update_time = now() ,
        is_deleted = 1
    where
    id = #{id}
</update>

3.7.3 前端对接

brand.js

在src/api目录下添加brand.js文件,内容如下所示:

// 根据id删除品牌
export const DeleteBrandById = id => {
    return request({
      url: `${api_name}/deleteById/${id}`,
      method: 'delete',
    })
}
brand.vue

修改brand.vue文件,内容如下所示:

<el-table-column label="操作" align="center" width="200" #default="scope">
    <el-button type="danger" size="small" @click="remove(scope.row.id)">
        删除
    </el-button>
</el-table-column>

<script setup>
import { DeleteBrandById } from '@/api/brand.js'
    
//删除
const remove = async id => {
  ElMessageBox.confirm('此操作将永久删除该记录, 是否继续?', 'Warning', {
    confirmButtonText: '确定',
    cancelButtonText: '取消',
    type: 'warning',
  })
    .then(async () => {
      await DeleteBrandById(id)
      ElMessage.success('删除成功')
      fetchData()
    })
    .catch(() => {
      ElMessage.info('取消删除')
    })
}

</script>

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/421031.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

1950-2022年各省逐年平均降水量数据

1950-2022年各省逐年平均降水量数据 1、时间&#xff1a;1950-2022年 2、指标&#xff1a;省逐年平均降水量 3、范围&#xff1a;33省&#xff08;不含澳门&#xff09; 4、指标解释&#xff1a;逐年平均降水数据是指当年的日降水量的年平均值&#xff0c;不是累计值&#…

【leetcode】环形链表✚环形链表II

大家好&#xff0c;我是苏貝&#xff0c;本篇博客带大家刷题&#xff0c;如果你觉得我写的还不错的话&#xff0c;可以给我一个赞&#x1f44d;吗&#xff0c;感谢❤️ 目录 1.环形链表解题拓展&#xff1a; 2.环形链表II 1.环形链表 点击查看题目 解题 思路: bool hasCycle…

【问题记录】pip install遇到“No space left on device“

一、pip安装包的过程中出现以下问题 二、问题溯源 【1】刚开始以为是空间不足&#xff0c;后来 df -h 看了一下&#xff0c;pip安装目录下空间绝对足够&#xff01;明显不是空间不足的问题。 【2】后来查了一下&#xff0c;发现是home目录下的空间满了&#xff0c;pip安装时&…

一台工控机的能量

使用Docker搭建EPICS的IOC记录 Zstack EPICS Archiver在小课题组的使用经验 以前电子枪调试&#xff0c;用一台工控机跑起束测后台&#xff0c;这次新光源用的电子枪加工回来又是测试&#xff0c;又是用一台工控机做起重复的事&#xff0c;不过生命在于折腾&#xff0c;重复的…

计算机网络-网络互连和互联网(五)

1.路由器技术NAT&#xff1a; 网络地址翻译&#xff0c;解决IP短缺&#xff0c;路由器内部和外部地址进行转换。静态地址转换&#xff1a;静态NAT&#xff08;一对一&#xff09; 静态NAT&#xff0c;内外一对一转换&#xff0c;用于web服务器&#xff0c;ftp服务器等固定IP的…

C++ 入门(七)— 基本数据类型

文章目录 Void整数有符号整数无符号整数固定宽度的整数和size_t 浮点数布尔值字符类型转换 Void Void 是最容易解释的数据类型。void 没有类型 不返回值的函数 最常见的是&#xff0c;void 用于指示函数不返回值&#xff1a; void writeValue(int x) {std::cout <<…

【MySQL】数据库中常用的函数

目录 聚合函数COUNT()函数的多种用法COUNT(*)COUNT(主键)COUNT(1)COUNT(常量)COUNT(非主键)COUNT(distinct(字段)) COUNT()函数小结 字符函数length(str)函数&#xff1a;获取参数值的字节个数concat(str1,str2,...)函数&#xff1a;字符串拼接upper(str)、lower(str)函数:大小…

部署Docker私有镜像仓库Harbor

Harbor介绍 Harbor 是为企业用户设计的开源镜像仓库项目&#xff0c;包括了权限管理(RBAC)、LDAP、审计、安全漏洞扫描、镜像验真、管理界面、自我注册、HA等企业必需的功能&#xff0c;同时针对中国用户的特点&#xff0c;设计镜像复制和中文支持等功能。 官网&#xff1a;h…

kotlin开发环境搭建,算法题+JVM+自定义View

什么是中年危机 根据权威数据显示&#xff0c;国内IT程序员鼎盛时期是在25-27岁左右&#xff0c;30岁对于程序员而言完全是一个38线&#xff0c;接着就是转业转岗的事情&#xff0c;这一点在业界也算是一个共识了。 大学毕业步入IT行业普遍年龄也是在22岁左右&#xff0c;然而…

线程安全的队列

学习一下别人写的&#xff0c;线程安全的队列代码。https://github.com/markparticle/WebServer/blob/master/code/log/blockqueue.hhttps://github.com/markparticle/WebServer/blob/master/code/log/blockqueue.h /** Author : mark* Date : 2020-06-16* copy…

20240301作业

1.使用fwrite、fread将一张随意的bmp图片&#xff0c;修改成德国的国旗 #include <stdio.h> #include <string.h> #include <unistd.h> #include <stdlib.h> int main(int argc, const char *argv[]) {FILE* fp fopen("./gaoda.bmp","…

Attention 中的 Q, K, V

Attention 中的 Q, K, V flyfish Attention Is All You Need. Q query 查询 K key 键 V value 值 简单理解 一篇文章&#xff0c;文章的标题就是key&#xff0c;文章的内容就是V 使用搜索引擎时&#xff0c;输入到 搜索栏中的文本 就是 query 输入内容 query 与 文章标…

你不可不知的数据安全词汇都在这!

关注公众号&#xff0c;回复关键词 “数据安全”&#xff0c;即可获取报告完整版 随着数字化时代的快速发展&#xff0c;数据安全已成为全球企业和组织面临的一项重大挑战。在这一背景下&#xff0c;我们深感需要有一个统一的、全面的数据安全术语基础&#xff0c;以便行业从业…

P2040 打开所有的灯

题目传送门&#xff1a;P2040 打开所有的灯 用深度优先搜索实现的一个填色题。 题目步骤&#xff1a; 1..dfs 首先dfs要判断是否符合题意&#xff0c;如果符合题意就更新最短路&#xff1b; 如果不符合题意就枚举 如果是关的就把周围四个包括 给标记上和原来相反的&#xf…

Redis--持久化机制详解

什么是redis持久化&#xff1f; Redis持久化是将内存的数据持久化到磁盘上&#xff0c;防止Redis宕机或者断点的时候内存中的数据丢失&#xff0c;把内存中的数据写入到磁盘的过程叫持久化。 Redis持久化的方式&#xff1f; RDB&#xff08;Redis DataBase&#xff09;&…

【YOLO v5 v7 v8 小目标改进】中心化特征金字塔(CFP) = 特征金字塔 + 显式视觉中心(EVC)+ 全局集中调节(GCR)

中心化特征金字塔&#xff08;CFP&#xff09; 特征金字塔 显式视觉中心&#xff08;EVC&#xff09; 全局集中调节&#xff08;GCR&#xff09; 提出背景中心化特征金字塔&#xff08;CFP&#xff09;CFP 架构图问题&#xff1a;不同尺度的对象检测问题&#xff1a;有限感受…

LeetCode //C - 118. Pascal‘s Triangle

118. Pascal’s Triangle Given an integer numRows, return the first numRows of Pascal’s triangle. In Pascal’s triangle, each number is the sum of the two numbers directly above it as shown: Example 1: Input: numRows 5 Output: [[1],[1,1],[1,2,1],[1,…

Spring Cloud 构建面向企业的大型分布式微服务快速开发框架+技术栈介绍

分布式架构图 Cloud架构清单 Commonservice&#xff08;通用服务&#xff09; 1&#xff09;清单列表 2&#xff09;代码结构 Component&#xff08;通用组件&#xff09; 1&#xff09;清单列表 2&#xff09;代码结构 快速开发管理平台——云架构【系统管理平台】 一…

python requests接口自动化测试 (数据库断言)

前言 熟练掌握接口自动化测试体系背后的这些技能和处理问题的思路&#xff0c;实现时间、人力、收益的平衡&#xff0c;对于一个经验尚浅的初、中级测试开发人员来说绝对是一个艰巨的挑战。 五步教会你写接口自动化用例 需要安装三方包:requests pytest pytest-htmlpip insta…

视觉三维重建colmap框架的现状与未来

近两年AI技术的火热尤其是nerf和gaussian splatting的出现&#xff0c;又将colmap推了一把&#xff0c;传统mvs的地位仿佛受到了挑战&#xff0c;虽然说nerf/gs的效果是无法胜任传统mvs的精度&#xff0c;但是作为"看看"的条件&#xff0c;是远远足够了。且传统重复纹…