商品服务
三级分类
查询树形展示三级分类数据
1.在PMS库中的pms_category表,导入菜单分类数据
2.然后在product模块中,对应的service文件中编写三级分类业务逻辑
public List<CategoryEntity> menu() {
List<CategoryEntity> allList = baseMapper.selectList(null);
// 找到所有的一级分类
List<CategoryEntity> level1Nenus = allList.stream().filter(categoryEntity -> categoryEntity.getParentCid() == 0
).map((item) -> {
item.setChildren(getChildren(item, allList));
return item;
}).sorted((item1, item2) -> {
return (item1.getSort() == null ? 0 : item1.getSort()) - (item2.getSort() == null ? 0 : item2.getSort());
}).collect(Collectors.toList());
return level1Nenus;
}
private List<CategoryEntity> getChildren(CategoryEntity currentItem, List<CategoryEntity> allList) {
List<CategoryEntity> collect = allList.stream().filter((item) -> {
return item.getParentCid().equals(currentItem.getCatId());
}).map((item) -> {
item.setChildren(getChildren(item, allList));
return item;
}).sorted((item1, item2) -> {
return (item1.getSort() == null ? 0 : item1.getSort()) - (item2.getSort() == null ? 0 : item2.getSort());
}).collect(Collectors.toList());
return collect;
}
配置网关路由与路径重写和网关统一配置跨域
1.启动前端代码,再启动后端服务,进入系统的系统管理->菜单管理,添加商品系统的一级目录,然后再添加这个一级目录的分类维护的子目录,path为product/category。
2.然后根据这个path的属性,在前端src/views/modules 下创建一个product文件夹,然后在这个文件夹下创建category.vue 文件,文件内容如下:
<template>
<el-tree :data="data" :props="defaultProps" @node-click="handleNodeClick"></el-tree>
</template>
<script>
export default {
data() {
return {
data: [{
label: '一级 1',
children: [{
label: '二级 1-1',
children: [{
label: '三级 1-1-1'
}]
}]
}, {
label: '一级 2',
children: [{
label: '二级 2-1',
children: [{
label: '三级 2-1-1'
}]
}, {
label: '二级 2-2',
children: [{
label: '三级 2-2-1'
}]
}]
}, {
label: '一级 3',
children: [{
label: '二级 3-1',
children: [{
label: '三级 3-1-1'
}]
}, {
label: '二级 3-2',
children: [{
label: '三级 3-2-1'
}]
}]
}],
defaultProps: {
children: 'children',
label: 'label'
}
};
},
methods: {
handleNodeClick(data) {
console.log(data);
},
getMenu(){
this.$http({
url: this.$http.adornUrl('/product/category/menu'),
method: 'get'
}).then(({data}) => {
console.log('菜单'+data)
})
}
},
activated () {
this.getMenu()
},
}
</script>
<style>
</style>
3.配置好请求,打开页面,发现请求404,我们要配置项目父目录下static->config的index.js 文件中的api请求接口路径,把所有服务请求都提交到网关服务去,修改如下:
/**
* 开发环境
*/
;(function () {
window.SITE_CONFIG = {};
// api接口请求地址
window.SITE_CONFIG['baseUrl'] = 'http://localhost:88/api';
// cdn地址 = 域名 + 版本号
window.SITE_CONFIG['domain'] = './'; // 域名
window.SITE_CONFIG['version'] = ''; // 版本号(年月日时分)
window.SITE_CONFIG['cdnUrl'] = window.SITE_CONFIG.domain + window.SITE_CONFIG.version;
})();
4.配置好之后,还需要配置网关模块的配置文件,修改如下:
spring:
cloud:
gateway:
routes:
- id: product_route
uri: lb://supermall-product
predicates:
- Path=/api/product/**
filters:
- RewritePath=/api/(?<segment>.*),/$\{segment}
- id: admin_route
uri: lb://renren-fast
predicates:
- Path=/api/**
filters:
- RewritePath=/api/(?<segment>.*),/renren-fast/$\{segment}
5.再把后端管理项目加入到nacos 服务发现中,配置nacos服务发现,重启网关服务。
6.但是我们发现,请求给了403,看控制台报错是没有配置跨域策略,所以我们必须在网关配置跨域策略,加入以下文件
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 SupermallCorsConfiguration {
@Bean
public CorsWebFilter corsWebFilter(){
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
CorsConfiguration corsConfiguration = new CorsConfiguration();
//1、配置跨域
corsConfiguration.addAllowedHeader("*");
corsConfiguration.addAllowedMethod("*");
corsConfiguration.addAllowedOrigin("*");
// 允许携带cookie
corsConfiguration.setAllowCredentials(true);
source.registerCorsConfiguration("/**",corsConfiguration);
return new CorsWebFilter(source);
}
}
7.重新请求,发现还是403,看了错误显示跨域配置了多个值,导致的报错,经过查找发现,后台管理服务中也增加了跨域策略,所以我们需要删除它,在io.renren.config目录下的CorsConfig 。
树型展示三级分类数据
1.修改category.vue文件,如下
<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);
},
getMenu(){
this.$http({
url: this.$http.adornUrl('/product/category/menu'),
method: 'get'
}).then(({data}) => {
this.menus = data.data
console.log('菜单=========',data.data)
})
}
},
activated () {
this.getMenu()
},
}
</script>
<style>
</style>
页面效果
修改过的页面内容如下:
<template>
<el-tree
:data="menus"
:props="defaultProps"
:expand-on-click-node="false"
default-expand-all
show-checkbox
node-key="catId"
>
<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>
</span></el-tree
>
</template>
<script>
export default {
data() {
return {
menus: [],
defaultProps: {
children: "children",
label: "name"
}
};
},
methods: {
append(data) {
console.log("append", data);
},
remove(node, data) {
console.log("remove", node, data);
},
getMenu() {
this.$http({
url: this.$http.adornUrl("/product/category/menu"),
method: "get"
}).then(({ data }) => {
this.menus = data.data;
console.log("菜单=========", data.data);
});
}
},
activated() {
this.getMenu();
}
};
</script>
逻辑删除
以下为官方操作文档路径
https://baomidou.com/guide/logic-delete.html#%E4%BD%BF%E7%94%A8%E6%96%B9%E6%B3%95%EF%BC%9A
1.使用mybatis-plus插件集成的便利性,修改application.yml,加上如下配置
mybatis-plus:
#mapper 的路径 ,classpath* 全路径扫描,即java的类路径下;classpath 即 resource 路径下
mapper-locations: classpath:/mapper/**/*.xml
#配置主键使用规则
global-config:
db-config:
#主键自增
id-type: auto
logic-delete-value: 1
logic-not-delete-value: 0
2.修改DO实体类,把逻辑删除的字段加上@TableLogic 注解
3.然后使用postman 调用请求接口,为了看到逻辑删除执行的语句,我们需要修改日志框架的日志级别为debug级别,配置文件加入以下配置
logging:
level:
com.centersept.supermall: debug
删除效果细化
category.vue 修改之后的内容为:
<template>
<el-tree
:data="menus"
:props="defaultProps"
:expand-on-click-node="false"
show-checkbox
node-key="catId"
:default-expanded-keys="expandedKey"
>
<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>
</span></el-tree
>
</template>
<script>
export default {
data() {
return {
menus: [],
expandedKey: [],
defaultProps: {
children: "children",
label: "name"
}
};
},
methods: {
append(data) {
console.log("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.expandedKey = [node.parent.data.catId];
});
})
.catch(() => {});
console.log("remove", node, data);
},
getMenus() {
this.$http({
url: this.$http.adornUrl("/product/category/menu"),
method: "get"
}).then(({ data }) => {
this.menus = data.data;
console.log("菜单=========", data.data);
});
}
},
activated() {
this.getMenus();
}
};
</script>
新增效果完成
category.vue 修改之后的内容为:
<template>
<div>
<el-tree
:data="menus"
:props="defaultProps"
:expand-on-click-node="false"
show-checkbox
node-key="catId"
:default-expanded-keys="expandedKey"
>
<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>
</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>
<span slot="footer" class="dialog-footer">
<el-button @click="dialogVisible = false">取 消</el-button>
<el-button type="primary" @click="addCategory">确 定</el-button>
</span>
</el-dialog>
</div>
</template>
<script>
export default {
data() {
return {
category: {
name: "",
parentCid: 0,
catLevel: 0,
showStatus: 1,
sort: 0,
productUnit: "",
icon: "",
catId: null
},
dialogVisible: false,
menus: [],
expandedKey: [],
defaultProps: {
children: "children",
label: "name"
}
};
},
methods: {
append(data) {
console.log("append", data);
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;
},
//添加三级分类
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);
},
getMenus() {
this.$http({
url: this.$http.adornUrl("/product/category/menu"),
method: "get"
}).then(({ data }) => {
this.menus = data.data;
console.log("菜单=========", data.data);
});
}
},
activated() {
this.getMenus();
}
};
</script>
基本修改效果完成
category.vue 修改之后的内容为:
<template>
<div>
<el-tree
:data="menus"
:props="defaultProps"
:expand-on-click-node="false"
show-checkbox
node-key="catId"
:default-expanded-keys="expandedKey"
>
<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 type="text" size="mini" @click="edit(data)"
>edit</el-button
>
<el-button
v-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>
export default {
data() {
return {
title: "",
dialogType: "", //edit,add
category: {
name: "",
parentCid: 0,
catLevel: 0,
showStatus: 1,
sort: 0,
productUnit: "",
icon: "",
catId: null
},
dialogVisible: false,
menus: [],
expandedKey: [],
defaultProps: {
children: "children",
label: "name"
}
};
},
methods: {
submitData() {
if (this.dialogType == "add") {
this.addCategory();
}
if (this.dialogType == "edit") {
this.editCategory();
}
},
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;
});
},
//修改三级分类数据
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];
});
},
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;
},
//添加三级分类
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);
},
getMenus() {
this.$http({
url: this.$http.adornUrl("/product/category/menu"),
method: "get"
}).then(({ data }) => {
this.menus = data.data;
console.log("菜单=========", data.data);
});
}
},
activated() {
this.getMenus();
}
};
</script>
拖拽效果
<template>
<div>
<el-tree
:data="menus"
:props="defaultProps"
:expand-on-click-node="false"
show-checkbox
:draggable="draggable"
:allow-drop="allowDrop"
node-key="catId"
:default-expanded-keys="expandedKey"
>
<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 type="text" size="mini" @click="edit(data)"
>edit</el-button
>
<el-button
v-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>
export default {
data() {
return {
draggable: true,
maxLevel: 0,
title: "",
dialogType: "", //edit,add
category: {
name: "",
parentCid: 0,
catLevel: 0,
showStatus: 1,
sort: 0,
productUnit: "",
icon: "",
catId: null
},
dialogVisible: false,
menus: [],
expandedKey: [],
defaultProps: {
children: "children",
label: "name"
}
};
},
methods: {
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.maxLevel
if (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]);
}
}
},
submitData() {
if (this.dialogType == "add") {
this.addCategory();
}
if (this.dialogType == "edit") {
this.editCategory();
}
},
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;
});
},
//修改三级分类数据
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];
});
},
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;
},
//添加三级分类
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);
},
getMenus() {
this.$http({
url: this.$http.adornUrl("/product/category/menu"),
method: "get"
}).then(({ data }) => {
this.menus = data.data;
console.log("菜单=========", data.data);
});
}
},
activated() {
this.getMenus();
}
};
</script>
拖拽数据收集
<template>
<div>
<el-tree
:data="menus"
:props="defaultProps"
:expand-on-click-node="false"
show-checkbox
node-key="catId"
:default-expanded-keys="expandedKey"
:draggable="draggable"
:allow-drop="allowDrop"
@node-drop="handleDrop"
>
<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 type="text" size="mini" @click="edit(data)"
>edit</el-button
>
<el-button
v-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>
export default {
data() {
return {
updateNodes: [],
draggable: true,
maxLevel: 0,
title: "",
dialogType: "", //edit,add
category: {
name: "",
parentCid: 0,
catLevel: 0,
showStatus: 1,
sort: 0,
productUnit: "",
icon: "",
catId: null
},
dialogVisible: false,
menus: [],
expandedKey: [],
defaultProps: {
children: "children",
label: "name"
}
};
},
methods: {
// 菜单拖拽 start =======================================================
handleDrop(draggingNode, dropNode, dropType, ev) {
console.log("handleDrop: ", draggingNode, dropNode, dropType);
//1、当前节点最新的父节点id
let 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.maxLevel
if (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]);
}
}
},
// 菜单拖拽 end =======================================================
submitData() {
if (this.dialogType == "add") {
this.addCategory();
}
if (this.dialogType == "edit") {
this.editCategory();
}
},
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;
});
},
//修改三级分类数据
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];
});
},
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;
},
//添加三级分类
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);
},
getMenus() {
this.$http({
url: this.$http.adornUrl("/product/category/menu"),
method: "get"
}).then(({ data }) => {
this.menus = data.data;
console.log("菜单=========", data.data);
});
}
},
activated() {
this.getMenus();
}
};
</script>
拖拽功能完成
1.后端代码增加接口 CategoryController
/**
* 批量更新菜单排序
*/
@RequestMapping("/update/sort")
public R updateSort(@RequestBody CategoryEntity[] category) {
categoryService.updateBatchById(Arrays.asList(category));
return R.ok();
}
2.单行修改频繁调用不够友好,需要使用按钮进行批量修改。单行提交代码省略。
最终菜单页面完成代码效果
<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-checkbox
node-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-button
v-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-button
v-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>
export default {
data() {
return {
pCid: [],
updateNodes: [],
draggable: false,
maxLevel: 0,
title: "",
dialogType: "", //edit,add
category: {
name: "",
parentCid: 0,
catLevel: 0,
showStatus: 1,
sort: 0,
productUnit: "",
icon: "",
catId: null
},
dialogVisible: false,
menus: [],
expandedKey: [],
defaultProps: {
children: "children",
label: "name"
}
};
},
methods: {
// 菜单批量处理 start =======================================================
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;
});
},
// 菜单批量处理 end =======================================================
// 菜单拖拽 start =======================================================
handleDrop(draggingNode, dropNode, dropType, ev) {
console.log("handleDrop: ", draggingNode, dropNode, dropType);
//1、当前节点最新的父节点id
let 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.maxLevel
if (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]);
}
}
},
// 菜单拖拽 end =======================================================
submitData() {
if (this.dialogType == "add") {
this.addCategory();
}
if (this.dialogType == "edit") {
this.editCategory();
}
},
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;
});
},
//修改三级分类数据
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];
});
},
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;
},
//添加三级分类
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);
},
// 查询菜单
getMenus() {
this.$http({
url: this.$http.adornUrl("/product/category/menu"),
method: "get"
}).then(({ data }) => {
this.menus = data.data;
console.log("菜单=========", data.data);
});
}
},
activated() {
this.getMenus();
}
};
</script>
品牌管理
使用逆向工程的前后端代码
1.在菜单管理中在商品服务下生成一个品牌管理的菜单,菜单路径为product/brand
2.使用逆向生成代码的两个文件 brand.vue 和 brand-add-or-update.vue 文件放到product 文件夹下。
3.由于做了权限设置,所以进入/utils/index.js 文件夹下修改 isAuth 方法的返回值一直为true。
效果优化与快速显示开关
关闭ESLint 语法检查
打开build文件夹下的webpack.base.conf.js,然后注释调createLintingRule中的方法体。
1.优化brand.vue 之后的代码
<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-button
v-if="isAuth('product:brand:save')"
type="primary"
@click="addOrUpdateHandle()"
>新增</el-button
>
<el-button
v-if="isAuth('product:brand:delete')"
type="danger"
@click="deleteHandle()"
:disabled="dataListSelections.length <= 0"
>批量删除</el-button
>
</el-form-item>
</el-form>
<el-table
:data="dataList"
border
v-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地址"
>
</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-switch
v-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="150"
label="操作"
>
<template slot-scope="scope">
<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>
</div>
</template>
<script>
import AddOrUpdate from "./brand-add-or-update";
export default {
data() {
return {
dataForm: {
key: ""
},
dataList: [],
pageIndex: 1,
pageSize: 10,
totalPage: 0,
dataListLoading: false,
dataListSelections: [],
addOrUpdateVisible: false
};
},
components: {
AddOrUpdate
},
activated() {
this.getDataList();
},
methods: {
// 修改显示状态
updateBrandStatus(data) {
console.log("最新信息", data);
let { brandId, showStatus } = data;
//发送请求修改状态
this.$http({
url: this.$http.adornUrl("/product/brand/update"),
method: "post",
data: this.$http.adornData({ brandId, showStatus }, false)
}).then(({ data }) => {
this.$message({
type: "success",
message: "状态更新成功"
});
});
},
// 获取数据列表
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;
});
},
// 每页数
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>
brand-add-or-update.vue 内容
<template>
<el-dialog
:title="!dataForm.id ? '新增' : '修改'"
:close-on-click-modal="false"
:visible.sync="visible">
<el-form :model="dataForm" :rules="dataRule" ref="dataForm" @keyup.enter.native="dataFormSubmit()" label-width="140px">
<el-form-item label="品牌名" prop="name">
<el-input v-model="dataForm.name" placeholder="品牌名"></el-input>
</el-form-item>
<el-form-item label="品牌logo地址" prop="logo">
<el-input v-model="dataForm.logo" placeholder="品牌logo地址"></el-input>
</el-form-item>
<el-form-item label="介绍" prop="descript">
<el-input v-model="dataForm.descript" placeholder="介绍"></el-input>
</el-form-item>
<el-form-item label="显示状态" prop="showStatus">
<el-switch
v-model="dataForm.showStatus"
active-color="#13ce66"
inactive-color="#ff4949"
:active-value="1"
:inactive-value="0"
></el-switch>
</el-form-item>
<el-form-item label="检索首字母" prop="firstLetter">
<el-input v-model="dataForm.firstLetter" placeholder="检索首字母"></el-input>
</el-form-item>
<el-form-item label="排序" prop="sort">
<el-input v-model="dataForm.sort" placeholder="排序"></el-input>
</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>
export default {
data () {
return {
visible: false,
dataForm: {
brandId: 0,
name: '',
logo: '',
descript: '',
showStatus: '',
firstLetter: '',
sort: ''
},
dataRule: {
name: [
{ required: true, message: '品牌名不能为空', trigger: 'blur' }
],
logo: [
{ required: true, message: '品牌logo地址不能为空', trigger: 'blur' }
],
descript: [
{ required: true, message: '介绍不能为空', trigger: 'blur' }
],
showStatus: [
{ required: true, message: '显示状态[0-不显示;1-显示]不能为空', trigger: 'blur' }
],
firstLetter: [
{ required: true, message: '检索首字母不能为空', trigger: 'blur' }
],
sort: [
{ required: true, message: '排序不能为空', trigger: 'blur' }
]
}
}
},
methods: {
init (id) {
this.dataForm.brandId = id || 0
this.visible = true
this.$nextTick(() => {
this.$refs['dataForm'].resetFields()
if (this.dataForm.brandId) {
this.$http({
url: this.$http.adornUrl(`/product/brand/info/${this.dataForm.brandId}`),
method: 'get',
params: this.$http.adornParams()
}).then(({data}) => {
if (data && data.code === 0) {
this.dataForm.name = data.brand.name
this.dataForm.logo = data.brand.logo
this.dataForm.descript = data.brand.descript
this.dataForm.showStatus = data.brand.showStatus
this.dataForm.firstLetter = data.brand.firstLetter
this.dataForm.sort = data.brand.sort
}
})
}
})
},
// 表单提交
dataFormSubmit () {
this.$refs['dataForm'].validate((valid) => {
if (valid) {
this.$http({
url: this.$http.adornUrl(`/product/brand/${!this.dataForm.brandId ? 'save' : 'update'}`),
method: 'post',
data: this.$http.adornData({
'brandId': this.dataForm.brandId || undefined,
'name': this.dataForm.name,
'logo': this.dataForm.logo,
'descript': this.dataForm.descript,
'showStatus': this.dataForm.showStatus,
'firstLetter': this.dataForm.firstLetter,
'sort': this.dataForm.sort
})
}).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)
}
})
}
})
}
}
}
</script>
云存储开通与使用
开通阿里云OSS
OSS 整合测试
普通Maven项目引入
1.在阿里云平台开通RAM服务,然后在RAM 服务下创建子账号,然后保存生成的AccessKey ID 和 AccessKey Secret 。
2.查看OSS官方文档,引入下面的maven依赖,根据文档示例写一个简单的demo 测试。
<dependency>
<groupId>com.aliyun.oss</groupId>
<artifactId>aliyun-sdk-oss</artifactId>
<version>3.10.2</version>
</dependency>
SpringCloud Alibaba OSS 引入
1.打开github 上的 demo示例,https://github.com/alibaba/aliyun-spring-boot/tree/master/aliyun-spring-boot-samples/aliyun-oss-spring-boot-sample.
2.加入以下以下maven依赖
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>aliyun-oss-spring-boot-starter</artifactId>
</dependency>
3.然后在配置文件文件中加入以下配置
// application.properties
alibaba.cloud.access-key=your-ak
alibaba.cloud.secret-key=your-sk
alibaba.cloud.oss.endpoint=***
4.然后在业务在业务项目中使用以下代码进行OSS客户端注入访问
@Service
public class YourService {
@Autowired
private OSSClient ossClient;
public void saveFile() {
// download file to local
ossClient.getObject(new GetObjectRequest(bucketName, objectName), new File("pathOfYourLocalFile"));
}
}
5.弄一个测试demo
import com.aliyun.oss.OSSClient;
import com.centersept.supermall.product.service.BrandService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.InputStream;
@RunWith(SpringRunner.class)
@SpringBootTest
public class SuperMallProductApplicationTests {
@Autowired
OSSClient ossClient;
@Test
public void test() throws FileNotFoundException {
InputStream inputStream = new FileInputStream("C:\\Users\\qkz-admin\\Pictures" +
"\\ba4957ffgy1g89ginl2cwj20dw0dw75d.jpg");
ossClient.putObject("center-test", "ba4957ffgy1g89ginl2cwj20dw0dw75d.jpg", inputStream);
ossClient.shutdown();
System.out.println("上传完成");
}
}
OSS 获取服务端签名
1.建立一个新的模块,该模块是引入第三方服务的模块,命名super-mall-TTS
2.maven的pom加入以下内容
注:依赖的springboot的版本号一定要一致。
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.8.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.centersept.supermall</groupId>
<artifactId>super-mall-tts</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>super-mall-tts</name>
<description>第三方服务模块</description>
<properties>
<java.version>1.8</java.version>
<spring-cloud.version>Greenwich.SR3</spring-cloud.version>
</properties>
<dependencies>
<dependency>
<groupId>com.centersept.supermall</groupId>
<artifactId>super-mall-common</artifactId>
<version>0.0.1-SNAPSHOT</version>
<exclusions>
<exclusion>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<!--引入阿里云封装好的cloud oss-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alicloud-oss</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>2.1.0.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
3.创建bootstrap.properties 文件加入nacos config 的配置。
4.创建一个生成签名的contorller
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.centersept.supermall.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;
/**
* @author Center_Sept
* @date 2020/11/19 15:51
*/
@RestController
public class OssController {
@Autowired
private OSS ossClient;
/**
* 端点
*/
@Value("${spring.cloud.alicloud.oss.endpoint}")
private String endpoint;
/**
* bucket
*/
@Value("${spring.cloud.alicloud.oss.bucket}")
private String bucket;
/**
* access-key
*/
@Value("${spring.cloud.alicloud.access-key}")
private String accessId;
/**
* 功能描述:获得上传policy和签名
*/
@RequestMapping("/oss/policy")
public R policy() {
String host = "https://" + bucket + "." + endpoint; // host的格式为 https:// + 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;
// 30 s 超时
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>();
// access-key
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);
}
}
OSS 前后联调测试上传
1.引入以下三个文件,放在src/components/upload 文件夹下。
多文件上传组件 multiUpload.vue
<template>
<div>
<el-upload
action="http://center-test.oss-cn-beijing.aliyuncs.com"
:data="dataObj"
list-type="picture-card"
:file-list="fileList"
:before-upload="beforeUpload"
:on-remove="handleRemove"
:on-success="handleUploadSuccess"
:on-preview="handlePreview"
:limit="maxCount"
:on-exceed="handleExceed"
>
<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
}
},
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>
请求OSS签名封装脚本 policy.js
import http from '@/utils/httpRequest.js'
export function policy() {
return new Promise((resolve,reject)=>{
http({
url: http.adornUrl("/tts/oss/policy"),
method: "get",
params: http.adornParams({})
}).then(({ data }) => {
resolve(data);
})
});
}
单文件上传组件 singleUpload.vue
<template>
<div>
<el-upload
action="http://center-test.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 => {
_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 => {
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>
2.修改brand-add-or-update.vue
<template>
<el-dialog
:title="!dataForm.id ? '新增' : '修改'"
:close-on-click-modal="false"
:visible.sync="visible">
<el-form :model="dataForm" :rules="dataRule" ref="dataForm" @keyup.enter.native="dataFormSubmit()" label-width="140px">
<el-form-item label="品牌名" prop="name">
<el-input v-model="dataForm.name" placeholder="品牌名"></el-input>
</el-form-item>
<el-form-item label="品牌logo地址" prop="logo">
<single-upload v-model="dataForm.logo"></single-upload>
</el-form-item>
<el-form-item label="介绍" prop="descript">
<el-input v-model="dataForm.descript" placeholder="介绍"></el-input>
</el-form-item>
<el-form-item label="显示状态" prop="showStatus">
<el-switch
v-model="dataForm.showStatus"
active-color="#13ce66"
inactive-color="#ff4949"
:active-value="1"
:inactive-value="0"
></el-switch>
</el-form-item>
<el-form-item label="检索首字母" prop="firstLetter">
<el-input v-model="dataForm.firstLetter" placeholder="检索首字母"></el-input>
</el-form-item>
<el-form-item label="排序" prop="sort">
<el-input v-model="dataForm.sort" placeholder="排序"></el-input>
</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 SingleUpload from "@/components/upload/singleUpload";
export default {
components: { SingleUpload },
data () {
return {
visible: false,
dataForm: {
brandId: 0,
name: '',
logo: '',
descript: '',
showStatus: '',
firstLetter: '',
sort: ''
},
dataRule: {
name: [
{ required: true, message: '品牌名不能为空', trigger: 'blur' }
],
logo: [
{ required: true, message: '品牌logo地址不能为空', trigger: 'blur' }
],
descript: [
{ required: true, message: '介绍不能为空', trigger: 'blur' }
],
showStatus: [
{ required: true, message: '显示状态[0-不显示;1-显示]不能为空', trigger: 'blur' }
],
firstLetter: [
{ required: true, message: '检索首字母不能为空', trigger: 'blur' }
],
sort: [
{ required: true, message: '排序不能为空', trigger: 'blur' }
]
}
}
},
methods: {
init (id) {
this.dataForm.brandId = id || 0
this.visible = true
this.$nextTick(() => {
this.$refs['dataForm'].resetFields()
if (this.dataForm.brandId) {
this.$http({
url: this.$http.adornUrl(`/product/brand/info/${this.dataForm.brandId}`),
method: 'get',
params: this.$http.adornParams()
}).then(({data}) => {
if (data && data.code === 0) {
this.dataForm.name = data.brand.name
this.dataForm.logo = data.brand.logo
this.dataForm.descript = data.brand.descript
this.dataForm.showStatus = data.brand.showStatus
this.dataForm.firstLetter = data.brand.firstLetter
this.dataForm.sort = data.brand.sort
}
})
}
})
},
// 表单提交
dataFormSubmit () {
this.$refs['dataForm'].validate((valid) => {
if (valid) {
this.$http({
url: this.$http.adornUrl(`/product/brand/${!this.dataForm.brandId ? 'save' : 'update'}`),
method: 'post',
data: this.$http.adornData({
'brandId': this.dataForm.brandId || undefined,
'name': this.dataForm.name,
'logo': this.dataForm.logo,
'descript': this.dataForm.descript,
'showStatus': this.dataForm.showStatus,
'firstLetter': this.dataForm.firstLetter,
'sort': this.dataForm.sort
})
}).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)
}
})
}
})
}
}
}
</script>
3.上传操作之后,点击保存,保存上传的路径
表单校验&自定义校验器
1.修改显示状态,返回后端的值,修改如下
<el-form-item label="显示状态" prop="showStatus">
<el-switch
v-model="dataForm.showStatus"
active-color="#13ce66"
inactive-color="#ff4949"
:active-value="1"
:inactive-value="0"
></el-switch>
</el-form-item>
2.把表格显示的URL地址,转变为显示图片
<el-table-column
prop="logo"
header-align="center"
align="center"
label="品牌logo地址"
>
<template slot-scope="scope">
<!-- <el-image v-if="scope.row.logo != null && scope.row.logo != ''"
style="width: 100px; height: 80px"
:src="scope.row.logo"
fit="fill"></el-image> -->
<img v-if="scope.row.logo != null && scope.row.logo != ''" :src="scope.row.logo" style="width: 100px; height: 80px" />
</template>
</el-table-column>
3.加入表单验证,修改brand-add-or-upadate.vue,内容如下:
<template>
<el-dialog
:title="!dataForm.id ? '新增' : '修改'"
:close-on-click-modal="false"
:visible.sync="visible"
>
<el-form
:model="dataForm"
:rules="dataRule"
ref="dataForm"
@keyup.enter.native="dataFormSubmit()"
label-width="140px"
>
<el-form-item label="品牌名" prop="name">
<el-input v-model="dataForm.name" placeholder="品牌名"></el-input>
</el-form-item>
<el-form-item label="品牌logo地址" prop="logo">
<single-upload v-model="dataForm.logo"></single-upload>
</el-form-item>
<el-form-item label="介绍" prop="descript">
<el-input v-model="dataForm.descript" placeholder="介绍"></el-input>
</el-form-item>
<el-form-item label="显示状态" prop="showStatus">
<el-switch
v-model="dataForm.showStatus"
active-color="#13ce66"
inactive-color="#ff4949"
:active-value="1"
:inactive-value="0"
></el-switch>
</el-form-item>
<el-form-item label="检索首字母" prop="firstLetter">
<el-input
v-model="dataForm.firstLetter"
placeholder="检索首字母"
></el-input>
</el-form-item>
<el-form-item label="排序" prop="sort">
<el-input v-model="dataForm.sort" placeholder="排序"></el-input>
</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 SingleUpload from "@/components/upload/singleUpload";
export default {
components: { SingleUpload },
data() {
return {
visible: false,
dataForm: {
brandId: 0,
name: "",
logo: "",
descript: "",
showStatus: 1,
firstLetter: "",
sort: 0
},
dataRule: {
name: [{ required: true, message: "品牌名不能为空", trigger: "blur" }],
logo: [
{ required: true, message: "品牌logo地址不能为空", trigger: "blur" }
],
descript: [
{ required: true, message: "介绍不能为空", trigger: "blur" }
],
showStatus: [
{
required: true,
message: "显示状态[0-不显示;1-显示]不能为空",
trigger: "blur"
}
],
firstLetter: [
{
validator: (rule, value, callback) => {
if (value == "") {
callback(new Error("首字母必须填写"));
} else if (!/^[a-zA-Z]$/.test(value)) {
callback(new Error("首字母必须a-z或者A-Z之间"));
} else {
callback();
}
},
trigger: "blur"
}
],
sort: [{
validator: (rule, value, callback) => {
if (value == "") {
callback(new Error("排序字段必须填写"));
} else if (!Number.isInteger(value) || value<0) {
callback(new Error("排序必须是一个大于等于0的整数"));
} else {
callback();
}
},
trigger: "blur"
}]
}
};
},
methods: {
init(id) {
this.dataForm.brandId = id || 0;
this.visible = true;
this.$nextTick(() => {
this.$refs["dataForm"].resetFields();
if (this.dataForm.brandId) {
this.$http({
url: this.$http.adornUrl(
`/product/brand/info/${this.dataForm.brandId}`
),
method: "get",
params: this.$http.adornParams()
}).then(({ data }) => {
if (data && data.code === 0) {
this.dataForm.name = data.brand.name;
this.dataForm.logo = data.brand.logo;
this.dataForm.descript = data.brand.descript;
this.dataForm.showStatus = data.brand.showStatus;
this.dataForm.firstLetter = data.brand.firstLetter;
this.dataForm.sort = data.brand.sort;
}
});
}
});
},
// 表单提交
dataFormSubmit() {
this.$refs["dataForm"].validate(valid => {
if (valid) {
this.$http({
url: this.$http.adornUrl(
`/product/brand/${!this.dataForm.brandId ? "save" : "update"}`
),
method: "post",
data: this.$http.adornData({
brandId: this.dataForm.brandId || undefined,
name: this.dataForm.name,
logo: this.dataForm.logo,
descript: this.dataForm.descript,
showStatus: this.dataForm.showStatus,
firstLetter: this.dataForm.firstLetter,
sort: this.dataForm.sort
})
}).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);
}
});
}
});
}
}
};
</script>
JSR303表单验证
1.给Bean实体类添加校验注解(在javax.validation.constraints 中),并定义自己的message提示,举个栗子,在BrandEntity.java 中修改。
@Data
@TableName("pms_brand")
public class BrandEntity implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 品牌id
*/
@NotNull(message = "修改必须指定品牌id")
@Null(message = "新增不能指定id")
@TableId
private Long brandId;
/**
* 品牌名
*/
@NotBlank(message = "品牌名必须提交")
private String name;
/**
* 品牌logo地址
*/
@URL(message = "logo必须是一个合法的url地址")
private String logo;
/**
* 介绍
*/
private String descript;
/**
* 显示状态[0-不显示;1-显示]
*/
private Integer showStatus;
/**
* 检索首字母
*/
@Pattern(regexp = "^[a-zA-Z]$", message = "检索首字母必须是一个字母")
private String firstLetter;
/**
* 排序
*/
@Min(value = 0, message = "排序必须大于等于0")
private Integer sort;
}
2.在对应的controller的保存接口上加上@Valid注解开启校验,但是返回的是默认的响应,不是我们想要的。
/**
* 保存
*/
@RequestMapping("/save")
public R save(@Valid @RequestBody BrandEntity brand){
brandService.save(brand);
return R.ok();
}
3.自定义校验体,需要在入参中加入BindingResult类型的参数,就可以获取到校验的结果,自定义返回给前端。
/**
* 保存
*/
@RequestMapping("/save")
public R save(@Valid @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();
}
4.也可以使用BindingResultUtils 提供的方法,对入参进行校验。
统一异常处理
1.为了不增加代码的侵入性,我们可以自定义一个异常类,基于AOP做统一处理。
2.在对应的模块下,创建一个exception包,声明一个统一的异常类型,然后加上@RestControllerAdvice(basePackages = “com.atguigu.gulimall.product.controller”)类注解,方法上加入@ExceptionHandler(value = MethodArgumentNotValidException.class)对应处理的异常注解,然后在公共模块定义统一的异常代码枚举类。
统一异常类:
@Slf4j
//@ResponseBody
//@ControllerAdvice(basePackages = "com.atguigu.gulimall.product.controller")
@RestControllerAdvice(basePackages = "com.atguigu.gulimall.product.controller")
public class SupermallExceptionControllerAdvice {
@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());
}
}
异常代码枚举:
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;
}
}
3.最后把controller侵入业务的代码删除调。
JSR303分组校验
1.定义校验分组接口,接口不需要实现类,仅仅是使用来标识分组。分别定义addGroup.java 和 upGroup.java分组,放到common模块下,放到valid的包下。
2.在bean实体类的字段上的校验注解中加入group属性,使用刚刚定义好的接口类型根据需求填充到group中。如,新增接口应该使用addGroup.class类标识,更新接口使用upGroup.class 标识。举了栗子:
/**
* 品牌id
*/
@NotNull(message = "修改必须指定品牌id",groups = {UpdateGroup.class})
@Null(message = "新增不能指定id",groups = {AddGroup.class})
@TableId
private Long brandId;
3.在对应的controller类上,加上@Validated 注解,开启校验功能,如下:
/**
* 保存
*/
@RequestMapping("/save")
public R save(@Validated({AddGroup.class}) @RequestBody BrandEntity brand) {
brandService.save(brand);
return R.ok();
}
注:默认没有指定分组的校验注解@NutBlank,在分组校验情况@Validated({AddGroup.class})下不生效。
JSR303自定义校验注解
1.在common模块引入javax.validation.validation-api maven依赖:
<dependency>
<groupId>javax.validation</groupId>
<artifactId>validation-api</artifactId>
<version>2.0.1.Final</version>
</dependency>
2.编写一个自定义的校验注解,在common模块中,valid包下新建一个注解类
@Documented
@Constraint(validatedBy = { ListValueConstraintValidator.class })
@Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE })
@Retention(RUNTIME)
public @interface ListValue {
String message() default "{com.centersept.supermall.common.valid.ListValue.message}";
Class<?>[] groups() default { };
Class<? extends Payload>[] payload() default { };
int[] vals() default { };
}
3.新建一个ListValueConstraintValidator校验器,该校验器需要实现ConstraintValidator接口
public class ListValueConstraintValidator implements ConstraintValidator<ListValue,Integer> {
private Set<Integer> set = new HashSet<>();
//初始化方法
@Override
public void initialize(ListValue constraintAnnotation) {
int[] vals = constraintAnnotation.vals();
for (int val : vals) {
set.add(val);
}
}
//判断是否校验成功
/**
*
* @param value 需要校验的值
* @param context
* @return
*/
@Override
public boolean isValid(Integer value, ConstraintValidatorContext context) {
return set.contains(value);
}
}
4.根据javax.validation 依赖规范,在resource 文件夹下新建ValidationMessages.properties文件,才能使String message() default “{com.centersept.supermall.common.valid.ListValue.message}”;生效。
com.centersept.supermall.common.valid.ListValue.message=显示状态参数错误
5.修改BrandEntity实体类的showStatus属性注解值
@NotNull(groups = {AddGroup.class, UpdateStatusGroup.class})
@ListValue(vals={0,1},groups = {AddGroup.class, UpdateStatusGroup.class})
private Integer showStatus;
6.为了只校验在修改状态的时候才判断该校验值,所以我们要重新声明一个请求方法
/**
* 修改状态
*/
@RequestMapping("/update/status")
public R updateStatus(@Validated(UpdateStatusGroup.class) @RequestBody BrandEntity brand){
brandService.updateById(brand);
return R.ok();
}
7.最后再修改前端页面的请求地址。
SPU&SKU&规格参数&销售属性
SPU = Standard Product Unit (标准产品单位)
SPU是商品信息聚合的最小单位,是一组可复用、易检索的标准化信息的集合,该集合描述了一个产品的特性。
通俗点讲,属性值、特性相同的商品就可以称为一个SPU。
例如:
iphone4就是一个SPU,与商家,与颜色、款式、套餐都无关。
SKU=stock keeping unit(库存量单位)
SKU即库存进出计量的单位, 可以是以件、盒、托盘等为单位。
SKU是物理上不可分割的最小存货单元。在使用时要根据不同业态,不同管理模式来处理。在服装、鞋类商品中使用最多最普遍。
例如:
纺织品中一个SKU通常表示:规格、颜色、款式。
属性分组
前端组件抽取&父子组件交互
1.执行提供的sys_menus.sql文件。
DROP TABLE IF EXISTS `sys_menu`;
CREATE TABLE `sys_menu` (
`menu_id` bigint(20) NOT NULL AUTO_INCREMENT,
`parent_id` bigint(20) DEFAULT NULL COMMENT '父菜单ID,一级菜单为0',
`name` varchar(50) DEFAULT NULL COMMENT '菜单名称',
`url` varchar(200) DEFAULT NULL COMMENT '菜单URL',
`perms` varchar(500) DEFAULT NULL COMMENT '授权(多个用逗号分隔,如:user:list,user:create)',
`type` int(11) DEFAULT NULL COMMENT '类型 0:目录 1:菜单 2:按钮',
`icon` varchar(50) DEFAULT NULL COMMENT '菜单图标',
`order_num` int(11) DEFAULT NULL COMMENT '排序',
PRIMARY KEY (`menu_id`)
) ENGINE=InnoDB AUTO_INCREMENT=76 DEFAULT CHARSET=utf8mb4 COMMENT='菜单管理';
INSERT INTO `sys_menu` ( `menu_id`, `parent_id`, `name`, `url`, `perms`, `type`, `icon`, `order_num` )
VALUES
( 1, 0, '系统管理', NULL, NULL, 0, 'system', 0 ),
( 2, 1, '管理员列表', 'sys/user', NULL, 1, 'admin', 1 ),
( 3, 1, '角色管理', 'sys/role', NULL, 1, 'role', 2 ),
( 4, 1, '菜单管理', 'sys/menu', NULL, 1, 'menu', 3 ),
( 5, 1, 'SQL监控', 'http://localhost:8080/renren-fast/druid/sql.html', NULL, 1, 'sql', 4 ),
( 6, 1, '定时任务', 'job/schedule', NULL, 1, 'job', 5 ),
( 7, 6, '查看', NULL, 'sys:schedule:list,sys:schedule:info', 2, NULL, 0 ),
( 8, 6, '新增', NULL, 'sys:schedule:save', 2, NULL, 0 ),
( 9, 6, '修改', NULL, 'sys:schedule:update', 2, NULL, 0 ),
( 10, 6, '删除', NULL, 'sys:schedule:delete', 2, NULL, 0 ),
( 11, 6, '暂停', NULL, 'sys:schedule:pause', 2, NULL, 0 ),
( 12, 6, '恢复', NULL, 'sys:schedule:resume', 2, NULL, 0 ),
( 13, 6, '立即执行', NULL, 'sys:schedule:run', 2, NULL, 0 ),
( 14, 6, '日志列表', NULL, 'sys:schedule:log', 2, NULL, 0 ),
( 15, 2, '查看', NULL, 'sys:user:list,sys:user:info', 2, NULL, 0 ),
( 16, 2, '新增', NULL, 'sys:user:save,sys:role:select', 2, NULL, 0 ),
( 17, 2, '修改', NULL, 'sys:user:update,sys:role:select', 2, NULL, 0 ),
( 18, 2, '删除', NULL, 'sys:user:delete', 2, NULL, 0 ),
( 19, 3, '查看', NULL, 'sys:role:list,sys:role:info', 2, NULL, 0 ),
( 20, 3, '新增', NULL, 'sys:role:save,sys:menu:list', 2, NULL, 0 ),
( 21, 3, '修改', NULL, 'sys:role:update,sys:menu:list', 2, NULL, 0 ),
( 22, 3, '删除', NULL, 'sys:role:delete', 2, NULL, 0 ),
( 23, 4, '查看', NULL, 'sys:menu:list,sys:menu:info', 2, NULL, 0 ),
( 24, 4, '新增', NULL, 'sys:menu:save,sys:menu:select', 2, NULL, 0 ),
( 25, 4, '修改', NULL, 'sys:menu:update,sys:menu:select', 2, NULL, 0 ),
( 26, 4, '删除', NULL, 'sys:menu:delete', 2, NULL, 0 ),
( 27, 1, '参数管理', 'sys/config', 'sys:config:list,sys:config:info,sys:config:save,sys:config:update,sys:config:delete', 1, 'config', 6 ),
( 29, 1, '系统日志', 'sys/log', 'sys:log:list', 1, 'log', 7 ),
( 30, 1, '文件上传', 'oss/oss', 'sys:oss:all', 1, 'oss', 6 ),
( 31, 0, '商品系统', '', '', 0, 'editor', 0 ),
( 32, 31, '分类维护', 'product/category', '', 1, 'menu', 0 ),
( 34, 31, '品牌管理', 'product/brand', '', 1, 'editor', 0 ),
( 37, 31, '平台属性', '', '', 0, 'system', 0 ),
( 38, 37, '属性分组', 'product/attrgroup', '', 1, 'tubiao', 0 ),
( 39, 37, '规格参数', 'product/baseattr', '', 1, 'log', 0 ),
( 40, 37, '销售属性', 'product/saleattr', '', 1, 'zonghe', 0 ),
( 41, 31, '商品维护', 'product/spu', '', 0, 'zonghe', 0 ),
( 42, 0, '优惠营销', '', '', 0, 'mudedi', 0 ),
( 43, 0, '库存系统', '', '', 0, 'shouye', 0 ),
( 44, 0, '订单系统', '', '', 0, 'config', 0 ),
( 45, 0, '用户系统', '', '', 0, 'admin', 0 ),
( 46, 0, '内容管理', '', '', 0, 'sousuo', 0 ),
( 47, 42, '优惠券管理', 'coupon/coupon', '', 1, 'zhedie', 0 ),
( 48, 42, '发放记录', 'coupon/history', '', 1, 'sql', 0 ),
( 49, 42, '专题活动', 'coupon/subject', '', 1, 'tixing', 0 ),
( 50, 42, '秒杀活动', 'coupon/seckill', '', 1, 'daohang', 0 ),
( 51, 42, '积分维护', 'coupon/bounds', '', 1, 'geren', 0 ),
( 52, 42, '满减折扣', 'coupon/full', '', 1, 'shoucang', 0 ),
( 53, 43, '仓库维护', 'ware/wareinfo', '', 1, 'shouye', 0 ),
( 54, 43, '库存工作单', 'ware/task', '', 1, 'log', 0 ),
( 55, 43, '商品库存', 'ware/sku', '', 1, 'jiesuo', 0 ),
( 56, 44, '订单查询', 'order/order', '', 1, 'zhedie', 0 ),
( 57, 44, '退货单处理', 'order/return', '', 1, 'shanchu', 0 ),
( 58, 44, '等级规则', 'order/settings', '', 1, 'system', 0 ),
( 59, 44, '支付流水查询', 'order/payment', '', 1, 'job', 0 ),
( 60, 44, '退款流水查询', 'order/refund', '', 1, 'mudedi', 0 ),
( 61, 45, '会员列表', 'member/member', '', 1, 'geren', 0 ),
( 62, 45, '会员等级', 'member/level', '', 1, 'tubiao', 0 ),
( 63, 45, '积分变化', 'member/growth', '', 1, 'bianji', 0 ),
( 64, 45, '统计信息', 'member/statistics', '', 1, 'sql', 0 ),
( 65, 46, '首页推荐', 'content/index', '', 1, 'shouye', 0 ),
( 66, 46, '分类热门', 'content/category', '', 1, 'zhedie', 0 ),
( 67, 46, '评论管理', 'content/comments', '', 1, 'pinglun', 0 ),
( 68, 41, 'spu管理', 'product/spu', '', 1, 'config', 0 ),
( 69, 41, '发布商品', 'product/spuadd', '', 1, 'bianji', 0 ),
( 70, 43, '采购单维护', '', '', 0, 'tubiao', 0 ),
( 71, 70, '采购需求', 'ware/purchaseitem', '', 1, 'editor', 0 ),
( 72, 70, '采购单', 'ware/purchase', '', 1, 'menu', 0 ),
( 73, 41, '商品管理', 'product/manager', '', 1, 'zonghe', 0 ),
( 74, 42, '会员价格', 'coupon/memberprice', '', 1, 'admin', 0 ),
( 75, 42, '每日秒杀', 'coupon/seckillsession', '', 1, 'job', 0 );
2.添加树型菜单公共组件,再modules下创建common文件夹,然后创建一个category.vue文件,内容如下:
注:使用this.&emit(’{事件名称}’,[参数。。。])
<template>
<div>
<el-input placeholder="输入关键字进行过滤" v-model="filterText"></el-input>
<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>
3.如何在父页面引用子组件,然后响应子组件的冒泡事件。
<template>
<category @tree-node-click="treenodeclick"></category>
</template>
<script>
import Category from "../common/category";
export default {
components: { Category },
methods: {
//感知树节点被点击
treenodeclick(data, node, component) {
if (node.level == 3) {
this.catId = data.catId;
}
}
}
}
</script>
获取分类属性分组
1.增加接口
AttrGroupController
/**
* 列表
*/
@RequestMapping("/list/{catelogId}")
public R list(@RequestParam Map<String, Object> params,
@PathVariable("catelogId") Long catelogId){
PageUtils page = attrGroupService.queryPage(params,catelogId);
return R.ok().put("page", page);
}
AttrGroupServiceImpl
public 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);
}
}
3.修改前端文件
attrgroup.vue
<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-button
v-if="isAuth('product:attrgroup:save')"
type="primary"
@click="addOrUpdateHandle()"
>新增</el-button>
<el-button
v-if="isAuth('product:attrgroup:delete')"
type="danger"
@click="deleteHandle()"
:disabled="dataListSelections.length <= 0"
>批量删除</el-button>
</el-form-item>
</el-form>
<el-table
:data="dataList"
border
v-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-column
fixed="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-button
type="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";
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>
分组新增&级联选择器
1.使用标签,引入级联选择器
2.为了返回的字段中不存在空字段值的参数返回到响应参数中,在字段变量上注解@JsonInclude(JsonInclude.Include.NON_EMPTY)
分组修改&级联选择器回显
1.this.$nextTick() 的用法是指:等到组件渲染完毕才调用next方法中的方法。
2.渲染级联回显
2.1 添加回显字段
@TableField(exist = false)
private Long[] catelogPath;
2.2 增加方法
AttrGroupController
@Autowired
private CategoryService categoryService;
/**
* 信息
*/
@RequestMapping("/info/{attrGroupId}")
public R infoNew(@PathVariable("attrGroupId") Long attrGroupId){
AttrGroupEntity attrGroup = attrGroupService.getById(attrGroupId);
Long catelogId = attrGroup.getCatelogId();
Long[] path = categoryService.findCatelogPath(catelogId);
attrGroup.setCatelogPath(path);
return R.ok().put("attrGroup", attrGroup);
}
CategoryServiceImpl
@Override
public Long[] findCatelogPath(Long catelogId) {
List<Long> paths = new ArrayList<>();
List<Long> parentPath = findParentPath(catelogId, paths);
Collections.reverse(parentPath);
return parentPath.toArray(new Long[parentPath.size()]);
}
private List<Long> findParentPath(Long catelogId,List<Long> paths){
//1、收集当前节点id
paths.add(catelogId);
CategoryEntity byId = this.getById(catelogId);
if(byId.getParentCid()!=0){
findParentPath(byId.getParentCid(),paths);
}
return paths;
}
3.使用自带属性filterable,增加搜索功能。
4.重置弹出框数据,标签自带数据closed ,声明关闭回调方法,然后置空动态绑定参数。
品牌分类关联与级联更新
1.引入MyBatisPlus插件实现分页
1.1 在product模块下,创建config包,包下建立插件配置类,使用分页插件,注入到Spring 中。
@Configuration
@EnableTransactionManagement //开启事务
@MapperScan("<你到数据控制层包路径>")
public class MyBatisConfig {
//引入分页插件
@Bean
public PaginationInterceptor paginationInterceptor() {
PaginationInterceptor paginationInterceptor = new PaginationInterceptor();
// 设置请求的页面大于最大页后操作, true调回到首页,false 继续请求 默认false
paginationInterceptor.setOverflow(true);
// 设置最大单页限制数量,默认 500 条,-1 不受限制
paginationInterceptor.setLimit(1000);
return paginationInterceptor;
}
}
1.2 修改品牌查询方法,能够通过关键词进行多行检索。
public PageUtils queryPage(Map<String, Object> params) {
//1、获取key
String key = (String) params.get("key");
QueryWrapper<BrandEntity> queryWrapper = new QueryWrapper<>();
if(!StringUtils.isEmpty(key)){
queryWrapper.eq("brand_id",key).or().like("name",key);
}
IPage<BrandEntity> page = this.page(
new Query<BrandEntity>().getPage(params),
queryWrapper
);
return new PageUtils(page);
}
2.品牌分类以及关联
2.1编写品牌分类信息查询接口,CategoryBrandRelationController 增加以下方法:
/**
* 获取当前品牌关联的所有分类列表
*/
@GetMapping("/catelog/list")
public R cateloglist(@RequestParam("brandId") Long brandId) {
List<CategoryBrandRelationEntity> data = categoryBrandRelationService.list(
new QueryWrapper<CategoryBrandRelationEntity>().eq("brand_id", brandId)
);
return R.ok().put("data", data);
}
2.2 编写平台分类信息保存接口
CategoryBrandRelationServiceImpl
public void saveDetail(CategoryBrandRelationEntity categoryBrandRelation) {
Long brandId = categoryBrandRelation.getBrandId();
Long catelogId = categoryBrandRelation.getCatelogId();
//1、查询详细名字
BrandEntity brandEntity = brandDao.selectById(brandId);
CategoryEntity categoryEntity = categoryDao.selectById(catelogId);
categoryBrandRelation.setBrandName(brandEntity.getName());
categoryBrandRelation.setCatelogName(categoryEntity.getName());
this.save(categoryBrandRelation);
}
CategoryBrandRelationController
/**
* 保存关联信息
*/
@RequestMapping("/save")
public R saveDetail(@RequestBody CategoryBrandRelationEntity categoryBrandRelation) {
categoryBrandRelationService.saveDetail(categoryBrandRelation);
return R.ok();
}
3.级联更新,如果更新品牌的名称,我们就需要同时更新关联的品牌数据
BrandServiceImpl.java
@Transactional
@Override
public void updateDetail(BrandEntity brand) {
//保证冗余字段的数据一致
this.updateById(brand);
if(!StringUtils.isEmpty(brand.getName())){
//同步更新其他关联表中的数据
categoryBrandRelationService.updateBrand(brand.getBrandId(),brand.getName());
//TODO 更新其他关联
}
}
BrandController.java
/**
* 修改
*/
@RequestMapping("/update")
//@RequiresPermissions("product:brand:update")
public R update(@Validated(UpdateGroup.class) @RequestBody BrandEntity brand){
brandService.updateDetail(brand);
return R.ok();
}
CategoryBrandRelationServiceImpl.java
@Override
public void updateBrand(Long brandId, String name) {
CategoryBrandRelationEntity relationEntity = new CategoryBrandRelationEntity();
relationEntity.setBrandId(brandId);
relationEntity.setBrandName(name);
this.update(relationEntity,new UpdateWrapper<CategoryBrandRelationEntity>().eq("brand_id",brandId));
}
@Override
public void updateCategory(Long catId, String name) {
this.baseMapper.updateCategory(catId,name);
}
CategoryBrandRelationDao.xml
<update id="updateCategory">
UPDATE `pms_category_brand_relation` SET catelog_name=#{name} WHERE catelog_id=#{catId}
</update>
CategoryBrandRelationDao.java
void updateCategory(@Param("catId") Long catId, @Param("name") String name);
CategoryServiceImpl.java
/**
* 级联更新所有关联的数据
* @param category
*/
@Transactional
@Override
public void updateCascade(CategoryEntity category) {
this.updateById(category);
categoryBrandRelationService.updateCategory(category.getCatId(),category.getName());
}
CategoryController.java
/**
* 修改
*/
@RequestMapping("/update")
//@RequiresPermissions("product:category:update")
public R update(@RequestBody CategoryEntity category){
categoryService.updateCascade(category);
return R.ok();
}
平台属性
规格参数新增与VO
1.VO的概念
略(搜索 object的划分)
2.规格参数新增接口
2.1 新增规格参数VO类
2.2 业务逻辑
AttrServiceImpl.java
@Transactional
@Override
public void saveAttr(AttrVo attr) {
AttrEntity attrEntity = new AttrEntity();
// attrEntity.setAttrName(attr.getAttrName());
BeanUtils.copyProperties(attr,attrEntity);
//1、保存基本数据
this.save(attrEntity);
//2、保存关联关系
if(attr.getAttrType() == ProductConstant.AttrEnum.ATTR_TYPE_BASE.getCode() && attr.getAttrGroupId()!=null){
AttrAttrgroupRelationEntity relationEntity = new AttrAttrgroupRelationEntity();
relationEntity.setAttrGroupId(attr.getAttrGroupId());
relationEntity.setAttrId(attrEntity.getAttrId());
relationDao.insert(relationEntity);
}
}
AttrController.java
/**
* 保存
*/
@RequestMapping("/save")
//@RequiresPermissions("product:attr:save")
public R save(@RequestBody AttrVo attr){
attrService.saveAttr(attr);
return R.ok();
}
规格参数列表
1.新增规格列表查询接口
修改AttrController
@GetMapping("/base/list/{catelogId}")
public R baseAttrList(@RequestParam Map<String, Object> params,
@PathVariable("catelogId") Long catelogId) {
PageUtils page = attrService.queryBaseAttrPage(params, catelogId);
return R.ok().put("page", page);
}
修改AttrServiceImpl
public PageUtils queryBaseAttrPage(Map<String, Object> params, Long catelogId) {
QueryWrapper<AttrEntity> queryWrapper = new QueryWrapper<AttrEntity>();
if (catelogId != 0) {
queryWrapper.eq("catelog_id", catelogId);
}
String key = (String) params.get("key");
if (!StringUtils.isEmpty(key)) {
//attr_id attr_name
queryWrapper.and((wrapper) -> {
wrapper.eq("attr_id", key).or().like("attr_name", key);
});
}
IPage<AttrEntity> page = this.page(
new Query<AttrEntity>().getPage(params),
queryWrapper
);
PageUtils pageUtils = new PageUtils(page);
List<AttrEntity> records = page.getRecords();
List<AttrRespVo> respVos = records.stream().map((attrEntity) -> {
AttrRespVo attrRespVo = new AttrRespVo();
BeanUtils.copyProperties(attrEntity, attrRespVo);
//1、设置分类和分组的名字
AttrAttrgroupRelationEntity attrId =
relationDao.selectOne(new QueryWrapper<AttrAttrgroupRelationEntity>().eq("attr_id",
attrEntity.getAttrId()));
if (attrId != null && attrId.getAttrGroupId() != null) {
AttrGroupEntity attrGroupEntity = attrGroupDao.selectById(attrId.getAttrGroupId());
attrRespVo.setGroupName(attrGroupEntity.getAttrGroupName());
}
CategoryEntity categoryEntity = categoryDao.selectById(attrEntity.getCatelogId());
if (categoryEntity != null) {
attrRespVo.setCatelogName(categoryEntity.getName());
}
return attrRespVo;
}).collect(Collectors.toList());
pageUtils.setList(respVos);
return pageUtils;
}
因为要关联查询数据,分类信息和分组信息,新增响应类AttrRespVo
@Data
public class AttrRespVo extends AttrVo {
/**
* "catelogName": "手机/数码/手机", //所属分类名字
* "groupName": "主体", //所属分组名字
*/
private String catelogName;
private String groupName;
private Long[] catelogPath;
}
规格修改
1.查询属性详情
1.1 修改 /product/attr/info/{attrId} 接口
AttrController.java
/**
* 信息
*/
@RequestMapping("/info/{attrId}")
//@RequiresPermissions("product:attr:info")
public R info(@PathVariable("attrId") Long attrId){
//AttrEntity attr = attrService.getById(attrId);
AttrRespVo respVo = attrService.getAttrInfo(attrId);
return R.ok().put("attr", respVo);
}
AttrServiceImpl.java
public AttrRespVo getAttrInfo(Long attrId) {
AttrRespVo respVo = new AttrRespVo();
AttrEntity attrEntity = this.getById(attrId);
BeanUtils.copyProperties(attrEntity, respVo);
//1、设置分组信息
AttrAttrgroupRelationEntity attrgroupRelation =
relationDao.selectOne(new QueryWrapper<AttrAttrgroupRelationEntity>().eq("attr_id", attrId));
if (attrgroupRelation != null) {
respVo.setAttrGroupId(attrgroupRelation.getAttrGroupId());
AttrGroupEntity attrGroupEntity = attrGroupDao.selectById(attrgroupRelation.getAttrGroupId());
if (attrGroupEntity != null) {
respVo.setGroupName(attrGroupEntity.getAttrGroupName());
}
}
//2、设置分类信息
Long catelogId = attrEntity.getCatelogId();
Long[] catelogPath = categoryService.findCatelogPath(catelogId);
respVo.setCatelogPath(catelogPath);
CategoryEntity categoryEntity = categoryDao.selectById(catelogId);
if(categoryEntity!=null){
respVo.setCatelogName(categoryEntity.getName());
}
return respVo;
}
2.更新属性分组
AttrServiceImpl.java
@Transactional(rollbackFor = RuntimeException.class)
@Override
public void updateAttr(AttrVo attr) {
AttrEntity attrEntity = new AttrEntity();
BeanUtils.copyProperties(attr, attrEntity);
this.updateById(attrEntity);
//1、修改分组关联
AttrAttrgroupRelationEntity relationEntity = new AttrAttrgroupRelationEntity();
relationEntity.setAttrGroupId(attr.getAttrGroupId());
relationEntity.setAttrId(attr.getAttrId());
Integer count = relationDao.selectCount(new QueryWrapper<AttrAttrgroupRelationEntity>().eq("attr_id", attr.getAttrId()));
if (count > 0) {
relationDao.update(relationEntity, new UpdateWrapper<AttrAttrgroupRelationEntity>().eq("attr_id", attr.getAttrId()));
} else {
relationDao.insert(relationEntity);
}
}
AttrController.java
/**
* 修改
*/
@RequestMapping("/update")
//@RequiresPermissions("product:attr:update")
public R update(@RequestBody AttrVo attr){
attrService.updateAttr(attr);
return R.ok();
}
销售属性维护
1.修改之前的所有方法,如果是基本属性需要关联修改,如果是销售属性就不需要,所以我们要定义一个常量作为判断。
AttrServiceImpl.java 修改部分方法
@Transactional
@Override
public void saveAttr(AttrVo attr) {
AttrEntity attrEntity = new AttrEntity();
// attrEntity.setAttrName(attr.getAttrName());
BeanUtils.copyProperties(attr,attrEntity);
//1、保存基本数据
this.save(attrEntity);
//2、保存关联关系
if(attr.getAttrType() == ProductConstant.AttrEnum.ATTR_TYPE_BASE.getCode() && attr.getAttrGroupId()!=null){
AttrAttrgroupRelationEntity relationEntity = new AttrAttrgroupRelationEntity();
relationEntity.setAttrGroupId(attr.getAttrGroupId());
relationEntity.setAttrId(attrEntity.getAttrId());
relationDao.insert(relationEntity);
}
}
@Override
public PageUtils queryBaseAttrPage(Map<String, Object> params, Long catelogId, String type) {
QueryWrapper<AttrEntity> queryWrapper = new QueryWrapper<AttrEntity>().eq("attr_type","base".equalsIgnoreCase(type)?ProductConstant.AttrEnum.ATTR_TYPE_BASE.getCode():ProductConstant.AttrEnum.ATTR_TYPE_SALE.getCode());
if(catelogId != 0){
queryWrapper.eq("catelog_id",catelogId);
}
String key = (String) params.get("key");
if(!StringUtils.isEmpty(key)){
//attr_id attr_name
queryWrapper.and((wrapper)->{
wrapper.eq("attr_id",key).or().like("attr_name",key);
});
}
IPage<AttrEntity> page = this.page(
new Query<AttrEntity>().getPage(params),
queryWrapper
);
PageUtils pageUtils = new PageUtils(page);
List<AttrEntity> records = page.getRecords();
List<AttrRespVo> respVos = records.stream().map((attrEntity) -> {
AttrRespVo attrRespVo = new AttrRespVo();
BeanUtils.copyProperties(attrEntity, attrRespVo);
//1、设置分类和分组的名字
if("base".equalsIgnoreCase(type)){
AttrAttrgroupRelationEntity attrId = relationDao.selectOne(new QueryWrapper<AttrAttrgroupRelationEntity>().eq("attr_id", attrEntity.getAttrId()));
if (attrId != null && attrId.getAttrGroupId()!=null) {
AttrGroupEntity attrGroupEntity = attrGroupDao.selectById(attrId.getAttrGroupId());
attrRespVo.setGroupName(attrGroupEntity.getAttrGroupName());
}
}
CategoryEntity categoryEntity = categoryDao.selectById(attrEntity.getCatelogId());
if (categoryEntity != null) {
attrRespVo.setCatelogName(categoryEntity.getName());
}
return attrRespVo;
}).collect(Collectors.toList());
pageUtils.setList(respVos);
return pageUtils;
}
@Override
public AttrRespVo getAttrInfo(Long attrId) {
AttrRespVo respVo = new AttrRespVo();
AttrEntity attrEntity = this.getById(attrId);
BeanUtils.copyProperties(attrEntity,respVo);
if(attrEntity.getAttrType() == ProductConstant.AttrEnum.ATTR_TYPE_BASE.getCode()){
//1、设置分组信息
AttrAttrgroupRelationEntity attrgroupRelation = relationDao.selectOne(new QueryWrapper<AttrAttrgroupRelationEntity>().eq("attr_id", attrId));
if(attrgroupRelation!=null){
respVo.setAttrGroupId(attrgroupRelation.getAttrGroupId());
AttrGroupEntity attrGroupEntity = attrGroupDao.selectById(attrgroupRelation.getAttrGroupId());
if(attrGroupEntity!=null){
respVo.setGroupName(attrGroupEntity.getAttrGroupName());
}
}
}
//2、设置分类信息
Long catelogId = attrEntity.getCatelogId();
Long[] catelogPath = categoryService.findCatelogPath(catelogId);
respVo.setCatelogPath(catelogPath);
CategoryEntity categoryEntity = categoryDao.selectById(catelogId);
if(categoryEntity!=null){
respVo.setCatelogName(categoryEntity.getName());
}
return respVo;
}
@Transactional
@Override
public void updateAttr(AttrVo attr) {
AttrEntity attrEntity = new AttrEntity();
BeanUtils.copyProperties(attr,attrEntity);
this.updateById(attrEntity);
if(attrEntity.getAttrType() == ProductConstant.AttrEnum.ATTR_TYPE_BASE.getCode()){
//1、修改分组关联
AttrAttrgroupRelationEntity relationEntity = new AttrAttrgroupRelationEntity();
relationEntity.setAttrGroupId(attr.getAttrGroupId());
relationEntity.setAttrId(attr.getAttrId());
Integer count = relationDao.selectCount(new QueryWrapper<AttrAttrgroupRelationEntity>().eq("attr_id", attr.getAttrId()));
if(count>0){
relationDao.update(relationEntity,new UpdateWrapper<AttrAttrgroupRelationEntity>().eq("attr_id",attr.getAttrId()));
}else{
relationDao.insert(relationEntity);
}
}
}
AttrController.java 修改
@GetMapping("/{attrType}/list/{catelogId}")
public R baseAttrList(@RequestParam Map<String, Object> params,
@PathVariable("catelogId") Long catelogId,
@PathVariable("attrType")String type){
PageUtils page = attrService.queryBaseAttrPage(params,catelogId,type);
return R.ok().put("page", page);
}
在common 模块添加常量类 ProductConstant
public class ProductConstant {
public enum AttrEnum{
ATTR_TYPE_BASE(1,"基本属性"),ATTR_TYPE_SALE(0,"销售属性");
private int code;
private String msg;
AttrEnum(int code,String msg){
this.code = code;
this.msg = msg;
}
public int getCode() {
return code;
}
public String getMsg() {
return msg;
}
}
}
查询分组关联属性&删除关联
1.查询分组关联属性
AttrServiceImpl.java
/**
* 根据分组id查找关联的所有基本属性
* @param attrgroupId
* @return
*/
@Override
public List<AttrEntity> getRelationAttr(Long attrgroupId) {
List<AttrAttrgroupRelationEntity> entities = relationDao.selectList(new QueryWrapper<AttrAttrgroupRelationEntity>().eq("attr_group_id", attrgroupId));
List<Long> attrIds = entities.stream().map((attr) -> {
return attr.getAttrId();
}).collect(Collectors.toList());
if(attrIds == null || attrIds.size() == 0){
return null;
}
Collection<AttrEntity> attrEntities = this.listByIds(attrIds);
return (List<AttrEntity>) attrEntities;
}
AttrGroupController 添加以下方法
@GetMapping("/{attrgroupId}/attr/relation")
public R attrRelation(@PathVariable("attrgroupId") Long attrgroupId){
List<AttrEntity> entities = attrService.getRelationAttr(attrgroupId);
return R.ok().put("data",entities);
}
2.删除关联
增加 AttrGroupRelationVo
@Data
public class AttrGroupRelationVo {
//"attrId":1,"attrGroupId":2
private Long attrId;
private Long attrGroupId;
}
AttrServiceImpl
public void deleteRelation(AttrGroupRelationVo[] vos) {
//relationDao.delete(new QueryWrapper<>().eq("attr_id",1L).eq("attr_group_id",1L));
//
List<AttrAttrgroupRelationEntity> entities = Arrays.asList(vos).stream().map((item) -> {
AttrAttrgroupRelationEntity relationEntity = new AttrAttrgroupRelationEntity();
BeanUtils.copyProperties(item, relationEntity);
return relationEntity;
}).collect(Collectors.toList());
relationDao.deleteBatchRelation(entities);
}
AttrGroupController 增加
@PostMapping("/attr/relation/delete")
public R deleteRelation(@RequestBody AttrGroupRelationVo[] vos){
attrService.deleteRelation(vos);
return R.ok();
}
AttrAttrgroupRelationDao
void deleteBatchRelation(@Param(value = "entities") List<AttrAttrgroupRelationEntity> entities);
AttrAttrgroupRelationDao.xml
<delete id="deleteBatchRelation">
DELETE FROM `pms_attr_attrgroup_relation` WHERE
<foreach separator=" OR " item="item" collection="entities">(attr_id=#{item.attrId} AND
attr_group_id=#{item.attrGroupId})
</foreach>
</delete>
查询分组未关联属性
AttrGroupController
@GetMapping("/{attrgroupId}/noattr/relation")
public R attrNoRelation(@PathVariable("attrgroupId") Long attrgroupId,
@RequestParam Map<String, Object> params){
PageUtils page = attrService.getNoRelationAttr(params,attrgroupId);
return R.ok().put("page",page);
}
AttrServiceImpl
/**
* 获取当前分组没有关联的所有属性
* @param params
* @param attrgroupId
* @return
*/
@Override
public PageUtils getNoRelationAttr(Map<String, Object> params, Long attrgroupId) {
//1、当前分组只能关联自己所属的分类里面的所有属性
AttrGroupEntity attrGroupEntity = attrGroupDao.selectById(attrgroupId);
Long catelogId = attrGroupEntity.getCatelogId();
//2、当前分组只能关联别的分组没有引用的属性
//2.1)、当前分类下的其他分组
List<AttrGroupEntity> group = attrGroupDao.selectList(new QueryWrapper<AttrGroupEntity>().eq("catelog_id", catelogId));
List<Long> collect = group.stream().map(item -> {
return item.getAttrGroupId();
}).collect(Collectors.toList());
//2.2)、这些分组关联的属性
List<AttrAttrgroupRelationEntity> groupId = relationDao.selectList(new QueryWrapper<AttrAttrgroupRelationEntity>().in("attr_group_id", collect));
List<Long> attrIds = groupId.stream().map(item -> {
return item.getAttrId();
}).collect(Collectors.toList());
//2.3)、从当前分类的所有属性中移除这些属性;
QueryWrapper<AttrEntity> wrapper = new QueryWrapper<AttrEntity>().eq("catelog_id", catelogId).eq("attr_type",ProductConstant.AttrEnum.ATTR_TYPE_BASE.getCode());
if(attrIds!=null && attrIds.size()>0){
wrapper.notIn("attr_id", attrIds);
}
String key = (String) params.get("key");
if(!StringUtils.isEmpty(key)){
wrapper.and((w)->{
w.eq("attr_id",key).or().like("attr_name",key);
});
}
IPage<AttrEntity> page = this.page(new Query<AttrEntity>().getPage(params), wrapper);
PageUtils pageUtils = new PageUtils(page);
return pageUtils;
}
新增分组与属性关联
AttrAttrgroupRelationServiceImpl.java
public void saveBatch(List<AttrGroupRelationVo> vos) {
List<AttrAttrgroupRelationEntity> collect = vos.stream().map(item -> {
AttrAttrgroupRelationEntity relationEntity = new AttrAttrgroupRelationEntity();
BeanUtils.copyProperties(item, relationEntity);
return relationEntity;
}).collect(Collectors.toList());
this.saveBatch(collect);
}
AttrGroupController.java
@PostMapping("/attr/relation")
public R addRelation(@RequestBody List<AttrGroupRelationVo> vos){
relationService.saveBatch(vos);
return R.ok();
}
新增商品
调试会员等级相关接口
1.在网关模块添加Member模块路由配置
routes.yml
- id: member_route
uri: lb://supermall-member
predicates:
- Path=/api/member/**
filters:
- RewritePath=/api/(?<segment>.*),/$\{segment}
2.在会员页面调试新增接口,添加会员信息
获取分类关联的品牌
CategoryBrandRelationController
/**
* /product/categorybrandrelation/brands/list
*
* 1、Controller:处理请求,接受和校验数据
* 2、Service接受controller传来的数据,进行业务处理
* 3、Controller接受Service处理完的数据,封装页面指定的vo
*/
@GetMapping("/brands/list")
public R relationBrandsList(@RequestParam(value = "catId",required = true)Long catId){
List<BrandEntity> vos = categoryBrandRelationService.getBrandsByCatId(catId);
List<BrandVo> collect = vos.stream().map(item -> {
BrandVo brandVo = new BrandVo();
brandVo.setBrandId(item.getBrandId());
brandVo.setBrandName(item.getName());
return brandVo;
}).collect(Collectors.toList());
return R.ok().put("data",collect);
}
CategoryBrandRelationServiceImpl
public List<BrandEntity> getBrandsByCatId(Long catId) {
List<CategoryBrandRelationEntity> catelogId = relationDao.selectList(new QueryWrapper<CategoryBrandRelationEntity>().eq("catelog_id", catId));
List<BrandEntity> collect = catelogId.stream().map(item -> {
Long brandId = item.getBrandId();
BrandEntity byId = brandService.getById(brandId);
return byId;
}).collect(Collectors.toList());
return collect;
}
注:前端部分页面引入pubsub-js
1.引入前端组件pubsub-js
现在项目根目录使用命令,下载pubsub-js
npm install pubsub-js --save
在spuuadd.vue ,category-cascader.vue,brand-select.vue 引入pubsub-js
// 导入
import PubSub from "pubsub-js"
修改部分使用this.PubSub 的地方,直接使用 PubSub。
获取分类下所有分组以及属性
AttrGroupRelationVo 添加实体类
@Data
public class AttrGroupWithAttrsVo {
/**
* 分组id
*/
private Long attrGroupId;
/**
* 组名
*/
private String attrGroupName;
/**
* 排序
*/
private Integer sort;
/**
* 描述
*/
private String descript;
/**
* 组图标
*/
private String icon;
/**
* 所属分类id
*/
private Long catelogId;
private List<AttrEntity> attrs;
}
AttrGroupController
@GetMapping("/{catelogId}/withattr")
public R getAttrGroupWithAttrs(@PathVariable("catelogId")Long catelogId){
//1、查出当前分类下的所有属性分组,
//2、查出每个属性分组的所有属性
List<AttrGroupWithAttrsVo> vos = attrGroupService.getAttrGroupWithAttrsByCatelogId(catelogId);
return R.ok().put("data",vos);
}
AttrGroupServiceImpl
/**
* 根据分类id查出所有的分组以及这些组里面的属性
* @param catelogId
* @return
*/
@Override
public List<AttrGroupWithAttrsVo> getAttrGroupWithAttrsByCatelogId(Long catelogId) {
//com.atguigu.gulimall.product.vo
//1、查询分组信息
List<AttrGroupEntity> attrGroupEntities = this.list(new QueryWrapper<AttrGroupEntity>().eq("catelog_id", catelogId));
//2、查询所有属性
List<AttrGroupWithAttrsVo> collect = attrGroupEntities.stream().map(group -> {
AttrGroupWithAttrsVo attrsVo = new AttrGroupWithAttrsVo();
BeanUtils.copyProperties(group,attrsVo);
List<AttrEntity> attrs = attrService.getRelationAttr(attrsVo.getAttrGroupId());
attrsVo.setAttrs(attrs);
return attrsVo;
}).collect(Collectors.toList());
return collect;
}
修改spuadd.vue的showBaseAttrs()
showBaseAttrs() {
if (!this.dataResp.steped[0]) {
this.$http({
url: this.$http.adornUrl(
`/product/attrgroup/${this.spu.catalogId}/withattr`
),
method: "get",
params: this.$http.adornParams({})
}).then(({ data }) => {
//先对表单的baseAttrs进行初始化
data.data.forEach(item => {
let attrArray = [];
if (item.attrs) {
item.attrs.forEach(attr => {
attrArray.push({
attrId: attr.attrId,
attrValues: "",
showDesc: attr.showDesc
});
});
}
this.dataResp.baseAttrs.push(attrArray);
});
this.dataResp.steped[0] = 0;
this.dataResp.attrGroups = data.data;
});
}
}
商品新增VO抽取
@Data
public class Attr {
private Long attrId;
private String attrName;
private String attrValue;
}
@Data
public class BaseAttrs {
private Long attrId;
private String attrValues;
private int showDesc;
}
@Data
public class Bounds {
private BigDecimal buyBounds;
private BigDecimal growBounds;
}
@Data
public class Images {
private String imgUrl;
private int defaultImg;
}
@Data
public class MemberPrice {
private Long id;
private String name;
private BigDecimal price;
}
@Data
public class Skus {
private List<Attr> attr;
private String skuName;
private BigDecimal price;
private String skuTitle;
private String skuSubtitle;
private List<Images> images;
private List<String> descar;
private int fullCount;
private BigDecimal discount;
private int countStatus;
private BigDecimal fullPrice;
private BigDecimal reducePrice;
private int priceStatus;
private List<MemberPrice> memberPrice;
}
@Data
public class SpuSaveVo {
private String spuName;
private String spuDescription;
private Long catalogId;
private Long brandId;
private BigDecimal weight;
private int publishStatus;
private List<String> decript;
private List<String> images;
private Bounds bounds;
private List<BaseAttrs> baseAttrs;
private List<Skus> skus;
}
商品新增业务流程分析
SpuInfoServiceImpl 下增加预备方法
public void saveSpuInfo(SpuSaveVo vo) {
//1、保存spu基本信息 pms_spu_info
//2、保存Spu的描述图片 pms_spu_info_desc
//3、保存spu的图片集 pms_spu_images
//4、保存spu的规格参数;pms_product_attr_value
//5、保存spu的积分信息;supermall_sms->sms_spu_bounds
//5、保存当前spu对应的所有sku信息;
//5.1)、sku的基本信息;pms_sku_info
//5.2)、sku的图片信息;pms_sku_image
//5.3)、sku的销售属性信息:pms_sku_sale_attr_value
// //5.4)、sku的优惠、满减等信息;supermall_sms->sms_sku_ladder\sms_sku_full_reduction\sms_member_price
}
SpuInfoController
/**
* 保存
*/
@RequestMapping("/save")
//@RequiresPermissions("product:spuinfo:save")
public R save(@RequestBody SpuSaveVo vo){
spuInfoService.saveSpuInfo(vo);
return R.ok();
}
保存SPU基本信息
新增修改 SpuInfoServiceImpl
@Autowired
SpuInfoDescService spuInfoDescService;
@Autowired
SpuImagesService imagesService;
@Autowired
AttrService attrService;
@Autowired
ProductAttrValueService attrValueService;
@Transactional(rollbackFor = Exception.class)
@Override
public void saveSpuInfo(SpuSaveVo vo) {
//1、保存spu基本信息 pms_spu_info
SpuInfoEntity infoEntity = new SpuInfoEntity();
BeanUtils.copyProperties(vo,infoEntity);
infoEntity.setCreateTime(new Date());
infoEntity.setUpdateTime(new Date());
this.saveBaseSpuInfo(infoEntity);
//2、保存Spu的描述图片 pms_spu_info_desc
List<String> decript = vo.getDecript();
SpuInfoDescEntity descEntity = new SpuInfoDescEntity();
descEntity.setSpuId(infoEntity.getId());
descEntity.setDecript(String.join(",",decript));
spuInfoDescService.saveSpuInfoDesc(descEntity);
//3、保存spu的图片集 pms_spu_images
List<String> images = vo.getImages();
imagesService.saveImages(infoEntity.getId(),images);
//4、保存spu的规格参数;pms_product_attr_value
List<BaseAttrs> baseAttrs = vo.getBaseAttrs();
List<ProductAttrValueEntity> collect = baseAttrs.stream().map(attr -> {
ProductAttrValueEntity valueEntity = new ProductAttrValueEntity();
valueEntity.setAttrId(attr.getAttrId());
AttrEntity id = attrService.getById(attr.getAttrId());
valueEntity.setAttrName(id.getAttrName());
valueEntity.setAttrValue(attr.getAttrValues());
valueEntity.setQuickShow(attr.getShowDesc());
valueEntity.setSpuId(infoEntity.getId());
return valueEntity;
}).collect(Collectors.toList());
attrValueService.saveProductAttr(collect);
//5、保存spu的积分信息;supermall_sms->sms_spu_bounds
//5、保存当前spu对应的所有sku信息;
//5.1)、sku的基本信息;pms_sku_info
//5.2)、sku的图片信息;pms_sku_image
//5.3)、sku的销售属性信息:pms_sku_sale_attr_value
// //5.4)、sku的优惠、满减等信息;supermall_sms->sms_sku_ladder\sms_sku_full_reduction\sms_member_price
}
@Override
public void saveBaseSpuInfo(SpuInfoEntity infoEntity) {
this.baseMapper.insert(infoEntity);
}
SpuInfoDescServiceImpl
@Override
public void saveSpuInfoDesc(SpuInfoDescEntity descEntity) {
this.baseMapper.insert(descEntity);
}
SpuImagesServiceImpl
@Override
public void saveImages(Long id, List<String> images) {
if (images == null || images.size() == 0) {
return;
} else {
List<SpuImagesEntity> collect = images.stream().map(img -> {
SpuImagesEntity spuImagesEntity = new SpuImagesEntity();
spuImagesEntity.setSpuId(id);
spuImagesEntity.setImgUrl(img);
return spuImagesEntity;
}).collect(Collectors.toList());
this.saveBatch(collect);
}
}
ProductAttrValueServiceImpl
@Override
public void saveProductAttr(List<ProductAttrValueEntity> collect) {
this.saveBatch(collect);
}
保存SKU基本信息
修改SpuInfoServiceImpl
@Autowired
SkuInfoService skuInfoService;
@Autowired
SkuImagesService skuImagesService;
@Autowired
SkuSaleAttrValueService skuSaleAttrValueService;
@Override
public void saveSpuInfo(SpuSaveVo vo) {
//1、保存spu基本信息 pms_spu_info
SpuInfoEntity infoEntity = new SpuInfoEntity();
BeanUtils.copyProperties(vo, infoEntity);
infoEntity.setCreateTime(new Date());
infoEntity.setUpdateTime(new Date());
this.saveBaseSpuInfo(infoEntity);
//2、保存Spu的描述图片 pms_spu_info_desc
List<String> decript = vo.getDecript();
SpuInfoDescEntity descEntity = new SpuInfoDescEntity();
descEntity.setSpuId(infoEntity.getId());
descEntity.setDecript(String.join(",", decript));
spuInfoDescService.saveSpuInfoDesc(descEntity);
//3、保存spu的图片集 pms_spu_images
List<String> images = vo.getImages();
imagesService.saveImages(infoEntity.getId(), images);
//4、保存spu的规格参数;pms_product_attr_value
List<BaseAttrs> baseAttrs = vo.getBaseAttrs();
List<ProductAttrValueEntity> collect = baseAttrs.stream().map(attr -> {
ProductAttrValueEntity valueEntity = new ProductAttrValueEntity();
valueEntity.setAttrId(attr.getAttrId());
AttrEntity id = attrService.getById(attr.getAttrId());
valueEntity.setAttrName(id.getAttrName());
valueEntity.setAttrValue(attr.getAttrValues());
valueEntity.setQuickShow(attr.getShowDesc());
valueEntity.setSpuId(infoEntity.getId());
return valueEntity;
}).collect(Collectors.toList());
attrValueService.saveProductAttr(collect);
//5、保存spu的积分信息;supermall_sms->sms_spu_bounds
// todo
//5、保存当前spu对应的所有sku信息;
List<Skus> skus = vo.getSkus();
if (skus != null && skus.size() > 0) {
skus.forEach(item -> {
String defaultImg = "";
for (Images image : item.getImages()) {
if (image.getDefaultImg() == 1) {
defaultImg = image.getImgUrl();
}
}
// private String skuName;
// private BigDecimal price;
// private String skuTitle;
// private String skuSubtitle;
SkuInfoEntity skuInfoEntity = new SkuInfoEntity();
BeanUtils.copyProperties(item, skuInfoEntity);
skuInfoEntity.setBrandId(infoEntity.getBrandId());
skuInfoEntity.setCatalogId(infoEntity.getCatalogId());
skuInfoEntity.setSaleCount(0L);
skuInfoEntity.setSpuId(infoEntity.getId());
skuInfoEntity.setSkuDefaultImg(defaultImg);
//5.1)、sku的基本信息;pms_sku_info
skuInfoService.saveSkuInfo(skuInfoEntity);
Long skuId = skuInfoEntity.getSkuId();
List<SkuImagesEntity> imagesEntities = item.getImages().stream().map(img -> {
SkuImagesEntity skuImagesEntity = new SkuImagesEntity();
skuImagesEntity.setSkuId(skuId);
skuImagesEntity.setImgUrl(img.getImgUrl());
skuImagesEntity.setDefaultImg(img.getDefaultImg());
return skuImagesEntity;
}).filter(entity->{
//返回true就是需要,false就是剔除
return !StringUtils.isEmpty(entity.getImgUrl());
}).collect(Collectors.toList());
//5.2)、sku的图片信息;pms_sku_image
skuImagesService.saveBatch(imagesEntities);
//没有图片路径的无需保存
List<Attr> attr = item.getAttr();
List<SkuSaleAttrValueEntity> skuSaleAttrValueEntities = attr.stream().map(a -> {
SkuSaleAttrValueEntity attrValueEntity = new SkuSaleAttrValueEntity();
BeanUtils.copyProperties(a, attrValueEntity);
attrValueEntity.setSkuId(skuId);
return attrValueEntity;
}).collect(Collectors.toList());
//5.3)、sku的销售属性信息:pms_sku_sale_attr_value
skuSaleAttrValueService.saveBatch(skuSaleAttrValueEntities);
});
}
//5.4)、sku的优惠、满减等信息;supermall_sms->sms_sku_ladder\sms_sku_full_reduction\sms_member_price
//todo
}
SkuInfoServiceImpl
@Override
public void saveSkuInfo(SkuInfoEntity skuInfoEntity) {
this.baseMapper.insert(skuInfoEntity);
}
调用远程服务保存优惠等信息
1.product模块的处理
1.1 在product模块下创建feign包,新建一个CouponFeignService远程调用接口:
@FeignClient("supermall-coupon")
public interface CouponFeignService {
/**
* 1、CouponFeignService.saveSpuBounds(spuBoundTo);
* 1)、@RequestBody将这个对象转为json。
* 2)、找到gulimall-coupon服务,给/coupon/spubounds/save发送请求。
* 将上一步转的json放在请求体位置,发送请求;
* 3)、对方服务收到请求。请求体里有json数据。
* (@RequestBody SpuBoundsEntity spuBounds);将请求体的json转为SpuBoundsEntity;
* 只要json数据模型是兼容的。双方服务无需使用同一个to
* @param spuBoundTo
* @return
*/
@PostMapping("/coupon/spubounds/save")
R saveSpuBounds(@RequestBody SpuBoundTo spuBoundTo);
@PostMapping("/coupon/skufullreduction/saveinfo")
R saveSkuReduction(@RequestBody SkuReductionTo skuReductionTo);
}
1.2 在common模块新建一个to包,在该包下新建涉及的TO类
@Data
public class SpuBoundTo {
private Long spuId;
private BigDecimal buyBounds;
private BigDecimal growBounds;
}
@Data
public class SkuReductionTo {
private Long skuId;
private int fullCount;
private BigDecimal discount;
private int countStatus;
private BigDecimal fullPrice;
private BigDecimal reducePrice;
private int priceStatus;
private List<MemberPrice> memberPrice;
}
@Data
public class MemberPrice {
private Long id;
private String name;
private BigDecimal price;
}
1.3 修改SpuInfoServiceImpl
@Autowired
CouponFeignService couponFeignService;
public void saveSpuInfo(SpuSaveVo vo) {
//1、保存spu基本信息 pms_spu_info
SpuInfoEntity infoEntity = new SpuInfoEntity();
BeanUtils.copyProperties(vo, infoEntity);
infoEntity.setCreateTime(new Date());
infoEntity.setUpdateTime(new Date());
this.saveBaseSpuInfo(infoEntity);
//2、保存Spu的描述图片 pms_spu_info_desc
List<String> decript = vo.getDecript();
SpuInfoDescEntity descEntity = new SpuInfoDescEntity();
descEntity.setSpuId(infoEntity.getId());
descEntity.setDecript(String.join(",", decript));
spuInfoDescService.saveSpuInfoDesc(descEntity);
//3、保存spu的图片集 pms_spu_images
List<String> images = vo.getImages();
imagesService.saveImages(infoEntity.getId(), images);
//4、保存spu的规格参数;pms_product_attr_value
List<BaseAttrs> baseAttrs = vo.getBaseAttrs();
List<ProductAttrValueEntity> collect = baseAttrs.stream().map(attr -> {
ProductAttrValueEntity valueEntity = new ProductAttrValueEntity();
valueEntity.setAttrId(attr.getAttrId());
AttrEntity id = attrService.getById(attr.getAttrId());
valueEntity.setAttrName(id.getAttrName());
valueEntity.setAttrValue(attr.getAttrValues());
valueEntity.setQuickShow(attr.getShowDesc());
valueEntity.setSpuId(infoEntity.getId());
return valueEntity;
}).collect(Collectors.toList());
attrValueService.saveProductAttr(collect);
//5、保存spu的积分信息;supermall_sms->sms_spu_bounds
Bounds bounds = vo.getBounds();
SpuBoundTo spuBoundTo = new SpuBoundTo();
BeanUtils.copyProperties(bounds,spuBoundTo);
spuBoundTo.setSpuId(infoEntity.getId());
R r = couponFeignService.saveSpuBounds(spuBoundTo);
if(r.getCode() != 0){
log.error("远程保存spu积分信息失败");
}
//5、保存当前spu对应的所有sku信息;
List<Skus> skus = vo.getSkus();
if (skus != null && skus.size() > 0) {
skus.forEach(item -> {
String defaultImg = "";
for (Images image : item.getImages()) {
if (image.getDefaultImg() == 1) {
defaultImg = image.getImgUrl();
}
}
// private String skuName;
// private BigDecimal price;
// private String skuTitle;
// private String skuSubtitle;
SkuInfoEntity skuInfoEntity = new SkuInfoEntity();
BeanUtils.copyProperties(item, skuInfoEntity);
skuInfoEntity.setBrandId(infoEntity.getBrandId());
skuInfoEntity.setCatalogId(infoEntity.getCatalogId());
skuInfoEntity.setSaleCount(0L);
skuInfoEntity.setSpuId(infoEntity.getId());
skuInfoEntity.setSkuDefaultImg(defaultImg);
//5.1)、sku的基本信息;pms_sku_info
skuInfoService.saveSkuInfo(skuInfoEntity);
Long skuId = skuInfoEntity.getSkuId();
List<SkuImagesEntity> imagesEntities = item.getImages().stream().map(img -> {
SkuImagesEntity skuImagesEntity = new SkuImagesEntity();
skuImagesEntity.setSkuId(skuId);
skuImagesEntity.setImgUrl(img.getImgUrl());
skuImagesEntity.setDefaultImg(img.getDefaultImg());
return skuImagesEntity;
}).filter(entity->{
//返回true就是需要,false就是剔除
return !StringUtils.isEmpty(entity.getImgUrl());
}).collect(Collectors.toList());
//5.2)、sku的图片信息;pms_sku_image
skuImagesService.saveBatch(imagesEntities);
//没有图片路径的无需保存
List<Attr> attr = item.getAttr();
List<SkuSaleAttrValueEntity> skuSaleAttrValueEntities = attr.stream().map(a -> {
SkuSaleAttrValueEntity attrValueEntity = new SkuSaleAttrValueEntity();
BeanUtils.copyProperties(a, attrValueEntity);
attrValueEntity.setSkuId(skuId);
return attrValueEntity;
}).collect(Collectors.toList());
//5.3)、sku的销售属性信息:pms_sku_sale_attr_value
skuSaleAttrValueService.saveBatch(skuSaleAttrValueEntities);
SkuReductionTo skuReductionTo = new SkuReductionTo();
BeanUtils.copyProperties(item,skuReductionTo);
skuReductionTo.setSkuId(skuId);
if(skuReductionTo.getFullCount() >0 || skuReductionTo.getFullPrice().compareTo(new BigDecimal("0")) == 1){
R r1 = couponFeignService.saveSkuReduction(skuReductionTo);
if(r1.getCode() != 0){
log.error("远程保存sku优惠信息失败");
}
}
});
}
}
1.4 在启动类加入@EnableFeignClients(basePackages = “com.centersept.supermall.product.feign”)注解,开启远程调用功能。
2.处理coupon模块
2.1 在nacos上配置Coupon模块的配置文件,Coupon模块引入nacos的配置
2.2 修改SpuBoundsController 方法
/**
* 保存
*/
@PostMapping("/save")
public R save(@RequestBody SpuBoundsEntity spuBounds){
spuBoundsService.save(spuBounds);
return R.ok();
}
2.3 新增 SkuFullReductionController 和 SkuFullReductionServiceImpl 的方法
@PostMapping("/saveinfo")
public R saveInfo(@RequestBody SkuReductionTo reductionTo){
skuFullReductionService.saveSkuReduction(reductionTo);
return R.ok();
}
@Autowired
SkuLadderService skuLadderService;
@Autowired
MemberPriceService memberPriceService;
@Override
public void saveSkuReduction(SkuReductionTo reductionTo) {
//1、// //5.4)、sku的优惠、满减等信息;gulimall_sms->sms_sku_ladder\sms_sku_full_reduction\sms_member_price
//sms_sku_ladder
SkuLadderEntity skuLadderEntity = new SkuLadderEntity();
skuLadderEntity.setSkuId(reductionTo.getSkuId());
skuLadderEntity.setFullCount(reductionTo.getFullCount());
skuLadderEntity.setDiscount(reductionTo.getDiscount());
skuLadderEntity.setAddOther(reductionTo.getCountStatus());
if(reductionTo.getFullCount() > 0){
skuLadderService.save(skuLadderEntity);
}
//2、sms_sku_full_reduction
SkuFullReductionEntity reductionEntity = new SkuFullReductionEntity();
BeanUtils.copyProperties(reductionTo,reductionEntity);
if(reductionEntity.getFullPrice().compareTo(new BigDecimal("0"))==1){
this.save(reductionEntity);
}
//3、sms_member_price
List<MemberPrice> memberPrice = reductionTo.getMemberPrice();
List<MemberPriceEntity> collect = memberPrice.stream().map(item -> {
MemberPriceEntity priceEntity = new MemberPriceEntity();
priceEntity.setSkuId(reductionTo.getSkuId());
priceEntity.setMemberLevelId(item.getId());
priceEntity.setMemberLevelName(item.getName());
priceEntity.setMemberPrice(item.getPrice());
priceEntity.setAddOther(1);
return priceEntity;
}).filter(item->{
return item.getMemberPrice().compareTo(new BigDecimal("0")) == 1;
}).collect(Collectors.toList());
memberPriceService.saveBatch(collect);
}
商品保存debug完成
- 使用Idea一键启动全部项目
1.1 打开 Edit Configuration… ,点击加号,选择Compound
1.2 在右边的界面点击加号添加需要运行的Application类
- 由于库表的id非自增的,修改 SpuInfoDescEntity 实体类 id 注解为 @TableId(type = IdType.INPUT)
商品保存其他问题
1.空图片的数据不需要保存到库中,需要做过滤
2.还有满减和折扣没有设置的,也需要过滤保存
3.还有会员价格。
商品管理
SPU检索
1.SPU管理界面查询接口
SpuInfoController
/**
* 列表
*/
@RequestMapping("/list")
//@RequiresPermissions("product:spuinfo:list")
public R list(@RequestParam Map<String, Object> params){
PageUtils page = spuInfoService.queryPageByCondition(params);
return R.ok().put("page", page);
}
SpuInfoServiceImpl
public PageUtils queryPageByCondition(Map<String, Object> params) {
QueryWrapper<SpuInfoEntity> wrapper = new QueryWrapper<>();
String key = (String) params.get("key");
if (!StringUtils.isEmpty(key)) {
wrapper.and((w) -> {
w.eq("id", key).or().like("spu_name", key);
});
}
// status=1 and (id=1 or spu_name like xxx)
String status = (String) params.get("status");
if (!StringUtils.isEmpty(status)) {
wrapper.eq("publish_status", status);
}
String brandId = (String) params.get("brandId");
if (!StringUtils.isEmpty(brandId) && !"0".equalsIgnoreCase(brandId)) {
wrapper.eq("brand_id", brandId);
}
String catelogId = (String) params.get("catelogId");
if (!StringUtils.isEmpty(catelogId) && !"0".equalsIgnoreCase(catelogId)) {
wrapper.eq("catalog_id", catelogId);
}
/**
* status: 2
* key:
* brandId: 9
* catelogId: 225
*/
IPage<SpuInfoEntity> page = this.page(
new Query<SpuInfoEntity>().getPage(params),
wrapper
);
return new PageUtils(page);
}
2.时间格式化,在application.yml 加入以下配置:
spring:
jackson:
date-format: yyyy-MM-dd HH:mm:ss
SKU检索
1.SKU管理界面查询
SkuInfoController
/**
* 列表
*/
@RequestMapping("/list")
//@RequiresPermissions("product:skuinfo:list")
public R list(@RequestParam Map<String, Object> params){
PageUtils page = skuInfoService.queryPageByCondition(params);
return R.ok().put("page", page);
}
SkuInfoServiceImpl
public PageUtils queryPageByCondition(Map<String, Object> params) {
QueryWrapper<SkuInfoEntity> queryWrapper = new QueryWrapper<>();
/**
* key:
* catelogId: 0
* brandId: 0
* min: 0
* max: 0
*/
String key = (String) params.get("key");
if(!StringUtils.isEmpty(key)){
queryWrapper.and((wrapper)->{
wrapper.eq("sku_id",key).or().like("sku_name",key);
});
}
String catelogId = (String) params.get("catelogId");
if(!StringUtils.isEmpty(catelogId)&&!"0".equalsIgnoreCase(catelogId)){
queryWrapper.eq("catalog_id",catelogId);
}
String brandId = (String) params.get("brandId");
if(!StringUtils.isEmpty(brandId)&&!"0".equalsIgnoreCase(catelogId)){
queryWrapper.eq("brand_id",brandId);
}
String min = (String) params.get("min");
if(!StringUtils.isEmpty(min)){
queryWrapper.ge("price",min);
}
String max = (String) params.get("max");
if(!StringUtils.isEmpty(max) ){
try{
BigDecimal bigDecimal = new BigDecimal(max);
if(bigDecimal.compareTo(new BigDecimal("0"))==1){
queryWrapper.le("price",max);
}
}catch (Exception e){
}
}
IPage<SkuInfoEntity> page = this.page(
new Query<SkuInfoEntity>().getPage(params),
queryWrapper
);
return new PageUtils(page);
}
SPU规格维护
1.获取SPU规格接口编写
1.1 编写/product/attr/base/listforspu/{spuId}
AttrController
@Autowired
ProductAttrValueService productAttrValueService;
@GetMapping("/base/listforspu/{spuId}")
public R baseAttrlistforspu(@PathVariable("spuId") Long spuId){
List<ProductAttrValueEntity> entities = productAttrValueService.baseAttrlistforspu(spuId);
return R.ok().put("data",entities);
}
ProductAttrValueServiceImpl
public List<ProductAttrValueEntity> baseAttrlistforspu(Long spuId) {
List<ProductAttrValueEntity> entities = this.baseMapper.selectList(new QueryWrapper<ProductAttrValueEntity>().eq("spu_id", spuId));
return entities;
}
2.修改商品规格
2.1 编写 /product/attr/update/{spuId} 接口
AttrController
@PostMapping("/update/{spuId}")
public R updateSpuAttr(@PathVariable("spuId") Long spuId,
@RequestBody List<ProductAttrValueEntity> entities) {
productAttrValueService.updateSpuAttr(spuId, entities);
return R.ok();
}
ProductAttrValueServiceImpl
@Transactional(rollbackFor = RuntimeException.class)
@Override
public void updateSpuAttr(Long spuId, List<ProductAttrValueEntity> entities) {
//1、删除这个spuId之前对应的所有属性
this.baseMapper.delete(new QueryWrapper<ProductAttrValueEntity>().eq("spu_id", spuId));
List<ProductAttrValueEntity> collect = entities.stream().map(item -> {
item.setSpuId(spuId);
return item;
}).collect(Collectors.toList());
this.saveBatch(collect);
}