guli商城业务逻辑-基础篇笔记

这里写目录标题

  • 0.1 viscode设置用户代码片段
  • 1.实现多级菜单接口
    • 1.1 对接前端菜单
    • 1.2 对接网关接口解决跨域问题,如果不解决跨域,浏览器还是访问不了api
    • 1.3 把商品服务添加网关
    • 1.4 修改前端显示分类菜单
    • 1.5 给菜单添加删除修改功能
      • 1.5.1 删除功能的后端业务逻辑
      • 1.5.2 前端对接删除接口
      • 1.5.3 添加前端菜单新增
      • 1.5.4 添加前端菜单修改
        • 1.5.4.1 前端菜单修改优化成可拖拽的形式
        • 1.5.4.2 让他拖拽完成后保存到数据库
    • 1.6 添加批量删除功能
  • 1.7 实现品牌管理 使用逆向工程快速生成
      • 1.7.1 实现文件上传功能存-对接使用OSS
  • 1.8 创建第三方的api项目服务
    • 1.8.1 前后端联调oss对接
    • 1.8.2 JSR303添加校验注解b
  • 1.9 统一异常处理
  • 1.10 SPU&SKU
  • 1.11 属性分组 整和分类组件
  • ---- 未完待续 直接看中级了-----

0.1 viscode设置用户代码片段

VS Code 定制属于你自己的代码段
通过设置用户代码片段来自定义快捷键,快捷输出重复代码。
在这里插入图片描述
在这里插入图片描述
添加指定快捷键代码

{// Place your 全局 snippets here. Each snippet is defined under a snippet name and has a scope, prefix, body and // description. Add comma separated ids of the languages where the snippet is applicable in the scope field. If scope // is left empty or omitted, the snippet gets applied to all languages. The prefix is what is // used to trigger the snippet and the body will be expanded and inserted. Possible variables are: // $1, $2 for tab stops, $0 for the final cursor position, and ${1:label}, ${2:another} for placeholders. // Placeholders with the same ids are connected.// Example:// "Print to console": {// 	"scope": "javascript,typescript",// 	"prefix": "log",// 	"body": [// 		"console.log('$1');",// 		"$2"// 	],// 	"description": "Log output to console"// }"http-get 请求": { "prefix": "httpget", "body": [ "this.\\$http({", "url: this.\\$http.adornUrl(''),", "method: 'get',", "params: this.\\$http.adornParams({})", "}).then(({data}) => {", "})"],"description": "httpGET 请求"},"http-post 请求": { "prefix": "httppost", "body": [ "this.\\$http({", "url: this.\\$http.adornUrl(''),", "method: 'post',", "data: this.\\$http.adornData(data, false)", "}).then(({ data }) => { });"
],"description": "httpPOST 请求"
}}

1.实现多级菜单接口

实现父子结构菜单效果
在这里插入图片描述
数据库表结构
在这里插入图片描述
实体类

