@TOC
1.SPU and SKU
SPU (standardized product unit): similar to classes in java, that is, abstract
SKU (inventory unit): similar to an object, it has more specific values
Basic attribute (specification parameter): common attributes such as length.
Sales attribute: specific attributes
1. Shared specification parameters under classification (such as mobile phones and computers) are basic attributes. spu (such as Xiaomi mobile phones) is a type with many basic attributes. sku corresponds to sales attributes. If there is an attribute table, spu accounts for some basic attributes and sku accounts for some sales attributes. sku will be associated with spu, If it is the same spu, it will have the basic attributes owned by the spu. If classification is a large class, spu is a small class in the large class, and sku is a specific class.
2. Attribute grouping (attribute type, such as basic information (xx,xx,xx), main chip (yy,yy,yy)), in which the basic information contains many attributes, such as model, size, etc. the main chip's attributes may be performance, memory size, etc.
- Attributes and attribute groups are organized by three levels of classification
- Specification parameters are also basic attributes, and they have their own grouping
- Specification parameters can be retrieved
- The attribute name is determined, but the attribute is determined by the product
[the external chain image transfer fails. The source station may have an anti-theft chain mechanism. It is recommended to save the image and upload it directly (img-kv4roczo-1631265738661) (C: \ users \ 11914 \ appdata \ roaming \ typora \ user images \ image-20210906183807825. PNG)]
There are two types of tables. The first is three-level classification, attribute grouping and attribute. The three-level classification contains multiple attribute groups, and there are also many attributes under the attribute group. There is an association table between attribute groups and attributes, which is used to associate the attributes.
[the external chain image transfer fails. The source station may have an anti-theft chain mechanism. It is recommended to save the image and upload it directly (img-svpmhvil-1631265738663) (C: \ users \ 11914 \ appdata \ roaming \ typora \ typora user images \ image-20210906183617859. PNG)]
2. Attribute grouping
1. Introduce classification tree and attribute grouping components.
- Create category component tree
- Create attrgroup, and then introduce category component and add and update component. Show table columns
- Split through layout.
attrgroup.vue
<!-- --> <template> <div> <el-row :gutter="20"> <el-col :span="6" ><div class="grid-content bg-purple"> <category></category></div ></el-col> <el-col :span="18"> <div class="grid-content bg-purple"> <div class="mod-config"> <el-form :inline="true" :model="dataForm" @keyup.enter.native="getDataList()" > <el-form-item> <el-input v-model="dataForm.key" placeholder="Parameter name" clearable ></el-input> </el-form-item> <el-form-item> <el-button @click="getDataList()">query</el-button> <el-button v-if="isAuth('product:attrgroup:save')" type="primary" @click="addOrUpdateHandle()" >newly added</el-button > <el-button v-if="isAuth('product:attrgroup:delete')" type="danger" @click="deleteHandle()" :disabled="dataListSelections.length <= 0" >Batch delete</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="grouping id" > </el-table-column> <el-table-column prop="attrGroupName" header-align="center" align="center" label="Group name" > </el-table-column> <el-table-column prop="sort" header-align="center" align="center" label="sort" > </el-table-column> <el-table-column prop="descript" header-align="center" align="center" label="describe" > </el-table-column> <el-table-column prop="icon" header-align="center" align="center" label="Group icon" > </el-table-column> <el-table-column prop="catelogId" header-align="center" align="center" label="Classification id" > </el-table-column> <el-table-column fixed="right" header-align="center" align="center" width="150" label="operation" > <template slot-scope="scope"> <el-button type="text" size="small" @click="addOrUpdateHandle(scope.row.attrGroupId)" >modify</el-button > <el-button type="text" size="small" @click="deleteHandle(scope.row.attrGroupId)" >delete</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> <!-- Popup, newly added / modify --> <add-or-update v-if="addOrUpdateVisible" ref="addOrUpdate" @refreshDataList="getDataList" ></add-or-update> </div> </div> </el-col> </el-row> </div> </template> <script> //Other files can be imported here (such as components, tools js, third-party plug-ins js, json files, picture files, etc.) //For example: import component name from 'component path'; import Category from "../common/category.vue"; import AddOrUpdate from "./attrgroup-add-or-update.vue" export default { //Introduction component //The components introduced by import need to be injected into the object before they can be used components: { Category ,AddOrUpdate}, data() { return { dataForm: { key: "", }, dataList: [], pageIndex: 1, pageSize: 10, totalPage: 0, dataListLoading: false, dataListSelections: [], addOrUpdateVisible: false, }; }, //Listening properties are similar to the data concept computed: {}, //Monitor data changes in data watch: {}, //Method set methods: { // Get data list getDataList() { this.dataListLoading = true; this.$http({ url: this.$http.adornUrl("/product/attrgroup/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; }); }, // Number of pages per page sizeChangeHandle(val) { this.pageSize = val; this.pageIndex = 1; this.getDataList(); }, // Current page currentChangeHandle(val) { this.pageIndex = val; this.getDataList(); }, // Multiple choice selectionChangeHandle(val) { this.dataListSelections = val; }, // Add / modify addOrUpdateHandle(id) { this.addOrUpdateVisible = true; this.$nextTick(() => { this.$refs.addOrUpdate.init(id); }); }, // delete deleteHandle(id) { var ids = id ? [id] : this.dataListSelections.map((item) => { return item.attrGroupId; }); this.$confirm( `OK, yes[id=${ids.join(",")}]conduct[${id ? "delete" : "Batch delete"}]operation?`, "Tips", { confirmButtonText: "determine", cancelButtonText: "cancel", 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: "Operation succeeded", type: "success", duration: 1500, onClose: () => { this.getDataList(); }, }); } else { this.$message.error(data.msg); } }); }); }, }, //Lifecycle - creation complete (you can access the current instance of this) created() {}, //Lifecycle - Mount complete (DOM elements can be accessed) mounted() {}, beforeCreate() {}, //Lifecycle - before creating beforeMount() {}, //Lifecycle - before mounting beforeUpdate() {}, //Lifecycle - before updating updated() {}, //Lifecycle - after update beforeDestroy() {}, //Life cycle - before destruction destroyed() {}, //Life cycle - destruction complete activated() { this.getDataList(); }, //If the page has a keep alive cache function, this function will be triggered }; </script> <style> </style>
Key points: review the process of classification tree formation. And extract it. Then the table vue is generated by reverse engineering.
2. After clicking the node, the table can be displayed through the node information.
Idea:
① The tree component can bind a @ node click method, which can transfer node information, and then bind the tree node click method by clicking, and use this.$emit("parent component method binding name", parameter...);
② Once the tree node click method of the parent component bound to the child component in methods is triggered, these parameters will be sensed and obtained through the parent component's own method (embodied in code)
category.vue
<!-- --> <template> <el-tree :data="menus" :props="defaultProps" ref="menuTree" @node-click="nodeclick"> </el-tree> </template> <script> //Other files can be imported here (such as components, tools js, third-party plug-ins js, json files, picture files, etc.) //For example: import component name from 'component path'; export default { //The components introduced by import need to be injected into the object before they can be used components: {}, data() { //Data is stored here return { menus: [], defaultProps: { children: "children", label: "name", }, }; }, //Listening properties are similar to the data concept computed: {}, //Monitor data changes in data watch: {}, //Method set methods: { //Click event nodeclick(data,node,component){ console.log(data,node,component); //Send to parent component this.$emit("tree-node-click",data,node,component); } , getMenus() { this.$http({ url: this.$http.adornUrl("/product/category/list"), method: "get", }).then(({ data }) => { this.menus = data.data; console.log("Output corresponding data"); console.log(data); }); }, }, //Lifecycle - creation complete (you can access the current instance of this) created() { this.getMenus(); }, //Lifecycle - Mount complete (DOM elements can be accessed) mounted() {}, beforeCreate() {}, //Lifecycle - before creating beforeMount() {}, //Lifecycle - before mounting beforeUpdate() {}, //Lifecycle - before updating updated() {}, //Lifecycle - after update beforeDestroy() {}, //Life cycle - before destruction destroyed() {}, //Life cycle - destruction complete activated() {}, //If the page has a keep alive cache function, this function will be triggered }; </script> <style> </style>
attrgroup.vue section
Main part <el-col :span="6" ><div class="grid-content bg-purple"> <category @tree-node-click="treenodeclick"></category></div ></el-col> method part //Receive parameters of sub components treenodeclick(data,node,component){ console.log("data",data,"node",node,"component",component); }
3. Obtain the corresponding attribute group according to the directory id
thinking
① Write the catelog parameter passed in the list interface of AttrGroupController.
② Judge catelog_ Whether the ID is 0. If it is 0, query all directly. If not, splice the query string, including catalog_ id,group_ ID (EQ) and group_ Name (fuzzy query)
AttrGroupService
@Override public PageUtils queryPage(Map<String, Object> params, Long catelogId) { //1. Judge whether the category is 0 if(catelogId==0){ IPage<AttrGroupEntity> page = this.page( new Query<AttrGroupEntity>().getPage(params), new QueryWrapper<AttrGroupEntity>() ); return new PageUtils(page); }else{ //1.getKey String key = (String) params.get("key"); QueryWrapper<AttrGroupEntity> queryWrapper = new QueryWrapper<>(); queryWrapper.eq("catelog_id",catelogId); //2. Air judgment if(!StringUtils.isEmpty(key)) { //3. If not empty queryWrapper.and((item) -> { item.eq("attr_group_id", key).or().like("attr_group_name", key); }); } IPage<AttrGroupEntity> page = this.page( new Query<AttrGroupEntity>().getPage(params), queryWrapper ); return new PageUtils(page); } }
supplement
By supplementing yml, sql statements can be displayed through logs
logging: level: com.atguigu.gulimall.product: debug
4. Process the display table of the front-end page
thinking
① Add catalog to the url of getDataList_ ID transmission
② If the node with level equal to 3 is clicked, the table will be regenerated. That is, access the list to get data
methods: { //Receive parameters of sub components treenodeclick(data,node,component){ console.log("data",data,"node",node,"component",component); if(node.level==3){ //Assign a value to the query id this.catId=node.data.catId; //Regenerate menu this.getDataList(); } } , // Get data list 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; }); },
3. Group addition
1. Category display under group pop-up box
Idea:
① Replacement of cascade components
② Query menu
③ Replace the props of the cascade. (just imitate category)
④ To solve the problem that chidren is empty, add the jsoninclude attribute to the chidren attribute on the categoryEntity. If it is not empty, it can be assigned
@JsonInclude(JsonInclude.Include.NON_EMPTY) @TableField(exist = false) private List<CategoryEntity> children;
<el-cascader v-model="dataForm.catelogId" :options="category" :props="props" ></el-cascader>
Details: there are three levels of catalog id acquisition, that is, three ids. First obtain it with an ids, and then submit the last one when submitting.
2. Classification path (to be displayed in attribute classification) query
thinking
① Introducing catalogservice in attrGroupController
② After querying the group object, obtain the catalogid and give it to the catalogservice to query the Path
③ Query path through a recursion, if parentId is found= 0, then continue recursion. If not, add your id to the list.
catelogService
@Override public Long[] findCatelogPath(Long catelogId) { List<Long> path=new ArrayList<>(); //1. Query path List<Long> paths = findPath(catelogId, path); //2. Reverse path Collections.reverse(paths); return (Long[])paths.toArray(new Long[paths.size()]); } private List<Long> findPath(Long id,List<Long> path){ //1. Put it in first path.add(id); CategoryEntity byId = this.getById(id); //2. If the query finds that the parentId is not 0, continue the query if(byId.getParentCid()!=0){ findPath(byId.getParentCid(),path); } return path; }
3. Update catalogpath after dialog is closed. By adding a @ closed method. Finally, add a filterable to castor, that is, search in the category bar.
4. Brand management
1. The paging plug-in is imported and the transaction is started
@Configuration @EnableTransactionManagement @MapperScan("com.atguigu.gulimall.product.dao") public class MybatisConfig { @Bean public PaginationInterceptor paginationInterceptor(){ PaginationInterceptor paginationInterceptor=new PaginationInterceptor(); paginationInterceptor.setOverflow(true); paginationInterceptor.setLimit(500); return paginationInterceptor; } }
2. Query of brand management conditions
thinking
① The brandService determines that the key is empty. If it is not empty, the connection conditions will be queried
brandService
@Override public PageUtils queryPage(Map<String, Object> params) { //1. Get the key to judge String key = (String) params.get("key"); QueryWrapper<BrandEntity> queryWrapper = new QueryWrapper<>(); //2. Air judgment 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); }
3. Association classification
thinking
① The list of CategoryBrandController is rewritten to obtain data according to brandId
② Save the relationship between brand and category, and obtain the name through their respective id, so as not to associate many tables.
CategoryBrandService
@Override public void saveDeatail(CategoryBrandRelationEntity categoryBrandRelation) { //1. Get two IDS Long brandId = categoryBrandRelation.getBrandId(); Long catelogId = categoryBrandRelation.getCatelogId(); //2. Search two entities according to dao BrandEntity brandEntity = brandDao.selectById(brandId); CategoryEntity categoryEntity = categoryDao.selectById(catelogId); //3.setname categoryBrandRelation.setBrandName(brandEntity.getName()); categoryBrandRelation.setCatelogName(categoryEntity.getName()); //4. Save this.save(categoryBrandRelation); }
CategoryBrandController
@RequestMapping(value = "catelog/list",method = RequestMethod.GET) //@RequiresPermissions("product:categorybrandrelation:list") public R cateloglist(@RequestParam("brandId")Long brandId){ //Get all objects corresponding to brandId. List<CategoryBrandRelationEntity> page = categoryBrandRelationService.list( new QueryWrapper<CategoryBrandRelationEntity>().eq("brand_id", brandId) ); return R.ok().put("data", page); }
4. Modify the brand name - > brand classification relationship table
Idea:
① The classification controller executes updateDetail, and the name is null. If it is not null, the relationship controller is called to updateBrand
② Then, the relationship controller is used to complete the corresponding update operation - > service. Create an object, set the attribute, and then update updates according to the wrapper
CategoryBrandService
@RequestMapping(value = "catelog/list",method = RequestMethod.GET) //@RequiresPermissions("product:categorybrandrelation:list") public R cateloglist(@RequestParam("brandId")Long brandId){ //Get all objects corresponding to brandId. List<CategoryBrandRelationEntity> page = categoryBrandRelationService.list( new QueryWrapper<CategoryBrandRelationEntity>().eq("brand_id", brandId) ); return R.ok().put("data", page); }
BrandService
@Override public void updateDetail(BrandEntity brand) { String name = brand.getName(); Long brandId = brand.getBrandId(); //1. If so, it is not empty if(!StringUtils.isEmpty(name)){ //2. Update the brand name in the relationship categoryBrandRelationService.updateBrand(brandId,name); } this.updateById(brand); }
BrandController
@RequestMapping("/update") //@RequiresPermissions("product:brand:update") public R update(@Validated(value = UpdateGroup.class)@RequestBody BrandEntity brand){ brandService.updateDetail(brand); return R.ok(); }
5. Modify the classification name and the relationship table at the same time
thinking
① Similarly, when modifying the category name, call the service of the relationship to modify it at the same time
② The difference this time is that cascade deletion is adopted, and baseMapper is used for modification.
@Override
public void updateDetail(BrandEntity brand) {
String name = brand.getName();
Long brandId = brand.getBrandId();
//1. If so, it is not empty
if(!StringUtils.isEmpty(name)){
//2. Update the brand name in the relationship
categoryBrandRelationService.updateBrand(brandId,name);
}
this.updateById(brand); }
BrandController ```java @RequestMapping("/update") //@RequiresPermissions("product:brand:update") public R update(@Validated(value = UpdateGroup.class)@RequestBody BrandEntity brand){ brandService.updateDetail(brand); return R.ok(); }
5. Modify the classification name and the relationship table at the same time
thinking
① Similarly, when modifying the category name, call the service of the relationship to modify it at the same time
② The difference this time is that cascade deletion is adopted, and baseMapper is used for modification.