package com.atguigu.gulimall.product.entity;import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableLogic;
import com.baomidou.mybatisplus.annotation.TableName;import java.io.Serializable;
import java.util.List;import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonInclude;
import lombok.Data;/*** 商品三级分类* * @author leifengyang* @email leifengyang@gmail.com* @date 2019-10-01 21:08:48*/@Data
@TableName("pms_category")
public class CategoryEntity implements Serializable {private static final long serialVersionUID = 1L;/*** 分类id*/@TableIdprivate Long catId;/*** 分类名称*/private String name;/*** 父分类id*/private Long parentCid;/*** 层级*/private Integer catLevel;/*** 是否显示[0-不显示,1显示]*/@TableLogic(value = "1",delval = "0")private Integer showStatus;/*** 排序*/private Integer sort;/*** 图标地址*/private String icon;/*** 计量单位*/private String productUnit;/*** 商品数量*/private Integer productCount;@JsonInclude(JsonInclude.Include.NON_EMPTY)@TableField(exist=false)//存放全部子分类private List<CategoryEntity> children;}

业务逻辑


@Override
public List<CategoryEntity> listWithTree() {//1、查出所有分类List<CategoryEntity> entities = baseMapper.selectList(null);//2.组装成父子的树形结构//2.1 找到所有的一级分类 因为一级分类的父级id都是0 ,通过父级id来筛选 最终返回一个集合List<CategoryEntity> level1Menus  = entities.stream().filter(categoryEntity ->//过滤条件categoryEntity.getParentCid() == 0).map((menu)->{//把当前菜单的子菜单都找出来然后放到他的Chidren子菜单集合中menu.setChildren(getChildrens(menu,entities));return menu;}).sorted((menu1,menu2)->{//进行菜单排序return menu1.getSort()-menu2.getSort();}).collect(Collectors.toList());return entities;
}
    //递归查找所有菜单的子菜单private List<CategoryEntity> getChildrens(CategoryEntity root,List<CategoryEntity> all){List<CategoryEntity> collect = all.stream().filter(categoryEntity -> {//遍历全部菜单把符合当前父级菜单的都取出来return categoryEntity.getParentCid() == root.getCatId();}).map(categoryEntity -> {//如果还是找他到他的子菜单那么在修改嵌入进去categoryEntity.setChildren(getChildrens(categoryEntity, all));return categoryEntity;}).sorted((menu1, menu2) -> {return (menu1.getSort()==null?0:menu1.getSort()) - menu2.getSort();}).collect(Collectors.toList());return collect;}

1.1 对接前端菜单

vue菜单组件

<template><el-tree:data="data":props="defaultProps"@node-click="handleNodeClick"></el-tree>
</template>
<script>
export default {data() {return {data: [],defaultProps: {children: "children",label: "label",},};},methods: {handleNodeClick(data) {console.log(data);},getMenus() {// 使用 this.$http 发起一个 GET 请求到 /sys/role/list 接口this.$http({url: this.$http.adornUrl("/product/category/list/tree"), // 请求的 URLmethod: "get"}).then(({ data }) => {// 请求成功后的回调函数console.log("成功获取数据",data)});},},//生命周期created(){this.getMenus();}
};
</script>

因为接口的后端api地址跟后台地址不一致,需要通过注册api网关的方式转发来解决。
在这里插入图片描述
给后端配置注册到nacos中

  application:name: renren-fast

在这里插入图片描述
再从启动类中添加@EnableDiscoveryClient注解注册到nacos
在这里插入图片描述
然后在通过网关配置路由代理。这里先处理人人后台的后端,后面在对接上guli的接口。

spring:cloud:gateway:routes:- id: bd_route # 路由 ID,用于唯一标识该路由规则uri: https://www.baidu.com # 目标服务的 URIpredicates: # 路由条件,用于匹配请求 Query是设置断言规则- Query=url,baidu- id: qq_route # 路由 ID,用于唯一标识该路由规则uri: https://www.qq.com # 目标服务的 URIpredicates: # 路由条件,用于匹配请求 Query是设置断言规则- Query=url,qq# 当你使用 lb://renren-fast 这样的目标服务 URI 时,它会通过服务发现组件(如 Nacos)来查找名为 renren-fast 的服务实例。- id: admin_route # 路由 ID,用于唯一标识该路由规则uri: lb://renren-fast # 目标服务的 URIpredicates: # 路由条件,用于匹配请求 Query是设置断言规则- Path=/api/**filters:- RewritePath=/api/(?<segment>.*),/renren-fast/$\{segment} # 因为直接转发的路径是 /api/captcha.jpg 而项目实际路径是/api/renren-fast 所以要重新转发的接口
## 前端项目,/api 路径
这个配置的含义如下:/api/(?<segment>.*):这是一个正则表达式,用于匹配以 /api 开头的请求路径。(?<segment>.*) 是一个命名捕获组,用于捕获 /api 之后的部分。
/renren-fast/$\{segment}:这是重写后的请求路径。$\{segment} 是一个变量,表示之前捕获的 /api 之后的部分。这个路径将替换原始请求路径中匹配到的部分。
在这个例子中,RewritePath 过滤器将所有以 /api 开头的请求路径重写为 /renren-fast 开头的路径。例如,请求 /api/user 将被重写为 /renren-fast/user。

1.2 对接网关接口解决跨域问题,如果不解决跨域,浏览器还是访问不了api

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
解决方案1
在这里插入图片描述
在这里插入图片描述
在网关项目新建config
在这里插入图片描述

package com.atguigu.gulimall.gulimallgateway.config;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;@Configuration
public class GulimallCorsConfiguration {@Beanpublic CorsWebFilter corsWebFilter() {UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();CorsConfiguration corsConfiguration = new CorsConfiguration();//1、配置跨域corsConfiguration.addAllowedHeader("*");corsConfiguration.addAllowedMethod("*");corsConfiguration.addAllowedOrigin("*");corsConfiguration.setAllowCredentials(true);source.registerCorsConfiguration("/**",corsConfiguration);return new CorsWebFilter(source);}
}

1.3 把商品服务添加网关

要实现把服务注册上nacos不然网关是找不到这个服务的
在这里插入图片描述

spring:cloud:gateway:routes:- id: bd_route # 路由 ID,用于唯一标识该路由规则uri: https://www.baidu.com # 目标服务的 URIpredicates: # 路由条件,用于匹配请求 Query是设置断言规则- Query=url,baidu- id: qq_route # 路由 ID,用于唯一标识该路由规则uri: https://www.qq.com # 目标服务的 URIpredicates: # 路由条件,用于匹配请求 Query是设置断言规则- Query=url,qq# 当你使用 lb://renren-fast 这样的目标服务 URI 时,它会通过服务发现组件(如 Nacos)来查找名为 renren-fast 的服务实例。- id: product_route # 路由 ID,用于唯一标识该路由规则uri: lb://gulimall-product # 目标服务的 URIpredicates: # 路由条件,用于匹配请求 Query是设置断言规则- Path=/api/product/**filters:- RewritePath=/api/(?<segment>.*),/$\{segment} # 重写转发的地址,让他转发的地址取消/api/这个前缀# 当你使用 lb://renren-fast 这样的目标服务 URI 时,它会通过服务发现组件(如 Nacos)来查找名为 renren-fast 的服务实例。- id: admin_route # 路由 ID,用于唯一标识该路由规则uri: lb://renren-fast # 目标服务的 URIpredicates: # 路由条件,用于匹配请求 Query是设置断言规则- Path=/api/**filters:- RewritePath=/api/(?<segment>.*),/renren-fast/$\{segment} # 因为直接转发的路径是 /api/captcha.jpg 而项目实际路径是/api/renren-fast 所以要重新转发的接口## 前端项目,/api 路径

1.4 修改前端显示分类菜单

获取后端数据

  methods: {handleNodeClick(data) {console.log(data);},getMenus() {// 使用 this.$http 发起一个 GET 请求到 /sys/role/list 接口this.$http({url: this.$http.adornUrl("/product/category/list/tree"), // 请求的 URLmethod: "get",}).then(({ data }) => {// 请求成功后的回调函数console.log("成功获取数据", data);this.menus =data.data});},},

赋值到组件

  data() {return {menus: [],defaultProps: {// 字节的的属性名字children: "children",// 那个字段需要显示label: "name",},};},

前端代码

<template><el-tree:data="menus":props="defaultProps"@node-click="handleNodeClick"></el-tree>
</template>
<script>
export default {data() {return {menus: [],defaultProps: {// 字节的的属性名字children: "children",// 那个字段需要显示label: "name",},};},methods: {handleNodeClick(data) {console.log(data);},getMenus() {// 使用 this.$http 发起一个 GET 请求到 /sys/role/list 接口this.$http({url: this.$http.adornUrl("/product/category/list/tree"), // 请求的 URLmethod: "get",}).then(({ data }) => {// 请求成功后的回调函数console.log("成功获取数据", data);this.menus =data.data});},},//生命周期created() {this.getMenus();},
};
</script>

在这里插入图片描述
在这里插入图片描述

1.5 给菜单添加删除修改功能

如果菜单是一级菜单才可以添加,否则不能。
在这里插入图片描述
添加增加 删除小组件

<span class="custom-tree-node" slot-scope="{ node, data }"><span>{{ node.label }}</span><span><el-button v-if="node.level <= 2" type="text" size="mini" @click="() => append(data)">Append</el-button><!-- 如果没有字节的就是最底层了,可以删除 --><el-button  v-if="node.childNodes.length==0" type="text" size="mini" @click="() => remove(node, data)">Delete</el-button></span>

1.5.1 删除功能的后端业务逻辑

删除只能使用逻辑删除,也就是把数据库里的删除字段从0改成1,数据不可以真正的删除掉,所以就要用到mybatis-plus的逻辑删除来执行数据库操作。
在这里插入图片描述
配置文件添加逻辑删除配置

mybatis-plus:mapper-locations: classpath:/mapper/**/*.xmlglobal-config:db-config:id-type: autologic-delete-value: 1logic-not-delete-value: 0

状态1就是显示0就是删除
在这里插入图片描述
实体类添加字段

    @Overridepublic void removeMenuByIds(List<Long> asList) {//TODO  1、检查当前删除的菜单,是否被别的地方引用//逻辑删除baseMapper.deleteBatchIds(asList);}
	/*** 是否显示[0-不显示,1显示]*///自定义规则,设置1是不删除0是删除@TableLogic(value = "1",delval = "0")private Integer showStatus;/**

添加后删除就会变成了逻辑删除

在这里插入图片描述

1.5.2 前端对接删除接口

发送post删除请求接口

remove(node, data) {//获取要发送的数组var ids =[data.catId];this.$http({url: this.$http.adornUrl('/product/category/delete'),method: 'post',data: this.$http.adornData(ids, false)}).then(({ data }) => { console.log("删除成功");});},

但是前端直接删除没有提示,所以要添加一个弹窗。

    remove(node, data) {//获取要发送的数组var ids = [data.catId];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 }) => {console.log("删除成功");});});},

在这里插入图片描述

<template><el-tree:data="menus":props="defaultProps":expand-on-click-node="false"show-checkbox="true"node-key="catId":default-expanded-keys="expandedLKey"><span class="custom-tree-node" slot-scope="{ node, data }"><span>{{ node.label }}</span><span><el-buttonv-if="node.level <= 2"type="text"size="mini"@click="() => append(data)">Append</el-button><!-- 如果没有字节的就是最底层了,可以删除 --><el-buttonv-if="node.childNodes.length == 0"type="text"size="mini"@click="() => remove(node, data)">Delete</el-button></span></span></el-tree>
</template>
<script>
export default {data() {return {menus: [],// 默认展开的节点的idexpandedLKey:[],defaultProps: {// 字节的的属性名字children: "children",// 那个字段需要显示label: "name",},};},methods: {handleNodeClick(data) {console.log(data);},getMenus() {// 使用 this.$http 发起一个 GET 请求到 /sys/role/list 接口this.$http({url: this.$http.adornUrl("/product/category/list/tree"), // 请求的 URLmethod: "get",}).then(({ data }) => {// 请求成功后的回调函数console.log("成功获取数据", data);this.menus = data.data;});},append(data) {},remove(node, data) {//获取要发送的数组var ids = [data.catId];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({message: "菜单删除成功",type: "success",});//刷新出新的菜单this.getMenus();//设置需要默认展开的菜单this.expandedLKey = [node.parent.data.catId]//刷新出新的菜单this.getMenus();});})},},//生命周期created() {this.getMenus();},
};
</script>

1.5.3 添加前端菜单新增

新增提示框

    <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="addCateory">确 定</el-button></span></el-dialog>

绑定参数

  data() {return {category:{name:""},//默认不展示对话1框dialogVisible: false,menus: [],// 默认展开的节点的idexpandedLKey: [],defaultProps: {// 字节的的属性名字children: "children",// 那个字段需要显示label: "name",},};},

添加方法

    addCateory(){this.$http({url: this.$http.adornUrl('/product/category/save'),method: 'post',data: this.$http.adornData(this.category, false)}).then(({ data }) => { this.$message({message: "菜单新增成功",type: "success",});//关闭对话框this.dialogVisible=false;});},

1.5.4 添加前端菜单修改

添加修改功能

    //发送修改三级分类的请求endiCategory() {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({message: "菜单修改成功",type: "success",});//关闭对话框this.dialogVisible = false;//刷新出新的菜单this.getMenus();//设置需要默认展开的菜单this.expandedLKey = [this.category.parentCid];});},//回显到菜单栏endi(data) {console.log("要修改的数据", this.endi)this.title = "修改分类"this.dialogType = "endi"this.dialogVisible = true;//发送请求获取最新的数据this.$http({url: this.$http.adornUrl(`/product/category/info/${data.catId}`),method: 'get',}).then(({ data }) => {//请求成功console.log("要回显的数据", data);console.log("category", this.category)this.category.name = data.data.namethis.category.icon = data.data.iconthis.category.catId = data.data.catId// this.category.productUnit = data.data.productUnit// this.category.sort = data.data.sort// this.category.catLevel = data.data.catLevel// this.category.showStatus = data.data.showStatus})},

完整前端代码

<template><div><el-tree :data="menus" :props="defaultProps" :expand-on-click-node="false" show-checkbox="true" node-key="catId":default-expanded-keys="expandedLKey"><span class="custom-tree-node" slot-scope="{ node, data }"><span>{{ node.label }}</span><span><el-button v-if="node.level <= 2" type="text" size="mini" @click="() => append(data)">Append</el-button><!-- 如果没有字节的就是最底层了,可以删除 --><el-button v-if="node.childNodes.length == 0" type="text" size="mini" @click="() => remove(node, data)">Delete</el-button><el-button v-if="node.level <= 2" type="text" size="mini" @click="() => endi(data)">endi</el-button></span></span></el-tree><el-dialog :title="title" :visible.sync="dialogVisible" width="30%" :close-on-click-modal="false"><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 {productUnit: "",icon: "",title: "",//给对话框设置一个类型,让他知道是谁调用的他。dialogType: "",category: { name: "", icon: "", parentCid: 0, catLevel: 0, showStatus: 1, sort: 0 },//默认不展示对话1框dialogVisible: false,menus: [],// 默认展开的节点的idexpandedLKey: [],defaultProps: {// 字节的的属性名字children: "children",// 那个字段需要显示label: "name",},};},methods: {handleNodeClick(data) {console.log(data);},addCateory() {this.$http({url: this.$http.adornUrl('/product/category/save'),method: 'post',data: this.$http.adornData(this.category, false)}).then(({ data }) => {this.$message({message: "菜单新增成功",type: "success",});//关闭对话框this.dialogVisible = false;//刷新出新的菜单this.getMenus();//设置需要默认展开的菜单this.expandedLKey = [this.category.parentCid];//刷新出新的菜单this.getMenus();});},getMenus() {// 使用 this.$http 发起一个 GET 请求到 /sys/role/list 接口this.$http({url: this.$http.adornUrl("/product/category/list/tree"), // 请求的 URLmethod: "get",}).then(({ data }) => {// 请求成功后的回调函数console.log("成功获取数据", data);this.menus = data.data;});},submitData() {if (this.dialogType == "add") {this.append();}if (this.dialogType = "endi") {console.log("修改")this.endiCategory();}},//发送修改三级分类的请求endiCategory() {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({message: "菜单修改成功",type: "success",});//关闭对话框this.dialogVisible = false;//刷新出新的菜单this.getMenus();//设置需要默认展开的菜单this.expandedLKey = [this.category.parentCid];});},append(data) {this.title = "添加分类"console.log("append", data)this.dialogType = "add"this.dialogVisible = true;this.category.parentCid = data.catId;//转换字符串—+1this.category.catLevel = data.catLevel * 1 + 1;},endi(data) {console.log("要修改的数据", this.endi)this.title = "修改分类"this.dialogType = "endi"this.dialogVisible = true;//发送请求获取最新的数据this.$http({url: this.$http.adornUrl(`/product/category/info/${data.catId}`),method: 'get',}).then(({ data }) => {//请求成功console.log("要回显的数据", data);console.log("category", this.category)this.category.name = data.data.namethis.category.icon = data.data.iconthis.category.catId = data.data.catId// this.category.productUnit = data.data.productUnit// this.category.sort = data.data.sort// this.category.catLevel = data.data.catLevel// this.category.showStatus = data.data.showStatus})},remove(node, data) {//获取要发送的数组var ids = [data.catId];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({message: "菜单删除成功",type: "success",});//刷新出新的菜单this.getMenus();//设置需要默认展开的菜单this.expandedLKey = [node.parent.data.catId];//刷新出新的菜单this.getMenus();});});},},//生命周期created() {this.getMenus();},
};
</script>
1.5.4.1 前端菜单修改优化成可拖拽的形式

在这里插入图片描述
在添加一个判断是否能被拖拽到指定的位置
在这里插入图片描述
方法

    allowDrop(draggingNode,dropNode,type){//1.被拖动的当前节点以及所在的父节点层数不能大于3//1.1.被拖动的当前节点总层数this.contNodeLevel(draggingNode.data)//当前拖动的节点+父节点所在深度不大于3即可let deep =(this.maxLevel-draggingNode.data.catLevel)+1;if(type=="inner"){return (deep+dropNode.level)<=3;}else{return (deep+dropNode.parent.level<=3);}return ;},contNodeLevel(node){//找到所有子节点求出最大深度if(node.children!=null&&node.children.length>0){for(let i=0;i<node.children.length;i++){if(node.children[i].catLevel>this.maxLevel){this.maxLevel = node.children[i].catLevel;}this.contNodeLevel(node.children[i]);}}},

完整代码

<template><div><el-tree :data="menus" :props="defaultProps" :expand-on-click-node="false" show-checkbox="true" node-key="catId":default-expanded-keys="expandedLKey" draggable ="true" :allow-drop="allowDrop"><span class="custom-tree-node" slot-scope="{ node, data }"><span>{{ node.label }}</span><span><el-button v-if="node.level <= 2" type="text" size="mini" @click="() => append(data)">Append</el-button><!-- 如果没有字节的就是最底层了,可以删除 --><el-button v-if="node.childNodes.length == 0" type="text" size="mini" @click="() => remove(node, data)">Delete</el-button><el-button v-if="node.level <= 2" type="text" size="mini" @click="() => endi(data)">endi</el-button></span></span></el-tree><el-dialog :title="title" :visible.sync="dialogVisible" width="30%" :close-on-click-modal="false"><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 {maxLevel:0,productUnit: "",icon: "",title: "",//给对话框设置一个类型,让他知道是谁调用的他。dialogType: "",category: { name: "", icon: "", parentCid: 0, catLevel: 0, showStatus: 1, sort: 0 },//默认不展示对话1框dialogVisible: false,menus: [],// 默认展开的节点的idexpandedLKey: [],defaultProps: {// 字节的的属性名字children: "children",// 那个字段需要显示label: "name",},};},methods: {handleNodeClick(data) {console.log(data);},addCateory() {this.$http({url: this.$http.adornUrl('/product/category/save'),method: 'post',data: this.$http.adornData(this.category, false)}).then(({ data }) => {this.$message({message: "菜单新增成功",type: "success",});//关闭对话框this.dialogVisible = false;//刷新出新的菜单this.getMenus();//设置需要默认展开的菜单this.expandedLKey = [this.category.parentCid];//刷新出新的菜单this.getMenus();});},getMenus() {// 使用 this.$http 发起一个 GET 请求到 /sys/role/list 接口this.$http({url: this.$http.adornUrl("/product/category/list/tree"), // 请求的 URLmethod: "get",}).then(({ data }) => {// 请求成功后的回调函数console.log("成功获取数据", data);this.menus = data.data;});},allowDrop(draggingNode,dropNode,type){//1.被拖动的当前节点以及所在的父节点层数不能大于3//1.1.被拖动的当前节点总层数this.contNodeLevel(draggingNode.data)//当前拖动的节点+父节点所在深度不大于3即可let deep =(this.maxLevel-draggingNode.data.catLevel)+1;if(type=="inner"){return (deep+dropNode.level)<=3;}else{return (deep+dropNode.parent.level<=3);}return ;},contNodeLevel(node){//找到所有子节点求出最大深度if(node.children!=null&&node.children.length>0){for(let i=0;i<node.children.length;i++){if(node.children[i].catLevel>this.maxLevel){this.maxLevel = node.children[i].catLevel;}this.contNodeLevel(node.children[i]);}}},submitData() {if (this.dialogType == "add") {this.addCateory();}if (this.dialogType = "endi") {console.log("修改")this.endiCategory();}},//发送修改三级分类的请求endiCategory() {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({message: "菜单修改成功",type: "success",});//关闭对话框this.dialogVisible = false;//刷新出新的菜单this.getMenus();//设置需要默认展开的菜单this.expandedLKey = [this.category.parentCid];});},append(data) {//点击添加按钮后给他执行默认的新增参数this.title = "添加分类"console.log("append", data)this.dialogType = "add"this.dialogVisible = true;this.category.parentCid = data.catId;//转换字符串—+1this.category.catLevel = data.catLevel * 1 + 1;},endi(data) {console.log("要修改的数据", this.endi)this.title = "修改分类"this.dialogType = "endi"this.dialogVisible = true;//发送请求获取最新的数据this.$http({url: this.$http.adornUrl(`/product/category/info/${data.catId}`),method: 'get',}).then(({ data }) => {//请求成功console.log("要回显的数据", data);console.log("category", this.category)this.category.name = data.data.namethis.category.icon = data.data.iconthis.category.catId = data.data.catId// this.category.productUnit = data.data.productUnit// this.category.sort = data.data.sort// this.category.catLevel = data.data.catLevel// this.category.showStatus = data.data.showStatus})},remove(node, data) {//获取要发送的数组var ids = [data.catId];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({message: "菜单删除成功",type: "success",});//刷新出新的菜单this.getMenus();//设置需要默认展开的菜单this.expandedLKey = [node.parent.data.catId];//刷新出新的菜单this.getMenus();});});},},//生命周期created() {this.getMenus();},
};
</script>
1.5.4.2 让他拖拽完成后保存到数据库

在这里插入图片描述
具体框架:https://element.eleme.cn/#/zh-CN/component/tree

<template><div><el-switch v-model="draggable" active-text="开启拖拽" inactive-text="关闭拖拽"></el-switch><el-button v-if="draggable" @click="batchSave">批量保存</el-button><el-button type="danger" @click="batchDelete">批量删除</el-button><el-tree:data="menus":props="defaultProps":expand-on-click-node="false"show-checkboxnode-key="catId":default-expanded-keys="expandedKey":draggable="draggable":allow-drop="allowDrop"@node-drop="handleDrop"ref="menuTree"><span class="custom-tree-node" slot-scope="{ node, data }"><span>{{ node.label }}</span><span><el-buttonv-if="node.level <=2"type="text"size="mini"@click="() => append(data)">Append</el-button><el-button type="text" size="mini" @click="edit(data)">edit</el-button><el-buttonv-if="node.childNodes.length==0"type="text"size="mini"@click="() => remove(node, data)">Delete</el-button></span></span></el-tree><el-dialog:title="title":visible.sync="dialogVisible"width="30%":close-on-click-modal="false"><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>
//这里可以导入其他文件(比如:组件,工具js,第三方插件js,json文件,图片文件等等)
//例如:import 《组件名称》 from '《组件路径》';export default {//import引入的组件需要注入到对象中才能使用components: {},props: {},data() {return {pCid: [],draggable: false,updateNodes: [],maxLevel: 0,title: "",dialogType: "", //edit,addcategory: {name: "",parentCid: 0,catLevel: 0,showStatus: 1,sort: 0,productUnit: "",icon: "",catId: null},dialogVisible: false,menus: [],expandedKey: [],defaultProps: {children: "children",label: "name"}};},//计算属性 类似于data概念computed: {},//监控data中的数据变化watch: {},//方法集合methods: {getMenus() {this.$http({url: this.$http.adornUrl("/product/category/list/tree"),method: "get"}).then(({ data }) => {console.log("成功获取到菜单数据...", data.data);this.menus = data.data;});},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({message: "菜单批量删除成功",type: "success"});this.getMenus();});}).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({message: "菜单顺序等修改成功",type: "success"});//刷新出新的菜单this.getMenus();//设置需要默认展开的菜单this.expandedKey = this.pCid;this.updateNodes = [];this.maxLevel = 0;// this.pCid = 0;});},handleDrop(draggingNode, dropNode, dropType, ev) {console.log("handleDrop: ", draggingNode, dropNode, dropType);//1、当前节点最新的父节点idlet 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.updateChildNodeLevel(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);},updateChildNodeLevel(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.updateChildNodeLevel(node.childNodes[i]);}}},allowDrop(draggingNode, dropNode, type) {//1、被拖动的当前节点以及所在的父节点总层数不能大于3//1)、被拖动的当前节点总层数console.log("allowDrop:", draggingNode, dropNode, type);//this.countNodeLevel(draggingNode);//当前正在拖动的节点+父节点所在的深度不大于3即可let deep = Math.abs(this.maxLevel - draggingNode.level) + 1;console.log("深度:", deep);//   this.maxLevelif (type == "inner") {// 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[i]);}}},edit(data) {console.log("要修改的数据", data);this.dialogType = "edit";this.title = "修改分类";this.dialogVisible = true;//发送请求获取当前节点最新的数据this.$http({url: this.$http.adornUrl(`/product/category/info/${data.catId}`),method: "get"}).then(({ data }) => {//请求成功console.log("要回显的数据", data);this.category.name = data.data.name;this.category.catId = data.data.catId;this.category.icon = data.data.icon;this.category.productUnit = data.data.productUnit;this.category.parentCid = data.data.parentCid;this.category.catLevel = data.data.catLevel;this.category.sort = data.data.sort;this.category.showStatus = data.data.showStatus;/***         parentCid: 0,catLevel: 0,showStatus: 1,sort: 0,*/});},append(data) {console.log("append", data);this.dialogType = "add";this.title = "添加分类";this.dialogVisible = true;this.category.parentCid = data.catId;this.category.catLevel = data.catLevel * 1 + 1;this.category.catId = null;this.category.name = "";this.category.icon = "";this.category.productUnit = "";this.category.sort = 0;this.category.showStatus = 1;},submitData() {if (this.dialogType == "add") {this.addCategory();}if (this.dialogType == "edit") {this.editCategory();}},//修改三级分类数据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({message: "菜单修改成功",type: "success"});//关闭对话框this.dialogVisible = false;//刷新出新的菜单this.getMenus();//设置需要默认展开的菜单this.expandedKey = [this.category.parentCid];});},//添加三级分类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({message: "菜单保存成功",type: "success"});//关闭对话框this.dialogVisible = false;//刷新出新的菜单this.getMenus();//设置需要默认展开的菜单this.expandedKey = [this.category.parentCid];});},remove(node, data) {var ids = [data.catId];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({message: "菜单删除成功",type: "success"});//刷新出新的菜单this.getMenus();//设置需要默认展开的菜单this.expandedKey = [node.parent.data.catId];});}).catch(() => {});console.log("remove", node, data);}},//生命周期 - 创建完成(可以访问当前this实例)created() {this.getMenus();},//生命周期 - 挂载完成(可以访问DOM元素)mounted() {},beforeCreate() {}, //生命周期 - 创建之前beforeMount() {}, //生命周期 - 挂载之前beforeUpdate() {}, //生命周期 - 更新之前updated() {}, //生命周期 - 更新之后beforeDestroy() {}, //生命周期 - 销毁之前destroyed() {}, //生命周期 - 销毁完成activated() {} //如果页面有keep-alive缓存功能,这个函数会触发
};
</script>
<style scoped>
</style>

后端修改成批量更新

@RequestMapping("/update/sort")//@RequiresPermissions("product:category:update")public R updateSort(@RequestBody CategoryEntity[] category){categoryService.updateBatchById(Arrays.asList(category));return R.ok();}

1.6 添加批量删除功能

 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({message: "菜单批量删除成功",type: "success"});this.getMenus();});}).catch(() => {});},

1.7 实现品牌管理 使用逆向工程快速生成

先创建一个分类
在这里插入图片描述
找到我们之前逆向生成的代码,用的是人人代码逆向生成项目生成的,具体流程看我之前的文章。
在这里插入图片描述

之前会把vue模块代码也生成到项目的modules下直接拿出来就可以用了

在这里插入图片描述
里面的字段名字就是数据库字段的注释
在这里插入图片描述
设置页面显示状态的开关

      <el-table-column prop="showStatus" header-align="center" align="center" label="显示状态"><template slot-scope="scope"><el-switchv-model="scope.row.showStatus"active-color="#13ce66"inactive-color="#ff4949":active-value="1":inactive-value="0"@change="updateBrandStatus(scope.row)"></el-switch></template></el-table-column>

在这里插入图片描述
绑定点击函数来更新最新状态。

    updateBrandStatus(data) {console.log("最新信息", data);let { brandId, showStatus } = data;//发送请求修改状态this.$http({url: this.$http.adornUrl("/product/brand/update/status"),method: "post",data: this.$http.adornData({ brandId, showStatus }, false)}).then(({ data }) => {this.$message({type: "success",message: "状态更新成功"});});},

1.7.1 实现文件上传功能存-对接使用OSS

因为自建存储服务太浪费时间,所以购买现成的云产品
在这里插入图片描述
在阿里云获取其他云存储厂商创建oss服务
在这里插入图片描述
两种上传模式
在这里插入图片描述
另一种通过前端直接上传oss
https://blog.csdn.net/txshchl/article/details/135626070

1.8 创建第三方的api项目服务

yml的配置文件

spring:cloud:nacos:discovery:server-addr: 127.0.0.1:8848alicloud: access-key: LTAI4FwvfjSycd1APnuG9bjjsecret-key: O6xaxyiWfSIitcOkSuK27ju4hXT5Hloss:endpoint: oss-cn-beijing.aliyuncs.combucket: gulimall-helloapplication:name: gulimall-third-partyserver:port: 30000

nacos的配置文件

spring.application.name=gulimall-third-party
spring.cloud.nacos.config.server-addr=127.0.0.1:8848
spring.cloud.nacos.config.namespace=4380e303-f736-4fe8-99e9-b5e842506888spring.cloud.nacos.config.ext-config[0].data-id=oss.yml
spring.cloud.nacos.config.ext-config[0].group=DEFAULT_GROUP
spring.cloud.nacos.config.ext-config[0].refresh=true

联调oss上传接口

package com.atguigu.gulimall.thirdparty.controller;import com.aliyun.oss.OSS;
import com.aliyun.oss.common.utils.BinaryUtil;
import com.aliyun.oss.model.MatchMode;
import com.aliyun.oss.model.PolicyConditions;
import com.atguigu.common.utils.R;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.LinkedHashMap;
import java.util.Map;@RestController
public class OssController {@AutowiredOSS ossClient;@Value("${spring.cloud.alicloud.oss.endpoint}")private String endpoint;@Value("${spring.cloud.alicloud.oss.bucket}")private String bucket;@Value("${spring.cloud.alicloud.access-key}")private String accessId;@RequestMapping("/oss/policy")public R policy() {//https://gulimall-hello.oss-cn-beijing.aliyuncs.com/hahaha.jpgString host = "https://" + bucket + "." + endpoint; // host的格式为 bucketname.endpoint// callbackUrl为 上传回调服务器的URL,请将下面的IP和Port配置为您自己的真实信息。
//        String callbackUrl = "http://88.88.88.88:8888";String format = new SimpleDateFormat("yyyy-MM-dd").format(new Date());String dir = format + "/"; // 用户上传文件时指定的前缀。Map<String, String> respMap = null;try {long expireTime = 30;long expireEndTime = System.currentTimeMillis() + expireTime * 1000;Date expiration = new Date(expireEndTime);PolicyConditions policyConds = new PolicyConditions();policyConds.addConditionItem(PolicyConditions.COND_CONTENT_LENGTH_RANGE, 0, 1048576000);policyConds.addConditionItem(MatchMode.StartWith, PolicyConditions.COND_KEY, dir);String postPolicy = ossClient.generatePostPolicy(expiration, policyConds);byte[] binaryData = postPolicy.getBytes("utf-8");String encodedPolicy = BinaryUtil.toBase64String(binaryData);String postSignature = ossClient.calculatePostSignature(postPolicy);respMap = new LinkedHashMap<String, String>();respMap.put("accessid", accessId);respMap.put("policy", encodedPolicy);respMap.put("signature", postSignature);respMap.put("dir", dir);respMap.put("host", host);respMap.put("expire", String.valueOf(expireEndTime / 1000));// respMap.put("expire", formatISO8601Date(expiration));} catch (Exception e) {// Assert.fail(e.getMessage());System.out.println(e.getMessage());}return R.ok().put("data",respMap);}
}

1.8.1 前后端联调oss对接

多文件前端上传组件

<template><div><el-uploadaction="http://guli-ossyun.oss-cn-beijing.aliyuncs.com":data="dataObj":list-type="listType":file-list="fileList":before-upload="beforeUpload":on-remove="handleRemove":on-success="handleUploadSuccess":on-preview="handlePreview":limit="maxCount":on-exceed="handleExceed":show-file-list="showFile"><i class="el-icon-plus"></i></el-upload><el-dialog :visible.sync="dialogVisible"><img width="100%" :src="dialogImageUrl" alt /></el-dialog></div>
</template>
<script>
import { policy } from "./policy";
import { getUUID } from '@/utils'
export default {name: "multiUpload",props: {//图片属性数组value: Array,//最大上传图片数量maxCount: {type: Number,default: 30},listType:{type: String,default: "picture-card"},showFile:{type: Boolean,default: true}},data() {return {dataObj: {policy: "",signature: "",key: "",ossaccessKeyId: "",dir: "",host: "",uuid: ""},dialogVisible: false,dialogImageUrl: null};},computed: {fileList() {let fileList = [];for (let i = 0; i < this.value.length; i++) {fileList.push({ url: this.value[i] });}return fileList;}},mounted() {},methods: {emitInput(fileList) {let value = [];for (let i = 0; i < fileList.length; i++) {value.push(fileList[i].url);}this.$emit("input", value);},handleRemove(file, fileList) {this.emitInput(fileList);},handlePreview(file) {this.dialogVisible = true;this.dialogImageUrl = file.url;},beforeUpload(file) {let _self = this;return new Promise((resolve, reject) => {policy().then(response => {console.log("这是什么${filename}");_self.dataObj.policy = response.data.policy;_self.dataObj.signature = response.data.signature;_self.dataObj.ossaccessKeyId = response.data.accessid;_self.dataObj.key = response.data.dir +getUUID()+"_${filename}";_self.dataObj.dir = response.data.dir;_self.dataObj.host = response.data.host;resolve(true);}).catch(err => {console.log("出错了...",err)reject(false);});});},handleUploadSuccess(res, file) {this.fileList.push({name: file.name,// url: this.dataObj.host + "/" + this.dataObj.dir + "/" + file.name; 替换${filename}为真正的文件名url: this.dataObj.host + "/" + this.dataObj.key.replace("${filename}",file.name)});this.emitInput(this.fileList);},handleExceed(files, fileList) {this.$message({message: "最多只能上传" + this.maxCount + "张图片",type: "warning",duration: 1000});}}
};
</script>
<style>
</style>

单文件前端上传组件

<template> <div><el-uploadaction="http://guli-ossyun.oss-cn-beijing.aliyuncs.com":data="dataObj"list-type="picture":multiple="false" :show-file-list="showFileList":file-list="fileList":before-upload="beforeUpload":on-remove="handleRemove":on-success="handleUploadSuccess":on-preview="handlePreview"><el-button size="small" type="primary">点击上传</el-button><div slot="tip" class="el-upload__tip">只能上传jpg/png文件,且不超过10MB</div></el-upload><el-dialog :visible.sync="dialogVisible"><img width="100%" :src="fileList[0].url" alt=""></el-dialog></div>
</template>
<script>import {policy} from './policy'import { getUUID } from '@/utils'export default {name: 'singleUpload',props: {value: String},computed: {imageUrl() {return this.value;},imageName() {if (this.value != null && this.value !== '') {return this.value.substr(this.value.lastIndexOf("/") + 1);} else {return null;}},fileList() {return [{name: this.imageName,url: this.imageUrl}]},showFileList: {get: function () {return this.value !== null && this.value !== ''&& this.value!==undefined;},set: function (newValue) {}}},data() {return {dataObj: {policy: '',signature: '',key: '',ossaccessKeyId: '',dir: '',host: '',// callback:'',},dialogVisible: false};},methods: {emitInput(val) {this.$emit('input', val)},handleRemove(file, fileList) {this.emitInput('');},handlePreview(file) {this.dialogVisible = true;},beforeUpload(file) {let _self = this;return new Promise((resolve, reject) => {policy().then(response => {console.log("响应的数据",response);_self.dataObj.policy = response.data.policy;_self.dataObj.signature = response.data.signature;_self.dataObj.ossaccessKeyId = response.data.accessid;_self.dataObj.key = response.data.dir +getUUID()+'_${filename}';_self.dataObj.dir = response.data.dir;_self.dataObj.host = response.data.host;console.log("响应的数据222。。。",_self.dataObj);resolve(true)}).catch(err => {reject(false)})})},handleUploadSuccess(res, file) {console.log("上传成功...")this.showFileList = true;this.fileList.pop();this.fileList.push({name: file.name, url: this.dataObj.host + '/' + this.dataObj.key.replace("${filename}",file.name) });this.emitInput(this.fileList[0].url);}}}
</script>
<style></style>

商品详情页

<template><div class="mod-config"><el-form :inline="true" :model="dataForm" @keyup.enter.native="getDataList()"><el-form-item><el-input v-model="dataForm.key" placeholder="参数名" clearable></el-input></el-form-item><el-form-item><el-button @click="getDataList()">查询</el-button><el-buttontype="primary"@click="addOrUpdateHandle()">新增</el-button><el-buttonv-if="isAuth('product:brand:delete')"type="danger"@click="deleteHandle()":disabled="dataListSelections.length <= 0">批量删除</el-button></el-form-item></el-form><el-table:data="dataList"borderv-loading="dataListLoading"@selection-change="selectionChangeHandle"style="width: 100%;"><el-table-column type="selection" header-align="center" align="center" width="50"></el-table-column><el-table-column prop="brandId" header-align="center" align="center" label="品牌id"></el-table-column><el-table-column prop="name" header-align="center" align="center" label="品牌名"></el-table-column><el-table-column prop="logo" header-align="center" align="center" label="品牌logo地址"><template slot-scope="scope"><!-- <el-imagestyle="width: 100px; height: 80px":src="scope.row.logo"fit="fill"></el-image>--><img :src="scope.row.logo" style="width: 100px; height: 80px" /></template></el-table-column><el-table-column prop="descript" header-align="center" align="center" label="介绍"></el-table-column><el-table-column prop="showStatus" header-align="center" align="center" label="显示状态"><template slot-scope="scope"><el-switchv-model="scope.row.showStatus"active-color="#13ce66"inactive-color="#ff4949":active-value="1":inactive-value="0"@change="updateBrandStatus(scope.row)"></el-switch></template></el-table-column><el-table-column prop="firstLetter" header-align="center" align="center" label="检索首字母"></el-table-column><el-table-column prop="sort" header-align="center" align="center" label="排序"></el-table-column><el-table-column fixed="right" header-align="center" align="center" width="250" label="操作"><template slot-scope="scope"><el-button type="text" size="small" @click="updateCatelogHandle(scope.row.brandId)">关联分类</el-button><el-button type="text" size="small" @click="addOrUpdateHandle(scope.row.brandId)">修改</el-button><el-button type="text" size="small" @click="deleteHandle(scope.row.brandId)">删除</el-button></template></el-table-column></el-table><el-pagination@size-change="sizeChangeHandle"@current-change="currentChangeHandle":current-page="pageIndex":page-sizes="[10, 20, 50, 100]":page-size="pageSize":total="totalPage"layout="total, sizes, prev, pager, next, jumper"></el-pagination><!-- 弹窗, 新增 / 修改 --><add-or-update v-if="addOrUpdateVisible" ref="addOrUpdate" @refreshDataList="getDataList"></add-or-update><el-dialog title="关联分类" :visible.sync="cateRelationDialogVisible" width="30%"><el-popover placement="right-end" v-model="popCatelogSelectVisible"><category-cascader :catelogPath.sync="catelogPath"></category-cascader><div style="text-align: right; margin: 0"><el-button size="mini" type="text" @click="popCatelogSelectVisible = false">取消</el-button><el-button type="primary" size="mini" @click="addCatelogSelect">确定</el-button></div><el-button slot="reference">新增关联</el-button></el-popover><el-table :data="cateRelationTableData" style="width: 100%"><el-table-column prop="id" label="#"></el-table-column><el-table-column prop="brandName" label="品牌名"></el-table-column><el-table-column prop="catelogName" label="分类名"></el-table-column><el-table-column fixed="right" header-align="center" align="center" label="操作"><template slot-scope="scope"><el-buttontype="text"size="small"@click="deleteCateRelationHandle(scope.row.id,scope.row.brandId)">移除</el-button></template></el-table-column></el-table><span slot="footer" class="dialog-footer"><el-button @click="cateRelationDialogVisible = false">取 消</el-button><el-button type="primary" @click="cateRelationDialogVisible = false">确 定</el-button></span></el-dialog></div>
</template><script>
import AddOrUpdate from "./brand-add-or-update";
import CategoryCascader from "../common/category-cascader";
export default {data() {return {dataForm: {key: ""},brandId: 0,catelogPath: [],dataList: [],cateRelationTableData: [],pageIndex: 1,pageSize: 10,totalPage: 0,dataListLoading: false,dataListSelections: [],addOrUpdateVisible: false,cateRelationDialogVisible: false,popCatelogSelectVisible: false};},components: {AddOrUpdate,CategoryCascader},activated() {this.getDataList();},methods: {addCatelogSelect() {//{"brandId":1,"catelogId":2}this.popCatelogSelectVisible =false;this.$http({url: this.$http.adornUrl("/product/categorybrandrelation/save"),method: "post",data: this.$http.adornData({brandId:this.brandId,catelogId:this.catelogPath[this.catelogPath.length-1]}, false)}).then(({ data }) => {this.getCateRelation();});},deleteCateRelationHandle(id, brandId) {this.$http({url: this.$http.adornUrl("/product/categorybrandrelation/delete"),method: "post",data: this.$http.adornData([id], false)}).then(({ data }) => {this.getCateRelation();});},updateCatelogHandle(brandId) {this.cateRelationDialogVisible = true;this.brandId = brandId;this.getCateRelation();},getCateRelation() {this.$http({url: this.$http.adornUrl("/product/categorybrandrelation/catelog/list"),method: "get",params: this.$http.adornParams({brandId: this.brandId})}).then(({ data }) => {this.cateRelationTableData = data.data;});},// 获取数据列表getDataList() {this.dataListLoading = true;this.$http({url: this.$http.adornUrl("/product/brand/list"),method: "get",params: this.$http.adornParams({page: this.pageIndex,limit: this.pageSize,key: this.dataForm.key})}).then(({ data }) => {if (data && data.code === 0) {this.dataList = data.page.list;this.totalPage = data.page.totalCount;} else {this.dataList = [];this.totalPage = 0;}this.dataListLoading = false;});},updateBrandStatus(data) {console.log("最新信息", data);let { brandId, showStatus } = data;//发送请求修改状态this.$http({url: this.$http.adornUrl("/product/brand/update/status"),method: "post",data: this.$http.adornData({ brandId, showStatus }, false)}).then(({ data }) => {this.$message({type: "success",message: "状态更新成功"});});},// 每页数sizeChangeHandle(val) {this.pageSize = val;this.pageIndex = 1;this.getDataList();},// 当前页currentChangeHandle(val) {this.pageIndex = val;this.getDataList();},// 多选selectionChangeHandle(val) {this.dataListSelections = val;},// 新增 / 修改addOrUpdateHandle(id) {this.addOrUpdateVisible = true;this.$nextTick(() => {this.$refs.addOrUpdate.init(id);});},// 删除deleteHandle(id) {var ids = id? [id]: this.dataListSelections.map(item => {return item.brandId;});this.$confirm(`确定对[id=${ids.join(",")}]进行[${id ? "删除" : "批量删除"}]操作?`,"提示",{confirmButtonText: "确定",cancelButtonText: "取消",type: "warning"}).then(() => {this.$http({url: this.$http.adornUrl("/product/brand/delete"),method: "post",data: this.$http.adornData(ids, false)}).then(({ data }) => {if (data && data.code === 0) {this.$message({message: "操作成功",type: "success",duration: 1500,onClose: () => {this.getDataList();}});} else {this.$message.error(data.msg);}});});}}
};
</script>

后端代码

package com.atguigu.gulimall.thirdparty.controller;import com.aliyun.oss.OSS;
import com.aliyun.oss.common.utils.BinaryUtil;
import com.aliyun.oss.model.MatchMode;
import com.aliyun.oss.model.PolicyConditions;
import com.atguigu.common.utils.R;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.LinkedHashMap;
import java.util.Map;@RestController
public class OssController {@AutowiredOSS ossClient;@Value("${spring.cloud.alicloud.oss.endpoint}")private String endpoint;@Value("${spring.cloud.alicloud.oss.bucket}")private String bucket;@Value("${spring.cloud.alicloud.access-key}")private String accessId;@RequestMapping("/oss/policy")public R policy() {//https://gulimall-hello.oss-cn-beijing.aliyuncs.com/hahaha.jpgString host = "https://" + bucket + "." + endpoint; // host的格式为 bucketname.endpoint// callbackUrl为 上传回调服务器的URL,请将下面的IP和Port配置为您自己的真实信息。
//        String callbackUrl = "http://88.88.88.88:8888";String format = new SimpleDateFormat("yyyy-MM-dd").format(new Date());String dir = format + "/"; // 用户上传文件时指定的前缀。Map<String, String> respMap = null;try {long expireTime = 30;long expireEndTime = System.currentTimeMillis() + expireTime * 1000;Date expiration = new Date(expireEndTime);PolicyConditions policyConds = new PolicyConditions();policyConds.addConditionItem(PolicyConditions.COND_CONTENT_LENGTH_RANGE, 0, 1048576000);policyConds.addConditionItem(MatchMode.StartWith, PolicyConditions.COND_KEY, dir);String postPolicy = ossClient.generatePostPolicy(expiration, policyConds);byte[] binaryData = postPolicy.getBytes("utf-8");String encodedPolicy = BinaryUtil.toBase64String(binaryData);String postSignature = ossClient.calculatePostSignature(postPolicy);respMap = new LinkedHashMap<String, String>();respMap.put("accessid", accessId);respMap.put("policy", encodedPolicy);respMap.put("signature", postSignature);respMap.put("dir", dir);respMap.put("host", host);respMap.put("expire", String.valueOf(expireEndTime / 1000));// respMap.put("expire", formatISO8601Date(expiration));} catch (Exception e) {// Assert.fail(e.getMessage());System.out.println(e.getMessage());}return R.ok().put("data",respMap);}
}

在这里插入图片描述

1.8.2 JSR303添加校验注解b

通过添加校验注解来让输入的内容不能为空。

package com.atguigu.gulimall.product.entity;import com.atguigu.common.valid.AddGroup;
import com.atguigu.common.valid.ListValue;
import com.atguigu.common.valid.UpdateGroup;
import com.atguigu.common.valid.UpdateStatusGroup;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;import java.io.Serializable;import lombok.Data;
import org.hibernate.validator.constraints.URL;import javax.validation.constraints.*;/*** 品牌* * @author leifengyang* @email leifengyang@gmail.com* @date 2019-10-01 21:08:49*/
@Data
@TableName("pms_brand")
public class BrandEntity implements Serializable {private static final long serialVersionUID = 1L;/*** 品牌id*/@NotNull(message = "修改必须指定品牌id",groups = {UpdateGroup.class})@Null(message = "新增不能指定id",groups = {AddGroup.class})@TableIdprivate Long brandId;/*** 品牌名*/@NotBlank(message = "品牌名必须提交",groups = {AddGroup.class,UpdateGroup.class})private String name;/*** 品牌logo地址*/@NotBlank(groups = {AddGroup.class})@URL(message = "logo必须是一个合法的url地址",groups={AddGroup.class,UpdateGroup.class})private String logo;/*** 介绍*/private String descript;/*** 显示状态[0-不显示;1-显示]*/
//	@Pattern()@NotNull(groups = {AddGroup.class, UpdateStatusGroup.class})@ListValue(vals={0,1},groups = {AddGroup.class, UpdateStatusGroup.class})private Integer showStatus;/*** 检索首字母*/@NotEmpty(groups={AddGroup.class})@Pattern(regexp="^[a-zA-Z]$",message = "检索首字母必须是一个字母",groups={AddGroup.class,UpdateGroup.class})private String firstLetter;/*** 排序*/@NotNull(groups={AddGroup.class})@Min(value = 0,message = "排序必须大于等于0",groups={AddGroup.class,UpdateGroup.class})private Integer sort;}

控制层要添加Validated注解才能让他判断不为空生效

    @RequestMapping("/save")//@RequiresPermissions("product:brand:save")public R save(@Validated({AddGroup.class}) @RequestBody BrandEntity brand/*,BindingResult result*/){
//        if(result.hasErrors()){
//            Map<String,String> map = new HashMap<>();
//            //1、获取校验的错误结果
//            result.getFieldErrors().forEach((item)->{
//                //FieldError 获取到错误提示
//                String message = item.getDefaultMessage();
//                //获取错误的属性的名字
//                String field = item.getField();
//                map.put(field,message);
//            });
//
//            return R.error(400,"提交的数据不合法").put("data",map);
//        }else {
//
//        }brandService.save(brand);return R.ok();}

1.9 统一异常处理

之前的异常处理方法太过于麻烦。单独创建一个异常类,他的作用就是用来统一处理异常的。

package com.atguigu.gulimall.product.exception;import com.atguigu.common.exception.BizCodeEnume;
import com.atguigu.common.utils.R;
import lombok.extern.slf4j.Slf4j;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.servlet.ModelAndView;import java.util.HashMap;
import java.util.Map;/*** 集中处理所有异常*/
@Slf4j
//@ResponseBody
//@ControllerAdvice(basePackages = "com.atguigu.gulimall.product.controller")
@RestControllerAdvice(basePackages = "com.atguigu.gulimall.product.controller")
public class GulimallExceptionControllerAdvice {@ExceptionHandler(value= MethodArgumentNotValidException.class)public R handleVaildException(MethodArgumentNotValidException e){log.error("数据校验出现问题{},异常类型:{}",e.getMessage(),e.getClass());BindingResult bindingResult = e.getBindingResult();Map<String,String> errorMap = new HashMap<>();bindingResult.getFieldErrors().forEach((fieldError)->{errorMap.put(fieldError.getField(),fieldError.getDefaultMessage());});return R.error(BizCodeEnume.VAILD_EXCEPTION.getCode(),BizCodeEnume.VAILD_EXCEPTION.getMsg()).put("data",errorMap);}@ExceptionHandler(value = Throwable.class)public R handleException(Throwable throwable){log.error("错误:",throwable);return R.error(BizCodeEnume.UNKNOW_EXCEPTION.getCode(),BizCodeEnume.UNKNOW_EXCEPTION.getMsg());}}

异常代码类

package com.atguigu.common.exception;/**** 错误码和错误信息定义类* 1. 错误码定义规则为5为数字* 2. 前两位表示业务场景,最后三位表示错误码。例如:100001。10:通用 001:系统未知异常* 3. 维护错误码后需要维护错误描述,将他们定义为枚举形式* 错误码列表:*  10: 通用*      001:参数格式校验*  11: 商品*  12: 订单*  13: 购物车*  14: 物流***/
public enum BizCodeEnume {UNKNOW_EXCEPTION(10000,"系统未知异常"),VAILD_EXCEPTION(10001,"参数格式校验失败");private int code;private String msg;BizCodeEnume(int code,String msg){this.code = code;this.msg = msg;}public int getCode() {return code;}public String getMsg() {return msg;}
}

1.10 SPU&SKU

SPU是一个商品,而SKU是该商品的不同的规格
在这里插入图片描述

1.11 属性分组 整和分类组件

计划设计左边是一个多级菜单 右边是我们的属性。
在这里插入图片描述
为了避免重复的造轮子,先创建出来一个公共的类category.vue
在这里插入图片描述
使用这个栅格布局代表一整行
在这里插入图片描述
计划是六列菜单十八列表格
在这里插入图片描述
在这里插入图片描述
把之前写的比菜单列表抽到刚刚新建的category组件中

<template><div><el-tree :data="menus" :props="defaultProps" node-key="catId" ref="menuTree" @node-click="nodeclick":filter-node-method="filterNode" :highlight-current="true"></el-tree></div>
</template><script>
//这里可以导入其他文件(比如:组件,工具js,第三方插件js,json文件,图片文件等等)
//例如:import 《组件名称》 from '《组件路径》';export default {//import引入的组件需要注入到对象中才能使用components: {},props: {},data() {//这里存放数据return {filterText: "",menus: [],expandedKey: [],defaultProps: {children: "children",label: "name"}};},//计算属性 类似于data概念computed: {},//监控data中的数据变化watch: {filterText(val) {this.$refs.menuTree.filter(val);}},//方法集合methods: {//树节点过滤filterNode(value, data) {if (!value) return true;return data.name.indexOf(value) !== -1;},getMenus() {this.$http({url: this.$http.adornUrl("/product/category/list/tree"),method: "get"}).then(({ data }) => {this.menus = data.data;});},nodeclick(data, node, component) {console.log("子组件category的节点被点击", data, node, component);//向父组件发送事件;this.$emit("tree-node-click", data, node, component);}},//生命周期 - 创建完成(可以访问当前this实例)created() {this.getMenus();},//生命周期 - 挂载完成(可以访问DOM元素)mounted() { },beforeCreate() { }, //生命周期 - 创建之前beforeMount() { }, //生命周期 - 挂载之前beforeUpdate() { }, //生命周期 - 更新之前updated() { }, //生命周期 - 更新之后beforeDestroy() { }, //生命周期 - 销毁之前destroyed() { }, //生命周期 - 销毁完成activated() { } //如果页面有keep-alive缓存功能,这个函数会触发
};
</script>
<style scoped></style>

在vue里面使用刚刚创建的菜单分类组件 直接导入就行

export default {//import引入的组件需要注入到对象中才能使用components: {category}, //注入后把组件名做标签使用props: {},data() {return {};},
}

然后直接插入

<template><el-row :gutter="20"><el-col :span="6"><category></category></el-col><el-col :span="18">十八列表格</el-col></el-row>
</template>

效果就是我们刚刚插入的组件了
在这里插入图片描述
把刚刚逆向生成的属性分组内容复制粘贴到表格中
在这里插入图片描述
把data数据也一并复制过去

<template><el-row :gutter="20"><el-col :span="6"><category></category></el-col><el-col :span="18"><div class="mod-config"><el-form :inline="true" :model="dataForm" @keyup.enter.native="getDataList()"><el-form-item><el-input v-model="dataForm.key" placeholder="参数名" clearable></el-input></el-form-item><el-form-item><el-button @click="getDataList()">查询</el-button><el-button type="success" @click="getAllDataList()">查询全部</el-button><el-buttonv-if="isAuth('product:attrgroup:save')"type="primary"@click="addOrUpdateHandle()">新增</el-button><el-buttonv-if="isAuth('product:attrgroup:delete')"type="danger"@click="deleteHandle()":disabled="dataListSelections.length <= 0">批量删除</el-button></el-form-item></el-form><el-table:data="dataList"borderv-loading="dataListLoading"@selection-change="selectionChangeHandle"style="width: 100%;"><el-table-column type="selection" header-align="center" align="center" width="50"></el-table-column><el-table-column prop="attrGroupId" header-align="center" align="center" label="分组id"></el-table-column><el-table-column prop="attrGroupName" header-align="center" align="center" label="组名"></el-table-column><el-table-column prop="sort" header-align="center" align="center" label="排序"></el-table-column><el-table-column prop="descript" header-align="center" align="center" label="描述"></el-table-column><el-table-column prop="icon" header-align="center" align="center" label="组图标"></el-table-column><el-table-column prop="catelogId" header-align="center" align="center" label="所属分类id"></el-table-column><el-table-columnfixed="right"header-align="center"align="center"width="150"label="操作"><template slot-scope="scope"><el-button type="text" size="small" @click="relationHandle(scope.row.attrGroupId)">关联</el-button><el-buttontype="text"size="small"@click="addOrUpdateHandle(scope.row.attrGroupId)">修改</el-button><el-button type="text" size="small" @click="deleteHandle(scope.row.attrGroupId)">删除</el-button></template></el-table-column></el-table><el-pagination@size-change="sizeChangeHandle"@current-change="currentChangeHandle":current-page="pageIndex":page-sizes="[10, 20, 50, 100]":page-size="pageSize":total="totalPage"layout="total, sizes, prev, pager, next, jumper"></el-pagination><!-- 弹窗, 新增 / 修改 --><add-or-update v-if="addOrUpdateVisible" ref="addOrUpdate" @refreshDataList="getDataList"></add-or-update></div></el-col></el-row>
</template><script>
import category  from '../common/category.vue';
export default {//import引入的组件需要注入到对象中才能使用components: {category}, //注入后把组件名做标签使用props: {},data() {return {catId: 0,dataForm: {key: ""},dataList: [],pageIndex: 1,pageSize: 10,totalPage: 0,dataListLoading: false,dataListSelections: [],addOrUpdateVisible: false,relationVisible: false};},
}
</script><style></style>

在这里插入图片描述
导入一个增删改查组件

<template><el-dialog:title="!dataForm.id ? '新增' : '修改'":close-on-click-modal="false":visible.sync="visible"@closed="dialogClose"><el-form :model="dataForm" :rules="dataRule" ref="dataForm" label-width="120px"><!--       @keyup.enter.native="dataFormSubmit()" --><el-form-item label="属性名" prop="attrName"><el-input v-model="dataForm.attrName" placeholder="属性名"></el-input></el-form-item><el-form-item label="属性类型" prop="attrType"><el-select v-model="dataForm.attrType" placeholder="请选择"><el-option label="规格参数" :value="1"></el-option><el-option label="销售属性" :value="0"></el-option></el-select></el-form-item><el-form-item label="值类型" prop="valueType"><el-switchv-model="dataForm.valueType"active-text="允许多个值"inactive-text="只能单个值"active-color="#13ce66"inactive-color="#ff4949":inactive-value="0":active-value="1"></el-switch></el-form-item><el-form-item label="可选值" prop="valueSelect"><!-- <el-input v-model="dataForm.valueSelect"></el-input> --><el-selectv-model="dataForm.valueSelect"multiplefilterableallow-createplaceholder="请输入内容"></el-select></el-form-item><el-form-item label="属性图标" prop="icon"><el-input v-model="dataForm.icon" placeholder="属性图标"></el-input></el-form-item><el-form-item label="所属分类" prop="catelogId"><category-cascader :catelogPath.sync="catelogPath"></category-cascader></el-form-item><el-form-item label="所属分组" prop="attrGroupId" v-if="type == 1"><el-select ref="groupSelect" v-model="dataForm.attrGroupId" placeholder="请选择"><el-optionv-for="item in attrGroups":key="item.attrGroupId":label="item.attrGroupName":value="item.attrGroupId"></el-option></el-select></el-form-item><el-form-item label="可检索" prop="searchType" v-if="type == 1"><el-switchv-model="dataForm.searchType"active-color="#13ce66"inactive-color="#ff4949":active-value="1":inactive-value="0"></el-switch></el-form-item><el-form-item label="快速展示" prop="showDesc" v-if="type == 1"><el-switchv-model="dataForm.showDesc"active-color="#13ce66"inactive-color="#ff4949":active-value="1":inactive-value="0"></el-switch></el-form-item><el-form-item label="启用状态" prop="enable"><el-switchv-model="dataForm.enable"active-color="#13ce66"inactive-color="#ff4949":active-value="1":inactive-value="0"></el-switch></el-form-item></el-form><span slot="footer" class="dialog-footer"><el-button @click="visible = false">取消</el-button><el-button type="primary" @click="dataFormSubmit()">确定</el-button></span></el-dialog>
</template><script>
import CategoryCascader from "../common/category-cascader";
export default {data() {return {visible: false,dataForm: {attrId: 0,attrName: "",searchType: 0,valueType: 1,icon: "",valueSelect: "",attrType: 1,enable: 1,catelogId: "",attrGroupId: "",showDesc: 0},catelogPath: [],attrGroups: [],dataRule: {attrName: [{ required: true, message: "属性名不能为空", trigger: "blur" }],searchType: [{required: true,message: "是否需要检索不能为空",trigger: "blur"}],valueType: [{required: true,message: "值类型不能为空",trigger: "blur"}],icon: [{ required: true, message: "属性图标不能为空", trigger: "blur" }],attrType: [{required: true,message: "属性类型不能为空",trigger: "blur"}],enable: [{required: true,message: "启用状态不能为空",trigger: "blur"}],catelogId: [{required: true,message: "需要选择正确的三级分类数据",trigger: "blur"}],showDesc: [{required: true,message: "快速展示不能为空",trigger: "blur"}]}};},props:{type:{type: Number,default: 1}},watch: {catelogPath(path) {//监听到路径变化需要查出这个三级分类的分组信息console.log("路径变了", path);this.attrGroups = [];this.dataForm.attrGroupId = "";this.dataForm.catelogId = path[path.length - 1];if (path && path.length == 3) {this.$http({url: this.$http.adornUrl(`/product/attrgroup/list/${path[path.length - 1]}`),method: "get",params: this.$http.adornParams({ page: 1, limit: 10000000 })}).then(({ data }) => {if (data && data.code === 0) {this.attrGroups = data.page.list;} else {this.$message.error(data.msg);}});} else if (path.length == 0) {this.dataForm.catelogId = "";} else {this.$message.error("请选择正确的分类");this.dataForm.catelogId = "";}}},components: { CategoryCascader },methods: {init(id) {this.dataForm.attrId = id || 0;this.dataForm.attrType = this.type;this.visible = true;this.$nextTick(() => {this.$refs["dataForm"].resetFields();if (this.dataForm.attrId) {this.$http({url: this.$http.adornUrl(`/product/attr/info/${this.dataForm.attrId}`),method: "get",params: this.$http.adornParams()}).then(({ data }) => {if (data && data.code === 0) {this.dataForm.attrName = data.attr.attrName;this.dataForm.searchType = data.attr.searchType;this.dataForm.valueType = data.attr.valueType;this.dataForm.icon = data.attr.icon;this.dataForm.valueSelect = data.attr.valueSelect.split(";");this.dataForm.attrType = data.attr.attrType;this.dataForm.enable = data.attr.enable;this.dataForm.catelogId = data.attr.catelogId;this.dataForm.showDesc = data.attr.showDesc;//attrGroupId//catelogPaththis.catelogPath = data.attr.catelogPath;this.$nextTick(() => {this.dataForm.attrGroupId = data.attr.attrGroupId;});}});}});},// 表单提交dataFormSubmit() {this.$refs["dataForm"].validate(valid => {if (valid) {this.$http({url: this.$http.adornUrl(`/product/attr/${!this.dataForm.attrId ? "save" : "update"}`),method: "post",data: this.$http.adornData({attrId: this.dataForm.attrId || undefined,attrName: this.dataForm.attrName,searchType: this.dataForm.searchType,valueType: this.dataForm.valueType,icon: this.dataForm.icon,valueSelect: this.dataForm.valueSelect.join(";"),attrType: this.dataForm.attrType,enable: this.dataForm.enable,catelogId: this.dataForm.catelogId,attrGroupId: this.dataForm.attrGroupId,showDesc: this.dataForm.showDesc})}).then(({ data }) => {if (data && data.code === 0) {this.$message({message: "操作成功",type: "success",duration: 1500,onClose: () => {this.visible = false;this.$emit("refreshDataList");}});} else {this.$message.error(data.msg);}});}});},//dialogClosedialogClose() {this.catelogPath = [];}}
};
</script>
import AddOrUpdate from "./attrgroup-add-or-update";

后续修改内容需要点击左边分类的时候,右边自动查询出来这个分类下面的商品信息
在这里插入图片描述
难点就在于如果感知我们点击了外部的树形组件后去查询数据库。动态的刷新表格里面的内容。

/*** 父子组件传递数据* 1)、子组件给父组件传递数据,事件机制;*    子组件给父组件发送一个事件,携带上数据。* // this.$emit("事件名",携带的数据...)

使用这个事件
在这里插入图片描述

//绑定nodeclient<category @tree-node-click="treenodeclick"></category>//感知树节点被点击treenodeclick(data, node, component) {if (node.level == 3) {this.catId = data.catId;this.getDataList(); //重新查询}},

完整写法 包含增删改查

<template><el-row :gutter="20"><el-col :span="6"><category @tree-node-click="treenodeclick"></category></el-col><el-col :span="18"><div class="mod-config"><el-form :inline="true" :model="dataForm" @keyup.enter.native="getDataList()"><el-form-item><el-input v-model="dataForm.key" placeholder="参数名" clearable></el-input></el-form-item><el-form-item><el-button @click="getDataList()">查询</el-button><el-button type="success" @click="getAllDataList()">查询全部</el-button><el-buttonv-if="isAuth('product:attrgroup:save')"type="primary"@click="addOrUpdateHandle()">新增</el-button><el-buttonv-if="isAuth('product:attrgroup:delete')"type="danger"@click="deleteHandle()":disabled="dataListSelections.length <= 0">批量删除</el-button></el-form-item></el-form><el-table:data="dataList"borderv-loading="dataListLoading"@selection-change="selectionChangeHandle"style="width: 100%;"><el-table-column type="selection" header-align="center" align="center" width="50"></el-table-column><el-table-column prop="attrGroupId" header-align="center" align="center" label="分组id"></el-table-column><el-table-column prop="attrGroupName" header-align="center" align="center" label="组名"></el-table-column><el-table-column prop="sort" header-align="center" align="center" label="排序"></el-table-column><el-table-column prop="descript" header-align="center" align="center" label="描述"></el-table-column><el-table-column prop="icon" header-align="center" align="center" label="组图标"></el-table-column><el-table-column prop="catelogId" header-align="center" align="center" label="所属分类id"></el-table-column><el-table-columnfixed="right"header-align="center"align="center"width="150"label="操作"><template slot-scope="scope"><el-button type="text" size="small" @click="relationHandle(scope.row.attrGroupId)">关联</el-button><el-buttontype="text"size="small"@click="addOrUpdateHandle(scope.row.attrGroupId)">修改</el-button><el-button type="text" size="small" @click="deleteHandle(scope.row.attrGroupId)">删除</el-button></template></el-table-column></el-table><el-pagination@size-change="sizeChangeHandle"@current-change="currentChangeHandle":current-page="pageIndex":page-sizes="[10, 20, 50, 100]":page-size="pageSize":total="totalPage"layout="total, sizes, prev, pager, next, jumper"></el-pagination><!-- 弹窗, 新增 / 修改 --><add-or-update v-if="addOrUpdateVisible" ref="addOrUpdate" @refreshDataList="getDataList"></add-or-update><!-- 修改关联关系 --><relation-update v-if="relationVisible" ref="relationUpdate" @refreshData="getDataList"></relation-update></div></el-col></el-row>
</template><script>
/*** 父子组件传递数据* 1)、子组件给父组件传递数据,事件机制;*    子组件给父组件发送一个事件,携带上数据。* // this.$emit("事件名",携带的数据...)*/
//这里可以导入其他文件(比如:组件,工具js,第三方插件js,json文件,图片文件等等)
//例如:import 《组件名称》 from '《组件路径》';
import Category from "../common/category";
import AddOrUpdate from "./attrgroup-add-or-update";
import RelationUpdate from "./attr-group-relation";
export default {//import引入的组件需要注入到对象中才能使用components: { Category, AddOrUpdate, RelationUpdate },props: {},data() {return {catId: 0,dataForm: {key: ""},dataList: [],pageIndex: 1,pageSize: 10,totalPage: 0,dataListLoading: false,dataListSelections: [],addOrUpdateVisible: false,relationVisible: false};},activated() {this.getDataList();},methods: {//处理分组与属性的关联relationHandle(groupId) {this.relationVisible = true;this.$nextTick(() => {this.$refs.relationUpdate.init(groupId);});},//感知树节点被点击treenodeclick(data, node, component) {if (node.level == 3) {this.catId = data.catId;this.getDataList(); //重新查询}},getAllDataList(){this.catId = 0;this.getDataList();},// 获取数据列表getDataList() {this.dataListLoading = true;this.$http({url: this.$http.adornUrl(`/product/attrgroup/list/${this.catId}`),method: "get",params: this.$http.adornParams({page: this.pageIndex,limit: this.pageSize,key: this.dataForm.key})}).then(({ data }) => {if (data && data.code === 0) {this.dataList = data.page.list;this.totalPage = data.page.totalCount;} else {this.dataList = [];this.totalPage = 0;}this.dataListLoading = false;});},// 每页数sizeChangeHandle(val) {this.pageSize = val;this.pageIndex = 1;this.getDataList();},// 当前页currentChangeHandle(val) {this.pageIndex = val;this.getDataList();},// 多选selectionChangeHandle(val) {this.dataListSelections = val;},// 新增 / 修改addOrUpdateHandle(id) {this.addOrUpdateVisible = true;this.$nextTick(() => {this.$refs.addOrUpdate.init(id);});},// 删除deleteHandle(id) {var ids = id? [id]: this.dataListSelections.map(item => {return item.attrGroupId;});this.$confirm(`确定对[id=${ids.join(",")}]进行[${id ? "删除" : "批量删除"}]操作?`,"提示",{confirmButtonText: "确定",cancelButtonText: "取消",type: "warning"}).then(() => {this.$http({url: this.$http.adornUrl("/product/attrgroup/delete"),method: "post",data: this.$http.adornData(ids, false)}).then(({ data }) => {if (data && data.code === 0) {this.$message({message: "操作成功",type: "success",duration: 1500,onClose: () => {this.getDataList();}});} else {this.$message.error(data.msg);}});});}}
};
</script>
<style scoped>
</style>

后端做多条件查询

    @Overridepublic PageUtils queryPage(Map<String, Object> params, Long catelogId) {String key = (String) params.get("key");//select * from pms_attr_group where catelog_id=? and (attr_group_id=key or attr_group_name like %key%)QueryWrapper<AttrGroupEntity> wrapper = new QueryWrapper<AttrGroupEntity>();if(!StringUtils.isEmpty(key)){wrapper.and((obj)->{obj.eq("attr_group_id",key).or().like("attr_group_name",key);});}if( catelogId == 0){IPage<AttrGroupEntity> page = this.page(new Query<AttrGroupEntity>().getPage(params),wrapper);return new PageUtils(page);}else {wrapper.eq("catelog_id",catelogId);IPage<AttrGroupEntity> page = this.page(new Query<AttrGroupEntity>().getPage(params),wrapper);return new PageUtils(page);}}

在这里插入图片描述

---- 未完待续 直接看中级了-----

https://www.bilibili.com/video/BV1np4y1C7Yf/?p=101&spm_id_from=pageDriver&vd_source=0b3904471b2f8a3a133e05bd42f729a9

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Oracle 入门--前提

目录 1.sqlplus 2.dual是什么&#xff1f; 3.SQL语句的种类 4.Oracle是如何工作的 5.Oracle查看配置文件 6.修改配置文件 7.常用的参数设置 1.sqlplus 管理数据库&#xff1a;启动&#xff0c;关闭&#xff0c;创建&#xff0c;删除对象......查看数据库的运行状态&…

【分布式计算】java消息队列机制

消息队列是一种在不同组件或应用之间进行数据传递的技术&#xff0c;通常用于处理异步通信。它允许消息的发送者&#xff08;生产者&#xff09;和接收者&#xff08;消费者&#xff09;之间进行解耦。 概念 消息队列是一种先进先出&#xff08;FIFO&#xff09;的数据结构&…

机器学习(V)--无监督学习(一)聚类

根据训练样本中是否包含标签信息&#xff0c;机器学习可以分为监督学习和无监督学习。聚类算法是典型的无监督学习&#xff0c;目的是想将那些相似的样本尽可能聚在一起&#xff0c;不相似的样本尽可能分开。 相似度或距离 聚类的核心概念是相似度(similarity)或距离(distance…

嵌入式学习记录6.14(练习)

#include "mainwindow.h" #include "ui_mainwindow.h"MainWindow::MainWindow(QWidget *parent): QMainWindow(parent), ui(new Ui::MainWindow) {ui->setupUi(this);this->resize(1028,783); //设置左侧背景QLabel *lab1new QLabel(this);lab1->…

【内存管理之堆内存】

1.栈上的基元 2.栈上的聚合对象 3.手动分配和释放 4.分配堆内存 5.数组内存分配和释放 6.数组内存分配 7.不要使用野指针 8.黑暗时代

STM32理论 —— μCOS-Ⅲ(2/2):时间管理、消息队列、信号量、任务内嵌信号量/队列、事件标志、软件定时器、内存管理

文章目录 9. 时间管理9.1 OSTimeDly()9.2 OSTimeDlyHMSM()9.3 OSTimeDlyResume()9.4 延时函数实验 10. 消息队列10.1 创建消息队列函数OSQCreate()10.2 发送消息到消息队列函数(写入队列)OSQPost()10.3 获取消息队列中的消息函数(读出队列)OSQPend()10.4 消息队列操作实验 11. …

12 款 Android 照片恢复应用程序列表

丢失难忘的照片总是令人痛苦的。如果软件崩溃或意外删除&#xff0c;Android 设备上的照片也可能会丢失。这时照片恢复应用程序就派上用场了。查看我们为 Android 收集的顶级照片恢复应用程序。 但是&#xff0c;您不会想为自己选择任何照片恢复应用程序。因此&#xff0c;我们…

Doris:冷热分层

目录 一、冷热分层介绍 二、存储策略&#xff08;Storage policy&#xff09; 2.1 创建存储资源 2.2 创建存储策略 2.3 使用存储策略 三、使用限制 一、冷热分层介绍 冷热分层支持所有 Doris 功能&#xff0c;只是把部分数据放到对象存储上&#xff0c;以节省成本&am…

openGauss 6.0.0 一主二备集群安装及使用zcbus实现Oracle到openGauss的数据同步

一、前言 openGauss 6.0.0-RC1是openGauss 2024年3月发布的创新版本&#xff0c;该版本生命周期为0.5年。根据openGauss官网介绍&#xff0c;6.0.0-RC1与之前的版本特性功能保持兼容,另外&#xff0c;在和之前版本兼容的基础上增加了很多新功能&#xff0c;比如分区表性能优化…

go的netpoll学习

go的运行时调度框架简介 Go的运行时&#xff08;runtime&#xff09;中&#xff0c;由调度器管理&#xff1a;goroutine&#xff08;G&#xff09;、操作系统线程&#xff08;M&#xff09;和逻辑处理器&#xff08;P&#xff09;之间的关系 以实现高效的并发执行 当一个gorout…

统计完全子字符串

很不错的计数问题&#xff0c;用到了分组循环技巧和滑动窗口 代码的实现方式也非常值得多看 class Solution { public:int f(string s,int k){int res 0;for(int m1;m<26&&k*m<s.size();m){int cnt[27]{};auto check[&](){for(int i0;i<26;i){if(cnt[i]…

跟着刘二大人学pytorch(第---10---节课之卷积神经网络)

文章目录 0 前言0.1 课程链接&#xff1a;0.2 课件下载地址&#xff1a; 回忆卷积卷积过程&#xff08;以输入为单通道、1个卷积核为例&#xff09;卷积过程&#xff08;以输入为3通道、1个卷积核为例&#xff09;卷积过程&#xff08;以输入为N通道、1个卷积核为例&#xff09…

计算机组成原理之定点除法

文章目录 定点除法运算原码恢复余数法原码不恢复余数法&#xff08;加减交替法&#xff09;运算规则 习题 定点除法运算 注意 &#xff08;1&#xff09;被除数小于除数的时候&#xff0c;商0 &#xff08;2&#xff09;接下来&#xff0c;有一个除数再原来的基础上&#xff0c…

springboot + Vue前后端项目(第十六记)

项目实战第十六记 写在前面1 第一个bug1.1 完整的Role.vue 2 第二个bug2.1 修改路由router下面的index.js 总结写在最后 写在前面 发现bug&#xff0c;修复bug 1 第一个bug 分配菜单时未加入父id&#xff0c;导致分配菜单失效 <!-- :check-strictly"true" 默…

图的应用之最小生成树

大纲 生成树介绍 特点 但n个 种类 最小生成树 应用 构造算法 MST性质 Prim算法 依次选择与顶点相邻的不会构成回路的最小边对应的顶点 Kruskal算法 依次选不会构成环的最小边 区别 Prim算法有n个顶点进行选择&#xff0c;每个顶点有n个选择&#xff0c;复杂度为O(n*n) K…

C51学习归纳13 --- AD/DA转换

AD/DA转换实现了计算机和模拟信号的连接&#xff0c;扩展了计算机的应用场景&#xff0c;为模拟信号数字化提供了底层支持。 AD转换通常是多个输入通道&#xff0c;使用多路选择器连接到AD开关&#xff0c;实现AD多路复用的目的&#xff0c;提高利用率。 AD/DA转换可以使用串口…

我的创作纪念日(1825天)

Ⅰ、机缘 1. 记得是大一、大二的时候就听学校的大牛说&#xff0c;可以通过写 CSDN 博客&#xff0c;来提升自己的代码和逻辑能力&#xff0c;虽然即将到了写作的第六个年头&#xff0c;但感觉这句话依旧受用; 2、今年一整年的创作都没有停止&#xff0c;本年度几乎是每周都来…

UniApp或微信小程序中scroll-view组件使用show-scrollbar在真机Android或IOS中隐藏不了滚动条的解决办法

show-scrollbar 属性 不论是使用 变量 还是直接使用 布尔值或者直接使用 css 都是在 ios、Android 上是都没有效果。。 真机中还是出现滚动条 解决办法 添加下面CSS ::-webkit-scrollbar {display: none;width: 0 !important;height: 0 !important;-webkit-appearance: no…

盛世古董乱世金-数据库稳定到底好不好?

是不是觉得这个还用问&#xff1f; 是的要问。因为这个还是一个有争议的问题。但是争议双方都没有错。这就像辩论&#xff0c;有正反双方。大家都说的有道理&#xff0c;但是很难说谁对谁错。 正方观点&#xff1a;数据库稳定好 其实这个是用户的观点&#xff0c;应用开发人…

17个关键方法指南,保护您的web站点安全!

了解如何让您的web应用程序或网站安全&#xff0c;对于网站所有者来说至关重要。以下是一些关键步骤&#xff0c;可以帮助您保护网站免受攻击和数据泄露。 1.使用公钥加密技术 当数据以明文形式传输时&#xff0c;它容易受到中间人 &#xff08;MitM&#xff09; 攻击。这意味…